Compare commits

...

704 Commits

Author SHA1 Message Date
1d81d840cf Test
Signed-off-by: Bruno Rybársky <bruno@brn.systems>
2024-11-12 16:48:53 +01:00
b68721668b Test
Signed-off-by: Bruno Rybársky <bruno@brn.systems>
2024-11-12 16:47:12 +01:00
9aac124848 Test
Signed-off-by: Bruno Rybársky <bruno@brn.systems>
2024-11-12 16:44:52 +01:00
b102ec1f0d Test
Signed-off-by: Bruno Rybársky <bruno@brn.systems>
2024-11-12 16:43:08 +01:00
dd36e3fd6a Test
Signed-off-by: Bruno Rybársky <bruno@brn.systems>
2024-11-12 16:42:22 +01:00
8629e66fab Rollback
Signed-off-by: Bruno Rybársky <bruno@brn.systems>
2024-11-12 16:36:26 +01:00
71881c54dc Test swiper integration
Signed-off-by: Bruno Rybársky <bruno@brn.systems>
2024-11-12 16:31:44 +01:00
68725c5225 Merge remote-tracking branch 'refs/remotes/origin/main'
merge
2024-11-12 16:23:31 +01:00
34d7bba3ba Test slick integration 2024-11-12 16:22:40 +01:00
c8bb40bc8c Fix meme css 2024-06-06 10:13:12 +02:00
24c1c8165e Fix meme css 2024-06-06 10:12:41 +02:00
6e7f4b5422 Fix meme css 2024-06-06 10:02:38 +02:00
b2cea9bb74 Fix meme css 2024-06-06 10:01:59 +02:00
c273e93a4d Fix meme css 2024-06-06 10:00:28 +02:00
ee3afde9ec Fix meme css 2024-06-06 09:58:44 +02:00
e8a9a3f2ba Fix meme css 2024-06-06 09:57:48 +02:00
b90054f365 Fix meme css 2024-06-06 09:47:30 +02:00
e4d67236be Add link to logo 2024-06-06 09:45:39 +02:00
520554024b Add link to logo 2024-06-06 09:14:41 +02:00
f9b4278a76 Fix active 2024-06-06 09:12:12 +02:00
9a379aa6ed Fix login 2024-06-06 09:04:43 +02:00
ec40fa0a7a Add message to notes 2024-06-06 08:59:54 +02:00
12118b5ce3 Fix meme 2024-06-05 22:22:06 +02:00
1f425aadcc Fix meme 2024-06-05 22:17:15 +02:00
87f2129652 Merge remote-tracking branch 'refs/remotes/origin/main'
test
2024-06-05 20:09:08 +02:00
af837c2a6e Update 2024-06-05 20:08:02 +02:00
7f562b490c Edit meme creation 2024-05-16 10:16:50 +02:00
37f5748e2c Edit meme creation 2024-05-16 10:14:29 +02:00
c9d62ecaea Edit meme creation 2024-05-16 10:12:23 +02:00
7c383141c6 Edit meme creation 2024-05-16 10:08:25 +02:00
b1386c23f2 Edit meme creation 2024-05-16 10:05:21 +02:00
5a4c9ef839 Edit meme creation 2024-05-16 10:03:26 +02:00
1e526fd38a Add survey 2024-05-15 19:49:36 +02:00
8ae319ec62 Add survey 2024-05-15 19:42:11 +02:00
66a881e960 Add survey 2024-05-15 19:41:00 +02:00
3f14542348 Add survey 2024-05-15 19:40:22 +02:00
983d74dff3 Add survey 2024-05-15 19:38:19 +02:00
ffcc6e5ce3 Add survey 2024-05-15 19:34:37 +02:00
40bc1eb615 Add survey 2024-05-15 19:25:50 +02:00
c63b91f0cd Add survey 2024-05-15 19:24:51 +02:00
d049848bff Add survey 2024-05-15 19:23:02 +02:00
9d222c203c Add survey 2024-05-15 19:18:15 +02:00
455fe2b3ac Test meme filtering 2024-05-01 17:36:35 +02:00
23c42e6c31 Test meme filtering 2024-05-01 17:34:58 +02:00
f0a2c067be Test meme filtering 2024-05-01 17:33:45 +02:00
91f8c3331e Test meme filtering 2024-05-01 17:29:27 +02:00
cf46f1043a Test meme filtering 2024-05-01 17:27:56 +02:00
c7b4f1c3c3 Test meme filtering 2024-05-01 17:25:46 +02:00
3e4a0ce45c Test meme filtering 2024-05-01 17:25:10 +02:00
4556b3c349 Test meme filtering 2024-05-01 17:23:57 +02:00
6fc026f70b Change navbar styling a bit 2024-04-28 23:04:07 +02:00
2343f254e4 Change navbar styling a bit 2024-04-28 23:03:54 +02:00
f4129bdf8d Change navbar styling a bit 2024-04-28 23:03:41 +02:00
7518b0ee99 Change navbar styling a bit 2024-04-28 23:03:27 +02:00
2d4fbeca68 Change navbar styling a bit 2024-04-28 23:02:54 +02:00
4126c05ed4 Change navbar styling a bit 2024-04-28 23:02:09 +02:00
8136f87cf7 Change navbar styling a bit 2024-04-28 23:01:32 +02:00
969009eed1 Change JS to not write to classList directly 2024-04-28 22:41:20 +02:00
1c9f5cf3c0 Add PHPDocs generated by ChatGPT,
add additional clarification to some functions,
add addNewsComment function and API, currently untested and not implemented in the client,
fix a bunch of stuff that PHPStorm pointed out
2024-04-28 22:37:23 +02:00
6e7df7f034 let mysql do work 2024-04-28 18:16:01 +02:00
f28d024bcd Update meme voting 2024-04-27 12:21:06 +02:00
00c84717df Update meme voting 2024-04-27 12:20:07 +02:00
56de200241 Update meme voting 2024-04-27 12:19:35 +02:00
bd24b8bdb3 Update meme voting 2024-04-27 12:19:16 +02:00
8702a549ab Update meme voting 2024-04-27 12:17:20 +02:00
cad1e333fe Update meme voting 2024-04-27 12:12:42 +02:00
3678460366 Update meme voting 2024-04-27 12:04:54 +02:00
0e63a29e39 Update meme voting 2024-04-27 11:58:34 +02:00
d6b8057053 Update meme voting 2024-04-27 11:57:07 +02:00
8af1b6f073 Update meme voting 2024-04-27 11:56:05 +02:00
f3657fd341 Update meme voting 2024-04-27 11:55:33 +02:00
c4a79c9435 Update meme voting 2024-04-27 11:52:39 +02:00
2ad7192802 Update meme voting 2024-04-27 11:52:22 +02:00
4d93405556 Update meme voting 2024-04-27 11:51:01 +02:00
b0dae7da6d Update meme voting 2024-04-27 11:49:53 +02:00
adb738c12f Update meme voting 2024-04-27 11:48:58 +02:00
287c2050e0 Update meme voting 2024-04-27 11:34:20 +02:00
cb234de269 Update meme voting 2024-04-27 11:33:55 +02:00
11955e740c Update meme voting 2024-04-27 11:33:04 +02:00
7bbbc64099 Update meme voting 2024-04-27 11:30:42 +02:00
a62d553190 Update meme voting 2024-04-27 11:29:42 +02:00
5bbd4c2e74 Update meme voting 2024-04-27 11:29:09 +02:00
3116e66c47 Update meme voting 2024-04-27 11:28:30 +02:00
7b5f418344 Update meme voting 2024-04-27 11:23:42 +02:00
f251d18bcb Test meme voting 2024-04-27 10:43:58 +02:00
9fe7eb14e9 Test meme voting 2024-04-27 10:32:07 +02:00
cd487d155c Test meme voting 2024-04-27 10:30:01 +02:00
154cff0372 Rewrite some stuff 2024-04-26 20:24:09 +02:00
c5b5cc9722 Rewrite some stuff 2024-04-26 15:12:05 +02:00
e52253b017 Rewrite some stuff 2024-04-26 15:11:22 +02:00
1f4fa8eb72 Rewrite some stuff 2024-04-26 15:09:45 +02:00
2949e59045 Rewrite some stuff 2024-04-26 15:06:08 +02:00
0f2498a0fa Rewrite some stuff 2024-04-26 15:04:43 +02:00
3be664d8c7 Rewrite some stuff 2024-04-26 15:03:12 +02:00
8177d33304 Rewrite some stuff 2024-04-26 15:00:18 +02:00
c41e3c2049 Rewrite some stuff 2024-04-26 14:59:04 +02:00
0a19b9a778 Rewrite some stuff 2024-04-26 14:57:09 +02:00
b9184a7c04 Rewrite some stuff 2024-04-26 14:56:40 +02:00
ec4df02b65 Rewrite some stuff 2024-04-26 14:55:45 +02:00
f1165f2c51 Stuff 2024-04-26 14:37:54 +02:00
1f1e4997fa Remove debug 2024-04-26 12:08:10 +02:00
dbfea88177 Fix 2024-04-26 10:52:11 +02:00
496d381ace Remove debug 2024-04-26 10:50:35 +02:00
07eda1b9da Remove debug 2024-04-26 10:50:03 +02:00
580880f0c5 Remove debug 2024-04-26 10:48:07 +02:00
9ac85a1afd Am I dumb? 2024-04-26 10:47:42 +02:00
64ccb94755 Fix 2024-04-26 10:46:34 +02:00
a403a65a69 Fix 2024-04-26 10:45:39 +02:00
fdce25fe98 Fix 2024-04-26 10:43:53 +02:00
baddeb86ed Fix 2024-04-26 10:42:04 +02:00
1c0a7355cc Fix 2024-04-26 10:39:54 +02:00
6b7bbaf8c9 Fix 2024-04-26 10:39:00 +02:00
0da696d733 Fix 2024-04-26 10:34:13 +02:00
72287e315d Fix 2024-04-26 09:51:40 +02:00
688a68042c Fix 2024-04-26 09:46:10 +02:00
c261e28427 MEME 2024-04-26 01:33:20 +02:00
f775a4d0cb MEME 2024-04-26 01:30:17 +02:00
cf0d3990c7 MEME 2024-04-26 01:28:52 +02:00
e5e4ff8b51 MEME 2024-04-26 01:28:33 +02:00
5c66ddc44f MEME 2024-04-26 01:27:53 +02:00
052861ff3f MEME 2024-04-26 01:26:37 +02:00
134cc40163 MEME 2024-04-26 01:24:50 +02:00
22314c67fe MEME 2024-04-26 01:23:03 +02:00
12037732b1 MEME 2024-04-26 01:21:44 +02:00
44126aa507 MEME 2024-04-26 01:20:39 +02:00
7f1311a254 MEME 2024-04-26 01:19:58 +02:00
a0d371ec04 MEME 2024-04-26 01:19:19 +02:00
24f4242523 MEME 2024-04-26 01:19:00 +02:00
20ca96ba05 MEME 2024-04-26 01:17:49 +02:00
0fca1b000b MEME 2024-04-25 23:46:23 +02:00
5c57b098f1 MEME 2024-04-25 23:44:14 +02:00
ae433ebc5a MEME 2024-04-25 23:42:39 +02:00
2cdac31673 MEME 2024-04-25 23:42:07 +02:00
3074bfc502 MEME 2024-04-25 23:40:27 +02:00
5cee6d3044 MEME 2024-04-25 23:38:13 +02:00
afbeff1b08 MEME 2024-04-25 23:37:52 +02:00
cd240b7d9f MEME 2024-04-25 23:35:43 +02:00
f5c70d0be2 MEME 2024-04-25 23:32:26 +02:00
b9003bcbd0 MEME 2024-04-25 23:32:02 +02:00
fa5ae71cef MEME 2024-04-25 23:31:41 +02:00
d96f74096b MEME 2024-04-25 23:30:34 +02:00
df0975313d MEME 2024-04-25 23:29:48 +02:00
1d23855343 MEME 2024-04-25 23:29:29 +02:00
936e751042 MEME 2024-04-25 23:29:11 +02:00
2eea8f2e88 MEME 2024-04-25 23:28:55 +02:00
06ad8d3627 MEME 2024-04-25 23:28:32 +02:00
79d5722bfb MEME 2024-04-25 23:28:08 +02:00
e0ae787569 MEME 2024-04-25 23:27:53 +02:00
37982115ed MEME 2024-04-25 23:27:04 +02:00
a507b507a5 MEME 2024-04-25 23:25:43 +02:00
826df55412 MEME 2024-04-25 23:23:48 +02:00
3912b5b1e2 MEME 2024-04-25 23:21:51 +02:00
5fae9e9747 MEME 2024-04-25 23:21:05 +02:00
b6b1fe7b0e MEME 2024-04-25 23:20:46 +02:00
cf7368ba4f MEME 2024-04-25 23:20:14 +02:00
e7f177e4cc MEME 2024-04-25 23:19:30 +02:00
c159b0b749 MEME 2024-04-25 23:19:11 +02:00
3d1eb77e2f MEME 2024-04-25 23:18:26 +02:00
7a74b06fab MEME 2024-04-25 23:17:57 +02:00
260789af82 MEME 2024-04-25 23:17:22 +02:00
d021302fd1 MEME 2024-04-25 23:16:57 +02:00
ace3ea61f7 MEME 2024-04-25 23:16:13 +02:00
74c44f36f9 MEME 2024-04-25 23:15:45 +02:00
16716a9b18 MEME 2024-04-25 23:12:16 +02:00
f824b0c236 MEME 2024-04-25 23:08:30 +02:00
7da94c06d4 MEME 2024-04-25 22:55:00 +02:00
99d4f076f9 MEME 2024-04-25 22:53:14 +02:00
16f9dedce2 MEME 2024-04-25 22:51:51 +02:00
5f6a2f8b15 MEME 2024-04-25 22:50:35 +02:00
6da1f475f8 MEME 2024-04-25 22:48:37 +02:00
1e38547f07 MEME 2024-04-25 22:47:06 +02:00
8fff030655 MEME 2024-04-25 22:45:54 +02:00
d2d23d1514 MEME 2024-04-25 22:45:07 +02:00
03556a1642 MEME 2024-04-25 22:43:07 +02:00
a33d5c1eb7 MEME 2024-04-25 22:41:23 +02:00
36913a7496 MEME 2024-04-25 22:38:27 +02:00
4996e52d4d MEME 2024-04-25 22:38:04 +02:00
b38b4726d9 Adlerka SMP FTW 2024-04-25 22:08:02 +02:00
e74a9c8578 Test file list 2024-04-25 15:05:33 +02:00
ceae8082d6 Test file list 2024-04-25 15:05:03 +02:00
c3d775efaa Test file list 2024-04-25 15:02:38 +02:00
b764dfad8b Test file list 2024-04-25 15:01:17 +02:00
c73d471fab Add file upload 2024-04-25 10:49:45 +02:00
8c471ca067 Add file upload 2024-04-25 10:48:02 +02:00
8e51278f41 Add file upload 2024-04-25 10:46:52 +02:00
1b8901145d Add file upload 2024-04-25 10:44:20 +02:00
ff0bc7c184 Add file upload 2024-04-25 10:43:11 +02:00
93f6b38748 Add file upload 2024-04-25 10:30:22 +02:00
74eef045d8 Add file upload 2024-04-25 10:21:53 +02:00
aa493a0b51 Add file upload 2024-04-25 10:20:32 +02:00
2f49e6aa8a Add file upload 2024-04-25 10:19:24 +02:00
eb3086cb12 Test meme rendering 2024-04-25 09:48:58 +02:00
6e824ffafd Test meme rendering 2024-04-25 09:45:40 +02:00
9f1cfc2ed4 Test meme rendering 2024-04-25 09:45:28 +02:00
8f392da809 Test meme rendering 2024-04-25 09:41:43 +02:00
0253ed6e04 Test meme rendering 2024-04-25 09:40:21 +02:00
7382c6ea4b Test css 2024-04-25 09:12:50 +02:00
46d4ef40c8 Test css 2024-04-25 09:11:40 +02:00
8da8a01869 Test css 2024-04-25 09:05:10 +02:00
2f204dd9f2 Merge remote-tracking branch 'origin/main' 2024-04-25 09:04:15 +02:00
359a985bcc Test css 2024-04-25 09:04:10 +02:00
9ac6421efc Update lib/endpoint.php 2024-04-11 15:27:06 +02:00
638f092432 Update lib/endpoint.php 2024-04-11 15:18:17 +02:00
e4c37a8c2d Fix 2024-04-11 15:15:41 +02:00
9a23a0802f Merge remote-tracking branch 'origin/main' 2024-04-11 10:36:47 +02:00
277232bdd1 Do something 2024-04-11 10:36:40 +02:00
eba1ed9539 Adlerka SMP FTW 2024-04-03 22:15:29 +02:00
87b3ac68d3 Adlerka SMP FTW 2024-03-30 15:43:57 +01:00
5590d61f14 Adlerka SMP FTW 2024-03-30 15:30:35 +01:00
d736605784 kanYE 2024-03-26 21:10:30 +01:00
704e96b650 kanYE 2024-03-26 21:07:22 +01:00
298a925041 Fix css 2024-03-26 21:06:04 +01:00
8032533cff Fix css 2024-03-26 21:03:17 +01:00
64dcc976c1 Fix css 2024-03-26 20:51:41 +01:00
6487c945f9 Fix css 2024-03-26 20:45:39 +01:00
dfdaa1e867 Test pico css 2024-03-26 20:32:43 +01:00
154fc4103f Verification ignore 2024-03-13 23:19:28 +01:00
8e0ac08ce6 PHPStorm stuff 2024-03-10 22:55:29 +01:00
4a2712af3c Add upload backend 2024-03-01 22:14:20 +01:00
0c2cb115bf Change style.css 2024-02-29 14:53:25 +01:00
29c675476d Fix Header 2024-02-29 10:20:45 +01:00
1bbd8f5e92 Fix Header 2024-02-29 10:20:11 +01:00
fcd3817d49 Fix Header 2024-02-29 10:18:57 +01:00
5f8ac07883 Fix Account 2024-02-29 10:18:03 +01:00
4b3163c24f Fix Account 2024-02-29 10:13:57 +01:00
805c2b61d9 Fix Rozvrh 2024-02-29 10:13:06 +01:00
c8ebd4f647 Fix Rozvrh 2024-02-29 10:12:22 +01:00
0454dac844 Fix Rozvrh 2024-02-29 10:07:20 +01:00
efcb93f8ad Fix Rozvrh 2024-02-29 10:05:32 +01:00
0fbb478beb Fix Rozvrh 2024-02-29 10:00:29 +01:00
21e75ad253 Fix grid 2024-02-29 09:56:16 +01:00
74f3c9513d Fix grid 2024-02-29 09:53:22 +01:00
4c4b49a4e0 Fix grid 2024-02-29 09:48:52 +01:00
0baef8a140 Remove comments 2024-02-29 09:47:03 +01:00
5cf3198356 Test grid 2024-02-29 09:44:47 +01:00
aa90d2aa6c Test grid 2024-02-29 09:43:11 +01:00
7434e35ffe Test grid 2024-02-29 09:39:20 +01:00
6d0be27c2e Fix title 2024-02-29 09:37:30 +01:00
573431aea1 Fix table 2024-02-29 09:33:52 +01:00
48cfe2744b Fix page title 2024-02-29 09:32:59 +01:00
fede2c3e83 Typo 2024-02-29 09:28:25 +01:00
287eb99b98 Merge remote-tracking branch 'origin/main' 2024-02-29 09:28:13 +01:00
7285aca748 Test 2024-02-29 09:27:44 +01:00
cda602f0ae Fix article 2024-02-25 20:34:29 +01:00
b386ff5fbd Fix navigation 2024-02-24 10:11:18 +01:00
201a728e7d Fix navigation 2024-02-24 10:10:08 +01:00
178e1e7e19 Fix navigation 2024-02-24 10:08:47 +01:00
4d829d6b76 Fix navigation 2024-02-24 10:06:58 +01:00
4f5732ad83 Fix page title 2024-02-24 10:04:16 +01:00
21e78d3ceb Fix page title 2024-02-24 09:23:09 +01:00
1650e852cd Fix page title 2024-02-24 09:20:10 +01:00
e08874f9d2 Fix page title 2024-02-24 09:19:44 +01:00
02ecffdb7f Fix page title 2024-02-24 09:18:31 +01:00
e77ecfbf93 Fix page title 2024-02-24 09:17:49 +01:00
7798e41e28 Fix page title 2024-02-24 09:16:21 +01:00
b51f3d9c6f Fix articles 2024-02-24 09:08:58 +01:00
148254b2f8 Fix articles 2024-02-24 09:06:28 +01:00
0418edfcf9 Fix articles 2024-02-24 09:04:56 +01:00
0cc87762fc Fix articles 2024-02-24 09:04:04 +01:00
e5a2fe8a1b Fix articles 2024-02-24 09:01:55 +01:00
d508231265 Fix articles 2024-02-24 09:01:13 +01:00
cb0c400f81 Fix articles 2024-02-22 18:27:55 +01:00
ae86216732 Fix articles 2024-02-22 18:26:11 +01:00
1372041271 Fix articles 2024-02-22 18:25:21 +01:00
17d303fb3d Fix articles 2024-02-22 18:17:12 +01:00
37d8577629 Fix articles 2024-02-22 18:16:42 +01:00
c905e929f6 Fix articles 2024-02-22 18:08:12 +01:00
5ede5b37ab Fix articles 2024-02-22 18:03:39 +01:00
ec9b4aa51b Fix articles 2024-02-22 18:00:30 +01:00
0ac17350d6 Fix user list 2024-02-22 17:54:46 +01:00
d5bc8931eb Fix user list 2024-02-22 17:52:20 +01:00
69b02ea332 Hide creation on create 2024-02-22 14:09:14 +01:00
cd46a52f83 Make it autorefresh 2024-02-22 14:08:55 +01:00
4b288b48c8 Test 2024-02-22 13:57:05 +01:00
c74feeb8cd Add article creation frontend 2024-02-22 12:28:16 +01:00
37c0827cdf Add article creation frontend 2024-02-22 12:25:32 +01:00
e0636c4411 Add article creation frontend 2024-02-22 12:25:03 +01:00
f9cf2eab8f Add article creation frontend 2024-02-22 12:24:09 +01:00
c478da8088 Add article creation frontend 2024-02-22 12:19:57 +01:00
aa1d474dfb Add article creation frontend 2024-02-22 12:18:20 +01:00
4531ecd12b Add article creation frontend 2024-02-22 10:50:48 +01:00
45948df88d Add article creation frontend 2024-02-22 10:50:21 +01:00
7396983bcf Add article creation frontend 2024-02-22 10:49:22 +01:00
e81dc8e8ac Add article creation frontend 2024-02-22 10:48:49 +01:00
5090c8e4bd Add article creation frontend 2024-02-22 10:48:06 +01:00
ac95ba5b60 Add article creation frontend 2024-02-22 10:43:18 +01:00
5be90d8e95 Add article creation 2024-02-22 10:05:09 +01:00
06feb93095 Add article page 2024-02-22 09:42:37 +01:00
26a0a7488c Test page title 2024-02-15 10:51:23 +01:00
dca021b5b0 Test page title 2024-02-15 10:50:22 +01:00
fa0a521e70 Test page title 2024-02-15 10:49:27 +01:00
9a2afd7528 Test page title 2024-02-15 10:49:19 +01:00
3344d7412c Create sitemap 2024-02-15 10:41:48 +01:00
e37387f084 Create sitemap 2024-02-15 10:39:02 +01:00
051ca0d101 Fix 2024-02-15 10:23:54 +01:00
5be1dac216 Fix 2024-02-15 10:23:22 +01:00
c4265543d4 Fix 2024-02-15 10:20:29 +01:00
23b96a3942 Create sitemap 2024-02-15 10:19:52 +01:00
92c470aade Create sitemap 2024-02-15 10:17:09 +01:00
30937e8d75 Remove umami init 2024-02-15 10:12:07 +01:00
a493f322e6 SEO test 2024-02-15 10:09:01 +01:00
64076f11df Logo test 2024-02-15 09:46:45 +01:00
66aec85b44 Logo test 2024-02-15 09:42:44 +01:00
473806425f Fix user list 2024-02-14 21:27:21 +01:00
3d73142e0d Fix css on tables 2024-02-14 21:05:13 +01:00
e3de6a085f Fix css on tables 2024-02-14 21:04:50 +01:00
9767f7557e Merge remote-tracking branch 'origin/main' 2024-02-14 21:03:47 +01:00
227b890468 Fix css on tables 2024-02-14 21:03:43 +01:00
6a9b1c151c work! 2024-02-09 10:49:58 +01:00
64e0bdf61f lets see 2024-02-09 10:49:01 +01:00
1383d47c41 put h1 on dashboard into jeader 2024-02-09 10:40:38 +01:00
f234ea0685 more marign more 2024-02-09 10:38:41 +01:00
2c561957f5 ID > Email 2024-02-08 10:49:14 +01:00
de377d74f8 add br to gitignore 2024-02-08 00:14:42 +01:00
b863d68873 minify remixicon 2024-02-07 23:51:11 +01:00
20dc6a27c6 Merge remote-tracking branch 'origin/main' 2024-02-07 23:05:58 +01:00
cd5aca3177 Add umami init event 2024-02-07 23:05:52 +01:00
333393a4fa WROK 2024-02-07 21:21:59 +01:00
53a20dca4a why still no work 2024-02-07 21:19:17 +01:00
6dba60d461 my bad 2024-02-07 21:17:18 +01:00
e53190ae30 test 2024-02-07 21:14:16 +01:00
9eddb45799 asf 2024-02-07 21:09:41 +01:00
2ab5806f90 test 2024-02-07 21:08:44 +01:00
6712b4a721 WORK 2024-02-07 21:06:54 +01:00
f83467e564 transition work 2024-02-07 21:06:07 +01:00
00d03ae4e1 test 2024-02-07 21:04:59 +01:00
fcd635f21e test 2024-02-07 21:03:02 +01:00
1e12225268 test 2024-02-07 21:01:54 +01:00
74b58c8139 test 2024-02-07 21:00:59 +01:00
d9567844d6 this is it 2024-02-07 21:00:09 +01:00
12941dd230 wooooooork 2024-02-07 20:58:31 +01:00
6c23c423a8 test 2024-02-07 20:57:27 +01:00
e40d0814e1 te 2024-02-07 20:55:52 +01:00
b4aa036c66 test 2024-02-07 20:54:01 +01:00
a47abfcd14 test 2024-02-07 20:52:48 +01:00
03b0e2fafa ert 2024-02-07 20:50:35 +01:00
449c1233cc Changed relative to inherit because my head was in my arse ig 2024-02-07 20:42:18 +01:00
9bda0a2ed7 work 2024-02-07 20:40:36 +01:00
519189d54d test 2024-02-07 20:39:38 +01:00
57c41c4cc2 tešst 2024-02-07 20:38:15 +01:00
262e6d8f0a Test JS 2024-02-07 18:43:04 +01:00
29bdcda68c Test JS 2024-02-07 18:41:15 +01:00
083fa5ac9e Test JS 2024-02-07 18:38:36 +01:00
fd8e5e9ebe Test JS 2024-02-07 18:31:01 +01:00
c4c9e021e4 Test JS 2024-02-07 18:17:38 +01:00
b698f9e284 Merge remote-tracking branch 'origin/main' 2024-02-07 18:13:29 +01:00
612baabb63 Test JS 2024-02-07 18:13:24 +01:00
b5ca251d10 Make navsite_list only full with when mobile 2024-02-07 18:07:54 +01:00
811871f518 asdf 2024-02-07 10:52:00 +01:00
64434aec99 asdf 2024-02-07 10:50:52 +01:00
93af1192f0 Merge branch 'main' of brn.systems:Adleraci/adlerka.top 2024-02-07 10:48:00 +01:00
06551b50f1 ysdf 2024-02-07 10:47:52 +01:00
928448075b Merge remote-tracking branch 'origin/main' 2024-02-07 10:47:15 +01:00
2d40f079af add footer 2024-02-07 10:47:02 +01:00
457bdfae7b wre 2024-02-07 08:42:30 +01:00
9134b8fe72 asdf 2024-02-07 08:41:20 +01:00
279c152e39 pls fix 2024-02-07 08:39:55 +01:00
a536b8dfb6 asdf 2024-02-07 08:30:53 +01:00
7455607455 test 2024-02-07 08:29:48 +01:00
206f2d9f3b asd 2024-02-07 08:27:55 +01:00
cb4f476c51 adf 2024-02-07 08:07:45 +01:00
36bde74d23 test 2024-02-07 08:06:55 +01:00
dfefcd2f51 testsadf 2024-02-07 08:01:02 +01:00
da52afa2ff test 2024-02-07 08:00:11 +01:00
4bd066e755 Test CSS 2024-02-06 21:57:53 +01:00
87e1c88445 Test CSS 2024-02-06 21:56:13 +01:00
564dde301c Test CSS 2024-02-06 21:52:09 +01:00
74f249f5e0 Test CSS 2024-02-06 21:50:55 +01:00
808848250b Test CSS 2024-02-06 21:49:59 +01:00
bfbdfac8a3 Fix input styling 2024-02-06 21:16:30 +01:00
2a8cf98e5f Fix input styling 2024-02-06 21:15:49 +01:00
b090c40747 Fix input styling 2024-02-06 21:14:55 +01:00
53f673c651 Fix input styling 2024-02-06 21:14:17 +01:00
e9d64554c1 Fix input styling 2024-02-06 21:13:59 +01:00
a9a04f3300 Fix input styling 2024-02-06 21:13:37 +01:00
6458ab82a1 Make statusmessage disappear sooner 2024-02-06 21:11:37 +01:00
e6de882141 Fix statusmessages 2024-02-06 21:10:35 +01:00
e369d2f4de Make first one silent 2024-02-06 21:09:33 +01:00
d2a057d105 add favicon 2024-02-06 21:07:11 +01:00
d9fbd61cdd Fix js 2024-02-06 20:59:03 +01:00
ee3d2810c0 Fix js 2024-02-06 20:58:14 +01:00
77e8cfe3ea Fix js 2024-02-06 20:57:25 +01:00
34fbecdbad Fix js 2024-02-06 20:55:33 +01:00
6fe20be187 Fix js 2024-02-06 20:53:57 +01:00
30ead7c469 Fix error pages 2024-02-06 20:52:27 +01:00
d5f3a1bb59 Fix error pages 2024-02-06 20:51:44 +01:00
80c278ca99 Fix order 2024-02-06 20:48:30 +01:00
7a7321f69d test UserInfo 2024-02-06 20:46:31 +01:00
356b99b7ca fix navpages 2024-02-06 20:31:20 +01:00
475c10ff32 fix navpages 2024-02-06 20:27:48 +01:00
c85246e81c fix navpages 2024-02-06 20:23:38 +01:00
82e8eb68b7 fix navpages 2024-02-06 20:21:59 +01:00
f17fbaed2d fix statusmessage 2024-02-06 20:21:28 +01:00
dbfae461f3 Fix async 2024-02-06 20:19:36 +01:00
f06337c2c7 Fix async 2024-02-06 20:12:09 +01:00
3cf29154aa Fix async 2024-02-06 20:10:06 +01:00
ee20b77602 Fix logout redirect 2024-02-06 20:02:29 +01:00
dc6b1589d3 Fix dashboard script 2024-02-06 20:00:32 +01:00
dafe34665c Totally original css and js, totally not "borrowed" from twip-network.org 2024-02-06 19:51:46 +01:00
5ad3c77fa3 Totally original css and js, totally not "borrowed" from twip-network.org 2024-02-06 19:49:50 +01:00
4c1864d6a1 test css 2024-02-06 19:40:31 +01:00
1fd03f30d9 fix statusmessage 2024-02-06 19:37:04 +01:00
76aca603ea fix statusmessage 2024-02-06 19:33:25 +01:00
47258fea97 fix id 2024-02-06 19:23:49 +01:00
e16024ff43 test 2024-02-06 19:23:06 +01:00
b4c23070a9 make body flex 2024-02-06 17:14:00 +01:00
0a71f6ec39 make body flex 2024-02-06 17:13:00 +01:00
a7acb28b2c make body flex 2024-02-06 17:12:01 +01:00
054a829ae0 make body flex 2024-02-06 17:11:04 +01:00
e495c31ed5 add footer 2024-02-06 17:09:28 +01:00
4a8c122645 disable ajax track 2024-02-06 17:06:09 +01:00
3820e63095 add umami tracking 2024-02-06 17:04:56 +01:00
67ce374953 add umami 2024-02-06 16:54:14 +01:00
4f42628137 test 2024-02-06 16:47:51 +01:00
7cee788ae5 test history 2024-02-06 16:38:23 +01:00
4c748a1007 fix js 2024-02-06 16:37:23 +01:00
6f4e8a4b43 remove debug print 2024-02-06 16:35:47 +01:00
28f7af8536 test 2024-02-06 16:34:53 +01:00
ef11821f45 test 2024-02-06 16:31:28 +01:00
cfe1cc196c test 2024-02-06 16:29:26 +01:00
0b089f944a test 2024-02-06 16:28:59 +01:00
6c1f6b1571 test 2024-02-06 16:28:36 +01:00
72bd8b8bd1 refactor 2024-02-06 16:24:57 +01:00
38895b1502 wait for fetch to finish 2024-02-05 23:07:21 +01:00
52e30c1e99 wait for fetch to finish 2024-02-05 23:07:09 +01:00
f33837f023 debug prints 2024-02-05 23:01:26 +01:00
3947bbb52a Fix script on open page 2024-02-05 22:56:20 +01:00
36de27df7a Fix script on open page 2024-02-05 22:54:55 +01:00
7dbb38913b Fix script on open page 2024-02-05 22:52:44 +01:00
2cade060cf Always include nav.html 2024-02-05 22:26:02 +01:00
89d5cccc31 Centralize scripts to fix ajax 2024-02-05 22:22:52 +01:00
554d2d93cb Fix login 2024-02-05 22:15:10 +01:00
9db95d6e1f Fix login 2024-02-05 22:12:00 +01:00
c95dad64a4 Fix navigation 2024-02-05 22:10:07 +01:00
6587f530e4 Fix navigation 2024-02-05 22:06:34 +01:00
5fe6f8c976 Fix navigation 2024-02-05 22:05:29 +01:00
9d26c19ee2 Fix navigation 2024-02-05 22:02:16 +01:00
a562e0bf22 Merge pull request 'main' (#1) from Kurk/adlerka.top:main into main
Reviewed-on: #1
2024-02-05 21:53:36 +01:00
c8e2f75b7e Merge remote-tracking branch 'origin/main' 2024-02-05 21:42:23 +01:00
fc5d60d0a5 add size to logo 2024-02-05 21:42:16 +01:00
23efe25681 Merge remote-tracking branch 'origin/main' 2024-02-05 21:37:47 +01:00
914057680d Change the page endpoint 2024-02-05 21:37:42 +01:00
49ce991a43 Merge branch 'main' of https://git.brn.systems/Adleraci/adlerka.top 2024-02-05 21:29:41 +01:00
0b3e6438e9 Merge branch 'main' of https://git.brn.systems/Adleraci/adlerka.top 2024-02-05 21:29:30 +01:00
01fd8389ac run ajax after loaded 2024-02-05 21:29:29 +01:00
d88cbb4759 aers 2024-02-05 21:29:29 +01:00
0c5ed29424 Merge remote-tracking branch 'origin/main' 2024-02-05 21:25:50 +01:00
45fe0e1144 run ajax after loaded 2024-02-05 21:25:41 +01:00
effbc5c726 Merge branch 'main' of https://git.brn.systems/Adleraci/adlerka.top 2024-02-05 21:24:11 +01:00
3608aa655f asd 2024-02-05 21:24:08 +01:00
6493ca7be7 Merge remote-tracking branch 'origin/main' 2024-02-05 21:23:27 +01:00
38c0269665 run ajax after loaded 2024-02-05 21:23:21 +01:00
5b0aaa1c89 Update pages/News/index.html 2024-02-05 21:23:14 +01:00
460f38a74d test 2024-02-05 21:22:50 +01:00
8a741ae90f Merge branch 'main' of https://git.brn.systems/Adleraci/adlerka.top 2024-02-05 21:22:17 +01:00
46a981cbbf tet 2024-02-05 21:22:16 +01:00
1629082e8d Merge remote-tracking branch 'origin/main' 2024-02-05 21:21:12 +01:00
2b29d0df16 ajax test 2024-02-05 21:21:04 +01:00
e3ec3c782b Update pages/News/index.html 2024-02-05 21:17:54 +01:00
e2ece6f848 Add pages/News/index 2024-02-05 21:16:27 +01:00
a1ae4ea805 Delete pages/test 2024-02-05 21:12:51 +01:00
52e50c76ec Add pages/test 2024-02-05 21:12:23 +01:00
79a8cc0f0a aasdgfg 2024-02-05 12:39:03 +01:00
1737c6cc13 sdf 2024-02-05 12:37:14 +01:00
5584a61560 Me failure 2024-02-05 10:47:46 +01:00
409d214538 test inlining 2024-02-04 11:15:54 +01:00
c5aa686997 test inlining 2024-02-04 11:13:46 +01:00
988b40e335 test inlining 2024-02-04 11:13:22 +01:00
20588d48aa test inlining 2024-02-04 11:12:54 +01:00
704e580a38 test inlining 2024-02-04 11:12:42 +01:00
98bf4ccb58 test inlining 2024-02-04 11:12:20 +01:00
7ade5ce039 test inlining 2024-02-04 11:12:03 +01:00
4078b61fa8 test inlining 2024-02-04 11:11:48 +01:00
f46690a814 test inlining 2024-02-04 11:11:38 +01:00
b5f16cc109 test inlining 2024-02-04 11:11:03 +01:00
3fbe49aa44 test inlining 2024-02-04 11:10:47 +01:00
3183e3c346 test inlining 2024-02-04 11:10:27 +01:00
98ca161624 test inlining 2024-02-04 11:09:23 +01:00
9fa5e55093 test inlining 2024-02-04 11:08:40 +01:00
03a2852f97 test inlining 2024-02-04 11:08:12 +01:00
3f410c5002 test inlining 2024-02-04 11:07:37 +01:00
c66ca9e8ac test inlining 2024-02-04 11:05:14 +01:00
17992f3e16 test inlining 2024-02-04 11:04:37 +01:00
169afc4ba5 test inlining 2024-02-04 11:01:16 +01:00
611280a738 test inlining 2024-02-04 10:59:35 +01:00
b8b9bc985e test inlining 2024-02-04 10:58:46 +01:00
dc0ed52b06 test inlining 2024-02-04 10:57:09 +01:00
1d1b31f035 test inlining 2024-02-04 10:55:12 +01:00
e4e10863c5 test inlining 2024-02-04 10:53:20 +01:00
7f90bacd4b test inlining 2024-02-04 10:52:35 +01:00
9c06142954 test inlining 2024-02-04 10:51:34 +01:00
42c3454094 test inlining 2024-02-04 10:48:42 +01:00
1ab5b5dc7c test inlining 2024-02-04 10:48:07 +01:00
d94a66ca2b test inlining 2024-02-04 10:45:44 +01:00
882c055346 test inlining 2024-02-04 10:42:54 +01:00
68d77a2138 test inlining 2024-02-04 10:42:36 +01:00
b59414f836 test inlining 2024-02-04 10:39:04 +01:00
b2c69ee231 test inlining 2024-02-04 10:37:06 +01:00
a904f050f6 test inlining 2024-02-04 10:35:27 +01:00
bf3a523ab2 test inlining 2024-02-04 10:34:57 +01:00
1e82eb9ecd test inlining 2024-02-04 10:33:54 +01:00
67f07debea test inlining 2024-02-04 10:32:08 +01:00
40eee27fbb test inlining 2024-02-04 10:30:59 +01:00
76cafbda3a test inlining 2024-02-04 10:30:39 +01:00
3c21c5e2f8 test inlining 2024-02-04 10:28:34 +01:00
729a80b647 test inlining 2024-02-04 10:26:46 +01:00
20d8f2513c test inlining 2024-02-04 10:24:52 +01:00
f67c6a6186 test inlining 2024-02-04 10:21:56 +01:00
d53d0b1f54 make remixicon local 2024-02-04 10:16:51 +01:00
7828553752 Admin ui 2024-02-04 10:03:55 +01:00
06750b8630 css is hard 2024-02-04 10:02:03 +01:00
0b7c8e91bd css is hard 2024-02-04 10:01:11 +01:00
9841f68b5d Change to defer 2024-02-04 09:59:51 +01:00
8e44b5aaf0 user ui changes 2024-02-04 09:57:48 +01:00
e409287632 admin ui changes 2024-02-04 09:55:47 +01:00
f3ea959f50 admin ui changes 2024-02-04 09:54:58 +01:00
95dcef5426 admin ui changes 2024-02-04 09:54:21 +01:00
547c8a1dd7 admin ui changes 2024-02-04 09:53:18 +01:00
f71c250a3a admin ui changes 2024-02-04 09:50:04 +01:00
e514573ebb test 2024-02-04 09:28:38 +01:00
8ab13c554e test 2024-02-04 09:26:17 +01:00
69dfb64ce4 fix autoload 2024-02-04 09:21:00 +01:00
c4a9dd9593 autoload user info 2024-02-04 09:19:35 +01:00
88b1391b34 test 2024-02-04 09:11:01 +01:00
6b08e4e8ad test 2024-02-04 09:01:28 +01:00
c2cb5d4473 fix 2024-02-04 09:00:24 +01:00
07b0c76c26 Centralize session update 2024-02-04 08:57:35 +01:00
3f74009488 Merge remote-tracking branch 'origin/main' 2024-02-03 23:01:40 +01:00
79ebf9f49f make it more consistent 2024-02-03 23:01:20 +01:00
97420e7f4b twsdfsdgf 2024-02-03 22:56:14 +01:00
49043d8abe asdf 2024-02-03 22:55:43 +01:00
6f22d39134 test 2024-02-03 22:55:09 +01:00
9636e1ad89 asd 2024-02-03 22:54:14 +01:00
6d607fbc52 asd 2024-02-03 22:53:17 +01:00
bd9ec07e55 DFasdf 2024-02-03 22:51:38 +01:00
9fda1b9195 lets see 2024-02-03 22:48:07 +01:00
2275d1623a add silent 2024-02-03 22:44:01 +01:00
a31a92b692 Clear token on register 2024-02-03 18:18:51 +01:00
6284848a7e Remove stuff 2024-02-03 18:15:41 +01:00
7f172db61c Remove stuff 2024-02-03 18:15:19 +01:00
b8323683f8 Remove id 2024-02-03 18:14:07 +01:00
69250ff804 Change field to email 2024-02-03 18:11:28 +01:00
856c3c8fdc Fix register 2024-02-03 18:09:48 +01:00
5c2d906fb0 Merge register and login into one 2024-02-03 18:06:15 +01:00
e3901499cd Edit APIs 2024-02-03 17:57:41 +01:00
1e037cd6a2 Edit APIs 2024-02-03 17:55:54 +01:00
8b7e465796 Edit userinfo 2024-02-03 17:43:50 +01:00
1e8877c87e Edit userinfo 2024-02-03 17:42:57 +01:00
bc4e698b35 Message only in one place. 2024-02-03 17:37:56 +01:00
4ab6dcee02 Message only in one place. 2024-02-03 17:35:45 +01:00
6f07524342 some more stuff 2024-02-03 17:30:51 +01:00
87c12b0bb4 remove debug print 2024-02-03 17:16:44 +01:00
e3bc9f4989 test 2024-02-03 17:16:23 +01:00
6753603ecd test 2024-02-03 17:15:21 +01:00
8073d6c29e debug print 2024-02-03 17:12:18 +01:00
2213f97f37 debug print 2024-02-03 17:11:41 +01:00
e37bfdd291 debug print 2024-02-03 17:10:54 +01:00
0b7a8c0252 fix 2024-02-03 17:05:31 +01:00
68fcaa6540 fix 2024-02-03 17:01:52 +01:00
fcb556914c remove debug print 2024-02-03 16:59:56 +01:00
f380a889e2 fix 2024-02-03 16:59:27 +01:00
b1792bd29a debug print 2024-02-03 16:57:28 +01:00
50d9fd2e39 fix 2024-02-03 16:54:50 +01:00
472c74a0c4 remove error handling 2024-02-03 16:54:04 +01:00
04117e3aaa error handling 2024-02-03 16:48:36 +01:00
0249aad0a3 error handling 2024-02-03 16:46:09 +01:00
a2edadd640 test 2024-02-03 16:34:42 +01:00
6fdd76de3d test 2024-02-03 16:33:00 +01:00
530c7ca13c test 2024-02-03 16:30:11 +01:00
5a359eaf4c test 2024-02-03 16:29:19 +01:00
e22e438c40 Fix 2024-02-03 16:27:42 +01:00
874310222a Fix 2024-02-03 16:25:24 +01:00
71f62f0e9c Convert return type of dynamic pages 2024-02-03 16:24:48 +01:00
8a0b5e3e3a Convert dynamic page metadata to variable 2024-02-03 16:21:28 +01:00
6ccbe82189 Fix a bunch of stuff 2024-02-03 16:18:48 +01:00
15964cf109 Implement a bunch of stuff 2024-02-03 16:08:26 +01:00
e3722e3ef7 Change up js 2024-02-02 19:17:21 +01:00
f0327df728 exit on redirect 2024-02-02 16:37:20 +01:00
2edc604327 exit on redirect 2024-02-02 16:37:15 +01:00
ac6ceb77a1 exit on redirect 2024-02-02 16:35:32 +01:00
aa0db2d582 exit on redirect 2024-02-02 16:33:53 +01:00
5a86c772f6 exit on redirect 2024-02-02 16:29:24 +01:00
7070ea9349 exit on redirect 2024-02-02 16:26:38 +01:00
05be7c9ee9 exit on redirect 2024-02-02 16:25:03 +01:00
a965afc454 exit on redirect 2024-02-02 16:23:54 +01:00
8f78663419 exit on redirect 2024-02-02 16:23:31 +01:00
a121abd910 exit on redirect 2024-02-02 16:22:36 +01:00
6861f5cf03 exit on redirect 2024-02-02 16:19:29 +01:00
b3b855d505 exit on redirect 2024-02-02 16:18:54 +01:00
033ab0cffd exit on redirect 2024-02-02 16:00:21 +01:00
b903a2db02 fix redir from home to apex 2024-02-02 15:59:36 +01:00
eeb043d8d7 fix redir from home to apex 2024-02-02 15:58:01 +01:00
edfb7b26f9 redir from home to apex 2024-02-02 15:57:26 +01:00
0c49410012 fix 2024-02-02 15:55:23 +01:00
4eff9ddb73 debug print 2024-02-02 15:53:32 +01:00
c0fbaec15a test apex domain 2024-02-02 15:50:39 +01:00
e8601b708c dokelu 2024-02-02 12:42:21 +01:00
4e8c621e0b dokelu 2024-02-02 12:41:52 +01:00
9ad8af8c6f dokelu 2024-02-02 12:41:02 +01:00
7f6afabab2 dokelu 2024-02-02 12:38:18 +01:00
5a087db6d2 test 2024-02-02 12:34:12 +01:00
f45200ee01 test 2024-02-02 12:33:16 +01:00
1510e416a6 convert to jsonless 2024-02-02 12:28:37 +01:00
0c4f37bab3 convert to jsonless 2024-02-02 12:20:17 +01:00
014a23377c asfdasdf 2024-02-02 10:45:01 +01:00
d07c2bd0ca Merge branch 'main' of brn.systems:Adleraci/adlerka.top 2024-02-02 10:42:41 +01:00
fa762a2ccd switching to remixicon 2024-02-02 08:55:43 +01:00
3cbeb4368a fix logic 2024-02-02 00:45:07 +01:00
2ed53291d6 fix logic 2024-02-02 00:44:07 +01:00
dd590ef492 remove debug prints 2024-02-02 00:42:27 +01:00
479e9a782d why 2024-02-02 00:41:57 +01:00
e4ca769bb8 debug print 2024-02-02 00:40:17 +01:00
01e0d34d42 debug print 2024-02-02 00:37:10 +01:00
2fe817e4c1 debug print 2024-02-02 00:34:56 +01:00
6e63f56fe2 fix dynamic pages 2024-02-01 13:44:00 +01:00
7920fa5b1d add todo 2024-02-01 10:52:03 +01:00
055cc20400 remove debug prints 2024-02-01 10:51:40 +01:00
cbcbb9f4b0 raise permission level by one
fix sql columns
2024-02-01 10:50:07 +01:00
f442a8f34b debug prints again 2024-02-01 10:20:12 +01:00
4d73c81c89 debug prints again 2024-02-01 10:19:28 +01:00
8208980118 debug prints again 2024-02-01 10:18:13 +01:00
aa5d35656a debug prints again 2024-02-01 10:17:46 +01:00
55b4e17a77 fix css 2024-02-01 10:16:36 +01:00
60bf24ec77 dont show pages you dont have permission to in the navigation 2024-02-01 10:14:49 +01:00
f2e3ac6bcd change page secret 2024-02-01 10:08:45 +01:00
775c40ff9a fix logic
remove debug prints
2024-02-01 10:06:50 +01:00
15ad67fdba is it empty? 2024-02-01 10:03:07 +01:00
c3ed87cf57 what is this variable 2024-02-01 10:02:29 +01:00
ac279a143e more debug print 2024-02-01 10:01:41 +01:00
c78f86a600 fix stupid mistake 2024-02-01 09:59:02 +01:00
2a4d852611 yet another debug print 2024-02-01 09:57:45 +01:00
efbb04ff0a yet another debug print 2024-02-01 09:55:25 +01:00
76a0568ee2 add per page scripts and styles 2024-02-01 09:46:52 +01:00
166ac751c8 initialize user 2024-02-01 09:38:16 +01:00
baa5105be5 more debug prints 2024-02-01 09:27:50 +01:00
1a3dc537b7 fix stupid mistake 2024-02-01 09:26:53 +01:00
1f915ce374 add debug print 2024-02-01 09:24:58 +01:00
f1dc7d866f test 2024-02-01 09:10:55 +01:00
3b3a504c9f Merge branch 'main' of brn.systems:Adleraci/adlerka.top
# Conflicts:
#	lib/account.php
#	lib/page.php
2024-02-01 08:02:46 +01:00
f3c7285463 test 2024-02-01 08:02:27 +01:00
8ba1637176 Add some more account actions,
Add return types,
Add some stuff
2024-01-31 23:07:12 +01:00
3d22ff555e Add some more account actions,
Add return types,
2024-01-31 22:05:23 +01:00
a4fd20ad00 test 2024-01-23 16:06:15 +01:00
d22a55d52c login test gpt chat ai helped (did everyhtging t) 2024-01-19 12:16:20 +01:00
13686d85dc fix stupid mistakes 2024-01-18 11:53:39 +01:00
7ae7b03ec3 Merge branch 'main' of brn.systems:Adleraci/adlerka.top 2024-01-18 11:49:54 +01:00
e4bb8f10a3 big changes hehe 2024-01-18 11:49:38 +01:00
445fdbe947 test 2024-01-17 13:54:10 +01:00
a0315ebb3f brčné miesto 2024-01-17 13:53:08 +01:00
ab19c71018 asdt 2024-01-17 13:52:12 +01:00
fd35888287 changes 2024-01-17 13:50:45 +01:00
821445c36b changes 2024-01-17 13:48:21 +01:00
6964c70dd8 Indents 2024-01-17 13:48:02 +01:00
8335278a75 display: block; 2024-01-17 13:46:10 +01:00
6dba647d55 nové veci 2024-01-17 13:45:11 +01:00
1e37ff3458 added icons to login 2024-01-17 11:24:19 +01:00
36bb7f98a3 added ® to Minecraft®™ 2024-01-17 11:21:16 +01:00
bd12c3064a asdg 2024-01-17 11:08:07 +01:00
155d8aff9f i forgor ; 2024-01-17 11:06:53 +01:00
4867edf630 more php testing 2024-01-17 11:06:02 +01:00
26cc5822ee php testing 2024-01-17 11:04:39 +01:00
4daf15a2c3 slow the border down 2024-01-17 11:00:26 +01:00
1119d1197d shortened transition delay 2024-01-17 10:59:56 +01:00
c40ab30296 slow animations of dropdown 2024-01-17 10:59:26 +01:00
c5fa19cfd5 relative pos 2024-01-17 10:56:24 +01:00
466f6952d4 z-index 2024-01-17 10:54:43 +01:00
b785babb3f commenty preč 2024-01-17 10:49:52 +01:00
83d8553e39 Merge branch 'main' of brn.systems:Adleraci/adlerka.top 2024-01-17 10:48:36 +01:00
7cf586603a test 2024-01-17 10:47:44 +01:00
e1ac973bc0 stuiff 2024-01-17 10:45:41 +01:00
1023e77ad3 and now for .feature-list 2024-01-17 10:44:07 +01:00
22e3f57538 fit content for header ul 2024-01-17 10:42:37 +01:00
aaf99f9923 style.css fit content for header ul li 2024-01-17 10:38:12 +01:00
8f18be71d2 Soem changes to the SMP website 2024-01-17 10:37:05 +01:00
abcfc23346 sdf 2024-01-17 10:33:01 +01:00
c21a095b49 styling 2024-01-17 10:32:48 +01:00
3b0c46faaf Removed gap in header ul 2024-01-17 10:32:16 +01:00
c9399092cf flexujeme (header ul changes) 2024-01-17 10:31:08 +01:00
86cc29ed5c changes to smp/domov.html 2024-01-17 10:28:47 +01:00
7fe463fb9a changes to home/domov.html 2024-01-17 10:22:35 +01:00
704d3d2028 added Zošit (notes) 2024-01-17 10:19:24 +01:00
7a2b20381a remove ::after tag 2024-01-17 09:48:30 +01:00
5276087409 test 2024-01-17 09:47:33 +01:00
9eff94b3fa fix 2024-01-17 09:46:54 +01:00
6f68b559f7 test 2024-01-17 09:46:23 +01:00
5e558c685d test 2024-01-17 09:45:18 +01:00
c62c916ca4 atr 2024-01-17 09:43:20 +01:00
8629a73e67 gaps 2024-01-17 09:42:47 +01:00
fbbf034fa1 line cahnges 2024-01-17 09:41:40 +01:00
4b521763cf centering 2024-01-17 08:45:51 +01:00
885638e40c i did php!! (well what bruno told me) also styles B) 2024-01-17 08:44:13 +01:00
e54ea002dc height and gap change for nav in style.css 2024-01-17 08:40:48 +01:00
3fb91a5351 len test!!!!! 2024-01-17 08:35:59 +01:00
f9efb1e704 test 2024-01-17 08:33:00 +01:00
c0aba27007 asdf 2024-01-16 22:07:47 +01:00
a394f9af48 ASDG 2024-01-16 22:07:15 +01:00
07b828c2f3 less taeanising 2024-01-16 22:03:50 +01:00
b5941e8686 SADFHG 2024-01-16 22:03:12 +01:00
f423c34883 more changes i guess? 2024-01-16 22:02:45 +01:00
117e41f08e test 2024-01-16 22:01:08 +01:00
5929703205 changes 2024-01-16 22:00:38 +01:00
4d98e2199d changes 2024-01-16 21:59:56 +01:00
879fd5c868 more changed 2024-01-16 21:59:18 +01:00
a3015a910a some achjangečd :) 2024-01-16 21:57:23 +01:00
78 changed files with 13325 additions and 415 deletions

4
.gitignore vendored
View File

@@ -2,3 +2,7 @@
secrets
secrets/
secrets/config.php
qodana.yaml
*.br
.br
google*.html

3
.jshintrc Normal file
View File

@@ -0,0 +1,3 @@
{
"esversion": 10
}

View File

@@ -1,7 +1,7 @@
# Adlerka.Top
The code for Adlerka.top
The code for https://adlerka.top
[![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit/)

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

4
assets/3rdparty/pico.min.css vendored Normal file

File diff suppressed because one or more lines are too long

196
assets/3rdparty/sliderm.css vendored Normal file

File diff suppressed because one or more lines are too long

595
assets/3rdparty/sliderm.js vendored Normal file
View File

@@ -0,0 +1,595 @@
(() => {
"use strict";
(() => {
function getDom(t) {
return function hasDom(t) {
return null !== document.querySelector(t)
}(t) ? document.querySelector(t) : null
}
function setDom(t) {
for (var e, n = document.createElement(t), o = arguments.length, i = new Array(o > 1 ? o - 1 : 0), r = 1; r < o; r++) i[r - 1] = arguments[r];
return (e = n.classList).add.apply(e, i), n
}
function findDom(t, e) {
try {
return t.querySelector(e)
} catch (t) {
return null
}
}
var t = "sliderm", e = "sliderm__slides", n = "sliderm__paginations", o = "sliderm__pagination",
i = "sliderm__slide--clone";
function isInteger(t) {
return Number.isFinite(t)
}
var r = function bold(t) {
var e = {1: "thin", 2: "regular", 3: "bold"};
return void 0 !== e[t] ? e[t] : "regular"
}, a = function shape(t) {
return "none" === t || "square" === t ? t : "circle"
}, s = function size(t) {
return isInteger(t) && 16 !== t ? t <= 13 ? 13 : t >= 28 ? 28 : t : null
}, l = function bgColor(t) {
return "#000000" !== t ? t : null
}, u = function color(t) {
return "#ffffff" !== t ? t : null
}, c = function opacity(t) {
return !isInteger(t) || t > 1 || t < .1 || .5 === t ? null : t
};
function queue(t) {
var e = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : 0;
return setTimeout((function () {
t()
}), e)
}
var p = [function breakpoint(t, e) {
if (t.getOption("breakpoint")) {
!function init() {
var n = t.getOption("columns"), o = t.getOption("breakpoint.columns"),
i = Number(e.getAttribute("data-columns")), r = function calculate(t, e) {
var n = window.innerWidth, o = Object.keys(t).filter((function (e) {
return n < t[e]
}));
return void 0 !== o[0] ? Number(o[0]) : e
}(o, n);
if (e.setAttribute("data-columns", n), void 0 !== r && i !== r) {
var a = t.getItems();
t.updateOption("columns", r), t.updateCurrentItems();
for (var s = 0; s < a.length; s += 1) t.go("columns", a[s]);
t.emit("breakpoint.changed")
}
}()
}
}, function transition(t, e) {
for (var n = arguments.length, o = new Array(n > 2 ? n - 2 : 0), i = 2; i < n; i++) o[i - 2] = arguments[i];
var r = o[0], a = t.getOption("duration");
"stop" !== r ? (e.style.setProperty("transition-duration", "".concat(a, "ms")), t.on("destory", (function () {
e.style.removeProperty("transition-duration")
}))) : e.style.removeProperty("transition-duration")
}, function transform(t, e) {
for (var n = arguments.length, o = new Array(n > 2 ? n - 2 : 0), i = 2; i < n; i++) o[i - 2] = arguments[i];
var r = o[0];
e.style.setProperty("transform", "translateX(".concat(r, "px)")), t.on("destory", (function () {
e.style.removeProperty("transform")
}))
}, function autoplay(t) {
if (t.getOption("autoplay")) {
var e = t.getOption("autoplay.duration"), n = "left" === t.getOption("autoplay.direction") ? "<" : ">",
o = function repeat(t) {
return setInterval((function () {
t()
}), arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : 5e3)
}((function () {
t.slideTo(n)
}), e);
t.on("destory", (function () {
!function stop(t) {
clearInterval(t), clearTimeout(t)
}(o)
}))
}
}, function grouping(t, e) {
for (var n = arguments.length, o = new Array(n > 2 ? n - 2 : 0), i = 2; i < n; i++) o[i - 2] = arguments[i];
var r = o[0], a = o[1], s = t.getOption("grouping"), l = a + 1;
if (s) {
var u = t.getOption("columns"), c = Math.ceil((a + 1) / u);
r.setAttribute("data-order", c)
} else r.setAttribute("data-order", l);
t.on("destory", (function () {
r.removeAttribute("data-order")
}))
}, function columns(t, e) {
for (var n = arguments.length, o = new Array(n > 2 ? n - 2 : 0), i = 2; i < n; i++) o[i - 2] = arguments[i];
var r = o[0], a = t.getOption("columns"), s = parseFloat((1 / a * 100).toFixed(2));
r.style.setProperty("flex", "0 0 ".concat(s, "%")), t.on("destory", (function () {
r.style.removeProperty("flex")
}))
}, function preview(t, e) {
if (t.getOption("preview")) {
var n = t.getOption("preview.edge");
e.style.setProperty("padding", "0 ".concat(n, "px")), t.on("destory", (function () {
e.style.removeProperty("padding")
}))
}
}, function spacing(t, e) {
for (var n = arguments.length, o = new Array(n > 2 ? n - 2 : 0), i = 2; i < n; i++) o[i - 2] = arguments[i];
var r = o[0], a = Math.floor(t.getOption("spacing") / 2);
r.style.setProperty("padding", "0px ".concat(a, "px")), t.on("destory", (function () {
r.style.removeProperty("padding")
}))
}, function align(t, e) {
var n = t.getOption("align");
"center" === n ? e.style.setProperty("align-items", "center") : "bottom" === n && e.style.setProperty("align-items", "flex-end"), t.on("destory", (function () {
e.style.removeProperty("align-items")
}))
}, function touch(t, e) {
if (t.getOption("touch")) {
!function init() {
var n = t.getOption("touch.threshold"), o = t.getOption("touch.duration"), i = t.adaptEvent(e),
r = {x: 0, y: 0, time: 0};
i.on("touchstart", (function (t) {
t.preventDefault();
var e = t.changedTouches[0];
r.x = e.pageX, r.y = e.pageY, r.time = (new Date).getTime()
})), i.on("touchmove", (function (t) {
t.preventDefault()
})), i.on("touchend", (function (e) {
e.preventDefault();
var i = e.changedTouches[0], a = (new Date).getTime() - r.time, s = Math.abs(i.pageX - r.x);
if (!(a > o || s < n)) {
var l = i.pageX > r.x ? ">" : "<";
t.slideTo(l)
}
}))
}()
}
}, function clone(t, e) {
for (var n = arguments.length, o = new Array(n > 2 ? n - 2 : 0), r = 2; r < n; r++) o[r - 2] = arguments[r];
var a = o[0], s = o[1], l = t.getOption("columns"), u = t.getOption("preview"), c = t.getOption("loop");
if (u || c) {
var p = t.getItemCount(), d = t.getItems(), f = l, h = a.cloneNode(!0), v = null, m = !1;
h.classList.add(i), s < f && (e.appendChild(h), m = !0), s >= p - f && (m ? ((v = a.cloneNode(!0)).classList.add(i), e.insertBefore(v, d[0])) : e.insertBefore(h, d[0])), t.on("destory", (function () {
h.remove(), v && v.remove()
}))
}
}, function slide(t, e) {
for (var n = arguments.length, o = new Array(n > 2 ? n - 2 : 0), i = 2; i < n; i++) o[i - 2] = arguments[i];
var r = o[0], a = o[1], s = t.getOption("grouping"), l = t.getOption("preview"),
u = t.getOption("duration"), c = t.getOption("columns"), p = t.getOption("loop"),
d = t.getItems()[0].offsetWidth, f = t.getPage(), h = p || l, v = f.maximum(), m = f.calculate(r, !1),
g = m < 1 || m > v, y = 0;
!p && g || (y = s ? d * (0 - (h ? 0 : -1) - m) * c : d * (1 - (h ? c : 0) - m), t.emit("slide.start"), t.go("transition", a), t.go("transform", y), t.updatePosition(m), g ? queue((function () {
m = f.calculate(r, g), y = s ? d * (0 - m) * c : d * (1 - c - m), t.go("transition", "stop"), t.go("transform", y), t.updatePosition(m), t.emit("slide.end")
}), u + 10) : t.emit("slide.end"))
}, function loop(t, e) {
var n = t.getOption("loop"), o = t.getOption("grouping");
if (n && o) {
var i = t.getItems(), r = t.getOption("columns"), a = t.getItemCount(), s = i[i.length - 1],
l = r - a % r, u = [];
if (l !== r && 1 !== r) {
for (var c = 1; c <= l; c += 1) {
var p = s.cloneNode(!0);
p.classList.add("sliderm__slide--empty"), p.innerHTML = "", e.appendChild(p), u.push(p)
}
t.updateCurrentItems(), t.on("destory", (function () {
u.forEach((function (t) {
t.remove()
}))
}))
}
}
}, function init(e) {
var n = e.getOption("duration"), o = e.getRoot();
o.classList.add(t), o.classList.remove("".concat(t, "--initialized")), o.classList.add("".concat(t, "--initialize")), e.on("initialized", (function () {
queue((function () {
o.classList.remove("".concat(t, "--initialize")), o.classList.add("".concat(t, "--initialized"))
}), n + 50)
}))
}], d = [function pagination(t) {
var e, i, r, a = function click(e) {
if (o === e.target.className) {
var n = Array.prototype.indexOf.call(i.childNodes, e.target) + 1;
t.slideTo(n)
}
}, s = function mark() {
var e = t.getPosition(), o = findDom(t.getRoot(), ".".concat(n)).children;
Array.from(o).forEach((function (t, n) {
var o = n + 1;
t.removeAttribute("data-active"), o === e && t.setAttribute("data-active", !0)
}))
}, l = function destory() {
r.off("click", a), t.off("slide.end", s), i.remove()
}, u = function init() {
!function render() {
var a = setDom("div", n);
e = t.getPage().maximum();
for (var s = 0; s < e; s += 1) {
var l = setDom("div", o);
0 === s && l.setAttribute("data-active", !0), a.append(l)
}
i = a, r = t.adaptEvent(i), t.getRoot().append(i)
}(), function listen() {
r.on("click", a), t.on("slide.end", s)
}()
};
t.on("destory", l), t.on("breakpoint.changed", (function () {
l(), u()
})), u()
}, function spinner(t) {
!function init() {
var e = t.getOption("spinner.color"), n = setDom("div", "sliderm__spinner");
n.style.setProperty("color", e), t.getRoot().append(n), t.on("destory", (function () {
n.remove()
}))
}()
}, function arrow(t) {
!function init() {
for (var e = [s, r, a, u, l, c], n = setDom("div", "sliderm__button--previous"), o = setDom("div", "sliderm__button--next"), i = t.adaptEvent(n), p = t.adaptEvent(o), d = null, f = null, h = 0; h < e.length; h += 1) {
var v = e[h].name, m = e[h](t.getOption("arrow.".concat(v)));
null !== m && ("bold" === v ? (d = setDom("span", "sliderm__icon-left--".concat(m)), f = setDom("span", "sliderm__icon-right--".concat(m))) : "shape" === v ? (n.classList.add("sliderm__button--".concat(m)), o.classList.add("sliderm__button--".concat(m))) : ("bgColor" === v ? v = "background-color" : "size" === v && (v = "font-size", m = "".concat(m, "px")), n.style.setProperty(v, m), o.style.setProperty(v, m)))
}
n.append(d), o.append(f), t.getRoot().append(n), t.getRoot().append(o), i.on("click", (function () {
t.slideTo("<")
})), p.on("click", (function () {
t.slideTo(">")
})), t.on("destory", (function () {
n.remove(), o.remove()
}))
}()
}];
function _defineProperties(t, e) {
for (var n = 0; n < e.length; n++) {
var o = e[n];
o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(t, o.key, o)
}
}
var f = function () {
function EventDispatcher() {
!function _classCallCheck(t, e) {
if (!(t instanceof e)) throw new TypeError("Cannot call a class as a function")
}(this, EventDispatcher), this.events = {}
}
return function _createClass(t, e, n) {
return e && _defineProperties(t.prototype, e), n && _defineProperties(t, n), Object.defineProperty(t, "prototype", {writable: !1}), t
}(EventDispatcher, [{
key: "on", value: function on(t, e) {
Object.prototype.hasOwnProperty.call(this.events, t) || (this.events[t] = []), this.events[t].push(e)
}
}, {
key: "off", value: function off(t, e) {
var n = this;
void 0 === e ? delete this.events[t] : this.events[t].forEach((function (o, i) {
o === e && n.events[t].splice(i, 1)
}))
}
}, {
key: "emit", value: function emit(t) {
for (var e = arguments.length, n = new Array(e > 1 ? e - 1 : 0), o = 1; o < e; o++) n[o - 1] = arguments[o];
void 0 !== this.events[t] && Array.isArray(this.events[t]) && this.events[t].forEach((function (t) {
t.apply(void 0, n)
}))
}
}, {
key: "destory", value: function destory() {
delete this.events
}
}]), EventDispatcher
}();
function event_adapter_defineProperties(t, e) {
for (var n = 0; n < e.length; n++) {
var o = e[n];
o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(t, o.key, o)
}
}
var h = function () {
function EventAdapter(t) {
!function event_adapter_classCallCheck(t, e) {
if (!(t instanceof e)) throw new TypeError("Cannot call a class as a function")
}(this, EventAdapter), this.target = t, this.events = {}
}
return function event_adapter_createClass(t, e, n) {
return e && event_adapter_defineProperties(t.prototype, e), n && event_adapter_defineProperties(t, n), Object.defineProperty(t, "prototype", {writable: !1}), t
}(EventAdapter, [{
key: "on", value: function on(t, e) {
this.events[t] = e, this.target.addEventListener(t, this.events[t])
}
}, {
key: "off", value: function off(t) {
this.target.removeEventListener(t, this.events[t])
}
}, {
key: "emit", value: function emit(t) {
void 0 !== this.events[t] && this.target.dispatchEvent(new Event(t))
}
}, {
key: "destory", value: function destory() {
for (var t = Object.keys(this.events), e = 0; e < t.length; e += 1) this.off(t[e]);
delete this.events
}
}, {
key: "mock", value: function mock(t, e) {
void 0 !== this.events[t] && this.events[t](e)
}
}]), EventAdapter
}();
function error(t) {
console.error("[Sliderm] ".concat(t))
}
const v = {
arrow: !0,
pagination: !0,
spinner: !0,
grouping: !1,
loop: !0,
preview: !1,
breakpoint: !0,
touch: !0,
autoplay: !1,
columns: 4,
duration: 1e3,
spacing: 10,
align: "center",
extensions: [],
_arrow: {color: "#ffffff", bgColor: "#000000", opacity: .5, size: 16, shape: "circle", bold: 2},
_preview: {edge: 40},
_spinner: {color: "#1cbbb4"},
_breakpoint: {columns: {4: !1, 3: 960, 2: 768, 1: 420}},
_touch: {threshold: 10, duration: 300},
_autoplay: {direction: "right", duration: 5e3}
};
function page_defineProperties(t, e) {
for (var n = 0; n < e.length; n++) {
var o = e[n];
o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(t, o.key, o)
}
}
var m = function () {
function Page(t) {
!function page_classCallCheck(t, e) {
if (!(t instanceof e)) throw new TypeError("Cannot call a class as a function")
}(this, Page), this.sliderm = t
}
return function page_createClass(t, e, n) {
return e && page_defineProperties(t.prototype, e), n && page_defineProperties(t, n), Object.defineProperty(t, "prototype", {writable: !1}), t
}(Page, [{
key: "calculate", value: function calculate() {
for (var t = arguments.length, e = new Array(t), n = 0; n < t; n++) e[n] = arguments[n];
var o = e[0], i = e[1], r = this.sliderm.getOption("columns"),
a = this.sliderm.getOption("grouping"), s = this.sliderm.getItemCount(),
l = this.sliderm.getGroupCount(), u = this.sliderm.getPosition(), c = a ? l : s, p = u, d = 0;
if (a) {
var f = Math.ceil(u * r / r);
p = f
}
return "number" == typeof o ? d = o : ">" === o ? (d = p + 1) > c && i && (d = 1) : "<" === o && (d = p - 1) <= 0 && i && (d = c), d
}
}, {
key: "maximum", value: function maximum() {
var t = this.sliderm.getOption("loop"), e = this.sliderm.getOption("preview"),
n = this.sliderm.getOption("grouping"), o = this.sliderm.getOption("columns"), i = t || e;
return n ? this.sliderm.getGroupCount() : i ? this.sliderm.getItemCount() : this.sliderm.getItemCount() - o + 1
}
}]), Page
}();
function _toConsumableArray(t) {
return function _arrayWithoutHoles(t) {
if (Array.isArray(t)) return _arrayLikeToArray(t)
}(t) || function _iterableToArray(t) {
if ("undefined" != typeof Symbol && null != t[Symbol.iterator] || null != t["@@iterator"]) return Array.from(t)
}(t) || _unsupportedIterableToArray(t) || function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")
}()
}
function _slicedToArray(t, e) {
return function _arrayWithHoles(t) {
if (Array.isArray(t)) return t
}(t) || function _iterableToArrayLimit(t, e) {
var n = null == t ? null : "undefined" != typeof Symbol && t[Symbol.iterator] || t["@@iterator"];
if (null == n) return;
var o, i, r = [], a = !0, s = !1;
try {
for (n = n.call(t); !(a = (o = n.next()).done) && (r.push(o.value), !e || r.length !== e); a = !0) ;
} catch (t) {
s = !0, i = t
} finally {
try {
a || null == n.return || n.return()
} finally {
if (s) throw i
}
}
return r
}(t, e) || _unsupportedIterableToArray(t, e) || function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")
}()
}
function _unsupportedIterableToArray(t, e) {
if (t) {
if ("string" == typeof t) return _arrayLikeToArray(t, e);
var n = Object.prototype.toString.call(t).slice(8, -1);
return "Object" === n && t.constructor && (n = t.constructor.name), "Map" === n || "Set" === n ? Array.from(t) : "Arguments" === n || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n) ? _arrayLikeToArray(t, e) : void 0
}
}
function _arrayLikeToArray(t, e) {
(null == e || e > t.length) && (e = t.length);
for (var n = 0, o = new Array(e); n < e; n++) o[n] = t[n];
return o
}
function sliderm_defineProperties(t, e) {
for (var n = 0; n < e.length; n++) {
var o = e[n];
o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(t, o.key, o)
}
}
function _classPrivateMethodInitSpec(t, e) {
!function _checkPrivateRedeclaration(t, e) {
if (e.has(t)) throw new TypeError("Cannot initialize the same private elements twice on an object")
}(t, e), e.add(t)
}
function _classPrivateMethodGet(t, e, n) {
if (!e.has(t)) throw new TypeError("attempted to get private field on non-instance");
return n
}
var g = new WeakSet, y = new WeakSet, _ = new WeakSet, b = new WeakSet, P = new WeakSet, w = function () {
function Sliderm(t, n) {
!function sliderm_classCallCheck(t, e) {
if (!(t instanceof e)) throw new TypeError("Cannot call a class as a function")
}(this, Sliderm), _classPrivateMethodInitSpec(this, P), _classPrivateMethodInitSpec(this, b), _classPrivateMethodInitSpec(this, _), _classPrivateMethodInitSpec(this, y), _classPrivateMethodInitSpec(this, g);
var o = getDom(t);
o ? (this.options = Object.assign(v, n), this.event = new f, this.page = new m(this), this.root = o, this.initialized = !1, this.domEvents = [], this.itemCount = 0, this.position = 1, this.modules = {}, this.slider = findDom(this.root, ".".concat(e)), this.items = [], _classPrivateMethodGet(this, g, _initialize2).call(this)) : error('The DOM "'.concat(t, '" is invalid.'))
}
return function sliderm_createClass(t, e, n) {
return e && sliderm_defineProperties(t.prototype, e), n && sliderm_defineProperties(t, n), Object.defineProperty(t, "prototype", {writable: !1}), t
}(Sliderm, [{
key: "adaptEvent", value: function adaptEvent(t) {
var e = new h(t);
return this.domEvents.push(e), e
}
}, {
key: "getPage", value: function getPage() {
return this.page
}
}, {
key: "getRoot", value: function getRoot() {
return this.root
}
}, {
key: "getItemCount", value: function getItemCount() {
return this.itemCount
}
}, {
key: "getGroupCount", value: function getGroupCount() {
return this.groupCount
}
}, {
key: "getItems", value: function getItems() {
return this.items
}
}, {
key: "getPosition", value: function getPosition() {
return this.position
}
}, {
key: "updatePosition", value: function updatePosition(t) {
this.position = t
}
}, {
key: "updateCurrentItems", value: function updateCurrentItems() {
return !this.initialized && (_classPrivateMethodGet(this, _, _updateItems2).call(this), _classPrivateMethodGet(this, b, _updateGroupCount2).call(this), !0)
}
}, {
key: "getOption", value: function getOption(t) {
var e = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : null,
n = void 0 !== this.options[t] ? this.options[t] : e;
if (t.includes(".")) try {
var o = t.split("."), i = _slicedToArray(o, 2), r = i[0], a = i[1];
return this.options["_".concat(r)][a]
} catch (t) {
return e
}
return n
}
}, {
key: "updateOption", value: function updateOption(t, e) {
if (t.includes(".")) try {
var n = _slicedToArray(t.split("."), 2), o = n[0], i = n[1];
this.options["_".concat(o)][i] = e
} catch (t) {
} else this.options[t] = e
}
}, {
key: "slideTo", value: function slideTo(t) {
this.go("slide", t)
}
}, {
key: "go", value: function go(t) {
var e;
if (void 0 !== this.modules[t]) {
for (var n = arguments.length, o = new Array(n > 1 ? n - 1 : 0), i = 1; i < n; i++) o[i - 1] = arguments[i];
(e = this.modules)[t].apply(e, [this, this.slider].concat(o))
} else error("Invalid module name: ".concat(t))
}
}, {
key: "on", value: function on(t, e) {
this.event.on(t, e)
}
}, {
key: "off", value: function off(t, e) {
this.event.off(t, e)
}
}, {
key: "emit", value: function emit(t) {
for (var e, n = arguments.length, o = new Array(n > 1 ? n - 1 : 0), i = 1; i < n; i++) o[i - 1] = arguments[i];
var r = [this].concat(o);
(e = this.event).emit.apply(e, [t].concat(_toConsumableArray(r)))
}
}, {
key: "destory", value: function destory() {
this.event.emit("destory"), this.event.destory(), this.domEvents.forEach((function (t) {
t.destory()
}))
}
}]), Sliderm
}();
function _initialize2() {
var t = this;
this.emit("initialize"), _classPrivateMethodGet(this, _, _updateItems2).call(this), _classPrivateMethodGet(this, b, _updateGroupCount2).call(this), _classPrivateMethodGet(this, P, _beforeMountExtensions2).call(this), _classPrivateMethodGet(this, y, _mountExtensions2).call(this), this.go("init"), this.go("breakpoint"), this.go("loop"), this.go("align"), this.go("touch"), this.go("preview"), this.go("autoplay"), this.items.forEach((function (e, n) {
t.go("columns", e), t.go("spacing", e), t.go("grouping", e, n), t.go("clone", e, n)
})), this.slideTo(1), this.initialized = !0, this.emit("initialized")
}
function _mountExtensions2() {
for (var t = 0; t < p.length; t += 1) "function" == typeof p[t] && (this.modules[p[t].name] = p[t]);
for (var e = 0; e < d.length; e += 1) "function" == typeof d[e] && this.getOption(d[e].name) && d[e](this)
}
function _updateItems2() {
this.items = Array.from(findDom(this.root, ".".concat(e)).children), this.itemCount = this.items.length
}
function _updateGroupCount2() {
var t = this.getOption("columns");
this.groupCount = Math.ceil(this.itemCount / t)
}
function _beforeMountExtensions2() {
for (var t = 0; t < this.options.extensions.length; t += 1) {
var e = this.options.extensions[t].name;
if ("" !== e) {
var n = this.options.extensions[t];
void 0 === this.options[e] ? p.push(n) : d.push(n)
}
}
}
window.Sliderm = w
})()
})();

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
assets/images/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
assets/images/ye.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

770
assets/script.js Normal file
View File

@@ -0,0 +1,770 @@
let UserInfo = {};
let PageIntervals = [];
let config =
{
articleRefresh: 300000,
messageFadeIn: 300,
messageDisappear: 200
};
function isLoggedIn() {
"use strict";
return UserInfo.Email && UserInfo.Email.length > 0;
}
async function setElementClasses(element, newClasses) {
// Ensure the element exists
if (!element) return;
// Clear all existing classes
element.className = '';
// Add the new classes to the element
element.classList.add(...newClasses);
}
async function handleResponse(data, successMessage, failureMessage) {
"use strict";
const statusMessageContainer = document.getElementById("statusMessageContainer");
const statusMessage = document.createElement("div");
statusMessage.classList.add("status-message");
if (data.Status === 'Success') {
statusMessage.innerText = successMessage;
statusMessage.classList.add("success");
} else {
statusMessage.innerText = failureMessage;
statusMessage.classList.add("failure");
}
statusMessageContainer.appendChild(statusMessage);
// Automatically remove the message after 3 seconds
setTimeout(() => {
statusMessage.style.opacity = "0";
setTimeout(() => {
statusMessage.remove();
}, config.messageFadeIn);
}, config.messageDisappear);
}
async function showDashboardGreeting() {
"use strict";
document.getElementById("welcomeMsg").innerText = `Ahoj, ${UserInfo.FirstName}.`;
}
async function doAction(url, requestData, successMessage, failureMessage, silent) {
"use strict";
const params = new FormData();
for (const key in requestData) {
params.append(key, requestData[key]);
}
const response = await fetch(url, {
method: 'POST',
body: params,
});
if (!response.ok) {
console.error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
if (!silent) {
await handleResponse(data, successMessage, failureMessage);
}
return data;
}
async function doSlicks() {
const elements = document.getElementsByClassName('sliderm');
Array.prototype.forEach.call(elements, function (element) {
// Initialize the Slick carousel on each element
const sliderm = new Sliderm('#' + element.id, {
arrow: true,
pagination: true,
grouping: false,
loop: true,
preview: false,
columns: 1,
duration: 1000,
spacing: 10,
align: 'center',
});
});
}
async function handlePageResponse(data) {
"use strict";
const navbar = document.getElementById("navbar_container");
const pageArea = document.getElementById("page_container");
if (data.Navigation) {
navbar.innerHTML = data.Navigation;
}
if (data.PageTitle) {
document.title = data.PageTitle;
}
if (data.Page) {
pageArea.innerHTML = data.Page;
if (data.PageLocation) {
history.pushState({}, "", data.PageLocation);
}
}
}
async function displayList(data, elementId, deleteFunction) {
"use strict";
const tableContainer = document.getElementById(elementId);
tableContainer.innerHTML = ""; // Clear previous content
const table = document.createElement("table");
table.classList.add("list-table");
const headerRow = table.insertRow(0);
for (const key in data[0]) {
const th = document.createElement("th");
th.appendChild(document.createTextNode(key));
headerRow.appendChild(th);
}
if (typeof deleteFunction === "function") {
const th = document.createElement("th");
let deleteBtn = document.createElement('i');
deleteBtn.classList.add("ri-delete-bin-line");
th.appendChild(deleteBtn);
headerRow.appendChild(th);
}
for (const line of data) {
const dataRow = table.insertRow();
for (const key in line) {
const td = document.createElement("td");
td.appendChild(document.createTextNode(line[key]));
dataRow.appendChild(td);
}
if (typeof deleteFunction === "function") {
const td = document.createElement("td");
const deleteButton = document.createElement('button');
deleteButton.innerHTML = "<i class='ri-delete-bin-line'></i>";
deleteButton.onclick = () => deleteFunction(line.ID);
td.appendChild(deleteButton);
dataRow.appendChild(td);
}
}
tableContainer.appendChild(table);
}
async function doPageAction(requestData) {
"use strict";
const response = await fetch('/page', {
method: 'POST',
body: new URLSearchParams(requestData),
});
if (!response.ok) {
console.error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
await handlePageResponse(data);
return data;
}
async function initAjaxNavigationEvents() {
"use strict";
const allLinks = document.querySelectorAll('.navsite_link, .navpage_link');
const pageLinks = document.querySelectorAll('.navpage_link');
pageLinks.forEach(function (link) {
link.addEventListener('click', function () {
navLinks.classList.remove("active");
});
});
allLinks.forEach(function (link) {
link.addEventListener('click', function (e) {
e.preventDefault();
let site = this.dataset.site;
let page = this.dataset.page;
if (site && page) {
navigateTo(site, page);
}
});
});
const toggleButton = document.getElementById("toggle_button");
const navLinks = document.getElementById("navsite_list");
toggleButton.addEventListener('click', () => {
navLinks.classList.toggle("active");
});
}
async function initAjax() {
"use strict";
await initAjaxNavigationEvents();
await onPageLoad();
}
async function togglearticlecreate() {
"use strict";
let articleContainerElement = document.getElementById("articlecreatecontainer");
articleContainerElement.classList.toggle("hidden");
}
async function togglememecreate() {
"use strict";
let memeContainerElement = document.getElementById("memecreatecontainer");
await getMemeImages();
memeContainerElement.classList.toggle("hidden");
}
async function renderarticles() {
"use strict";
let template = document.querySelector('template[data-template-name="article"]').innerHTML;
let articles = await doAction(
"/newsarticle",
{
action: "getNewsArticles"
},
"Články načítané",
"Nastala chyba pri načítavaní článkov",
true
);
let articleout = "";
for (const article of articles.Articles) {
articleout += template.replace("__TEMPLATE_ARTICLE_TITLE__", article.Title).replace("__TEMPLATE_ARTICLE_AUTHOR__", article.WrittenByName).replace("__TEMPLATE_ARTICLE_DATE__", article.WrittenAt).replace("__TEMPLATE_ARTICLE_BODY__", article.Body);
}
document.getElementById("articleslist").innerHTML = articleout;
}
async function submitarticle() {
"use strict";
let articleTitleElement = document.getElementById("articletitleinput");
let articleBodyElement = document.getElementById("articlebodyinput");
await doAction(
"/newsarticle",
{
action: "addNewsArticle",
title: articleTitleElement.value,
body: articleBodyElement.value
},
"Článok úspešne pridaný",
"Nastala chyba pri pridávaní článku",
false
);
await togglearticlecreate();
}
async function articleInit() {
"use strict";
let articleContainerElement = document.getElementById("articlecreatecontainer");
let articleCreateOpenElement = document.getElementById("articlecreateopen");
articleContainerElement.addEventListener("keyup", function (ev) {
if (ev.key === "Escape") {
togglearticlecreate();
}
});
PageIntervals.push(setInterval(renderarticles, config.articleRefresh));
document.getElementById("articleprivilegeinput").setAttribute("max", UserInfo.Privileges);
if (UserInfo.Privileges < 2) {
articleContainerElement.style.display = "none";
articleCreateOpenElement.style.display = "none";
} else {
articleCreateOpenElement.style.display = "inline-block";
}
}
async function onPageLoad() {
"use strict";
await restoreUserInfo();
let currentSite = localStorage.getItem("currentSite");
let currentPage = localStorage.getItem("currentPage");
for (let interval of PageIntervals) {
clearInterval(interval);
}
if (currentSite === "account" && currentPage === "settings") {
if (document.getElementById("user-settings")) {
await populateUserInfoFields(UserInfo);
}
if (document.getElementById("admin-settings")) {
await listActivationCodes(true);
await listUsers(true);
}
}
if (currentSite === "account" && currentPage === "index" && isLoggedIn()) {
await showDashboardGreeting();
}
if (currentSite === "news" && currentPage === "index") {
await articleInit();
}
if (currentSite === "account" && currentPage === "files") {
await listFiles();
}
if (currentSite === "memes" && currentPage === "index") {
await getMemeImages();
}
await doSlicks();
}
async function navigateTo(site, page) {
"use strict";
const data = {
action: "getPage",
site: site,
page: page,
};
doPageAction(data).then(() => {
localStorage.setItem("currentSite", site);
localStorage.setItem("currentPage", page);
onPageLoad();
});
}
async function softReload() {
"use strict";
let currentSite = localStorage.getItem("currentSite");
let currentPage = localStorage.getItem("currentPage");
await navigateTo(currentSite, currentPage);
umami.track("softReload");
}
async function refreshNavbar() {
"use strict";
const data = {
action: "getNavigation",
};
await doPageAction(data);
umami.track("refreshNavbar");
await initAjaxNavigationEvents();
}
async function logout() {
"use strict";
const data = {
action: "logout",
};
doAction('/account', data, "Logout Successful!", "Logout failed.", false)
.then(async () => {
await refreshNavbar();
await navigateTo(localStorage.getItem("defaultSite"), localStorage.getItem("defaultPage"));
localStorage.clear();
UserInfo = {};
umami.track("logout");
})
.catch((error) => {
// Handle errors if needed
console.error("An error occurred during logout:", error);
});
}
async function login() {
"use strict";
const email = document.getElementById("login_email").value;
const password = document.getElementById("login_password").value;
await doLogin(email, password);
await getUserInfo();
await refreshNavbar();
await softReload();
}
async function doLogin(email, password) {
"use strict";
const data = {
action: "login",
email: email,
password: password,
};
await doAction('/account', data, "Login Successful!", "Login failed. Please check your credentials.", false);
umami.track("login");
}
async function register() {
"use strict";
const firstName = document.getElementById("register_firstName").value;
const lastName = document.getElementById("register_lastName").value;
const email = document.getElementById("register_email").value;
const password = document.getElementById("register_password").value;
const activationToken = document.getElementById("register_activationToken").value;
const data = {
action: "register",
firstname: firstName,
lastname: lastName,
email: email,
password: password,
activation_token: activationToken,
};
await doRegister(data);
}
async function doRegister(requestData) {
"use strict";
await doAction('/account', requestData, "Registration Successful!", "Registration failed.", false);
umami.track("register");
}
//User settings start
async function changePassword() {
"use strict";
const oldPassword = document.getElementById("changeOldPassword").value;
const newPassword = document.getElementById("changeNewPassword").value;
const data = {
action: "change_password",
old_password: oldPassword,
new_password: newPassword,
};
await doChangePassword(data, "Password change Successful!", "Password change failed.");
}
async function doChangePassword(requestData, successMessage, failureMessage) {
"use strict";
await doAction('/account', requestData, successMessage, failureMessage, false);
umami.track("passwordChange");
}
async function updateUserProfile() {
"use strict";
const firstName = document.getElementById("updateFirstName").value;
const lastName = document.getElementById("updateLastName").value;
const nickname = document.getElementById("updateNickname").value;
const minecraftNick = document.getElementById("updateMinecraftNick").value;
const data = {
action: "update_user_profile",
first_name: firstName,
last_name: lastName,
nickname: nickname,
minecraft_nick: minecraftNick,
};
await doAction('/account', data, "Profile update Successful!", "Profile update failed.", false);
umami.track("updateUserProfile");
}
async function updateEmail() {
"use strict";
const newEmail = document.getElementById("updateNewEmail").value;
const data = {
action: "update_user_email",
email: newEmail,
};
await doAction('/account', data, "Email update Successful!", "Email update failed.", false);
umami.track("updateEmail");
}
async function populateUserInfoFields(userData) {
"use strict";
document.getElementById("updateFirstName").value = userData.FirstName || "";
document.getElementById("updateLastName").value = userData.LastName || "";
document.getElementById("updateNickname").value = userData.Nickname || "";
document.getElementById("updateMinecraftNick").value = userData.MinecraftNick || "";
document.getElementById("updateNewEmail").value = userData.Email || "";
}
async function restoreUserInfo() {
"use strict";
for (let i = 0; i < localStorage.length; i++) {
let key = localStorage.key(i);
if (key.startsWith("UserInfo_")) {
let keyClean = key.replace("UserInfo_", "");
UserInfo[keyClean] = localStorage.getItem(key);
}
}
}
async function getUserInfo() {
"use strict";
const data = {
action: "get_user_info",
};
const result = await doAction('/account', data, "User info retrieved Successfully!", "User info retrieval failed.", true);
if (result && result.Status === "Success") {
Object.keys(result.UserInfo).forEach(index => {
let value = result.UserInfo[index];
localStorage.setItem("UserInfo_" + index, value);
UserInfo[index] = value;
});
}
}
//User settings end
//Admin settings start
async function addActivationCodes() {
"use strict";
const count = document.getElementById("activationCodeCount").value;
const data = {
action: "add_activation_codes",
count: count,
};
doAction('/account', data, "Activation codes added Successfully!", "Activation codes addition failed.", false).then((result) => {
displayList(result.ActivationCodes, "codeListTable", deleteActivationCode);
umami.track("addActivationCodes");
});
}
async function listUsers(silent) {
"use strict";
const data = {
action: "list_users",
};
doAction('/account', data, "User list retrieved Successfully!", "User list retrieval failed.", silent).then((result) => {
if (result && result.Status === "Success") {
displayList(result.Users, "userListTable", deleteUser);
}
});
}
async function listActivationCodes(silent) {
"use strict";
const data = {
action: "list_activation_codes",
};
doAction('/account', data, "Activation code list retrieved Successfully!", "Activation code list retrieval failed.", silent).then((result) => {
displayList(result.ActivationCodes, "codeListTable", deleteActivationCode);
});
}
async function deleteUser(userId) {
"use strict";
const data = {
action: "delete_user",
user_id: userId,
};
await doAction('/account', data, "User deleted Successfully!", "User deletion failed.", false);
await listUsers(false);
umami.track("deleteUser");
}
async function deleteActivationCode(activationCode) {
"use strict";
const data = {
action: "delete_activation_code",
activation_code: activationCode,
};
await doAction('/account', data, "Activation code deleted Successfully!", "Activation code deletion failed.", false);
await listActivationCodes(false);
umami.track("deleteActivationCode");
}
//Admin settings end
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initAjax);
} else {
setTimeout(initAjax, 0);
}
async function uploadFile() {
const fileInput = document.getElementById('fileInput');
const fileForm = document.getElementById('uploadForm');
let data = {
action: "uploadFiles"
};
for (let i = 0; i < fileInput.files.length; i++) {
data[`userFile${i}`] = fileInput.files[i];
}
await doAction("/upload", data, "Súbor bol úspešne nahraný", "Nastala chyba pri nahrávaní súboru", false);
fileForm.reset();
await listFiles();
}
async function deleteFile(fileID) {
await doAction("/upload", {
action: "deleteFile",
file_id: fileID
}, "Súbor bol úspešne zmazaný", "Nastala chyba pri mazaní súboru", true);
await listFiles();
}
async function getFileList() {
const resp = await doAction("/upload", {
action: "getAllFiles"
}, "Zoznam súborov bol úspešne stiahnutý", "Nastala chyba pri sťahovaní zoznamu súborov", true);
if (resp.Status === "Success") {
return resp.Files;
} else {
return false;
}
}
async function listFiles() {
const fileList = await getFileList();
if (fileList) {
await displayList(fileList, "filelist", deleteFile);
}
}
async function addMeme() {
let memeTitleElement = document.getElementById("meme_title_input");
let memeTextElement = document.getElementById("meme_text_input");
let memeImageElement = document.getElementById("meme_image_input");
await doAction("/meme", {
action: "addMeme",
meme_title: memeTitleElement.value,
meme_text: memeTextElement.value,
meme_image_id: memeImageElement.value
}, "Meme bol zmazaný", "Nastala chyba pri mazaní meme-u", false);
memeTitleElement.value = "";
memeTextElement.value = "";
memeImageElement.selectedIndex = 0;
await togglememecreate();
}
async function deleteMeme(memeId) {
await doAction("/meme", {
action: "deleteMeme",
meme_id: memeId
}, "Meme bol zmazaný", "Nastala chyba pri mazaní meme-u", false);
await softReload();
}
async function getMemeImages() {
let memeImageSelector = document.getElementById("meme_image_input");
let fileList = await getFileList();
fileList.forEach((item) => {
let option = document.createElement("option");
option.value = item.ID;
let splitPath = item.Path.split("/");
option.text = `${splitPath[splitPath.length - 1]} - ID: (${item.ID}) Autor: [${item.UploadedBy} (${item.UploadedByID})]`;
memeImageSelector.appendChild(option);
});
}
async function reloadMemeVotes(memeID) {
let memeVoteCounterElement = document.getElementById(`meme_votes_counter_${memeID}`);
let memeVoteUpvoteElement = document.getElementById(`meme_votes_upvote_${memeID}`);
let memeVoteDownvoteElement = document.getElementById(`meme_votes_downvote_${memeID}`);
let memeVoteUpvoteButtonElement = document.getElementById(`meme_votes_upvote_button_${memeID}`);
let memeVoteDownvoteButtonElement = document.getElementById(`meme_votes_downvote_button_${memeID}`);
let memeVoteResponse = await doAction('/meme', {
action: "getMemeVotes",
meme_id: memeID
}, "Počet hlasov k meme-u bol stiahnutý", "Nastala chyba pri sťahovaní počtu hlasov k meme-u", true);
let memeVotes = memeVoteResponse.NetVotes;
let userVote = memeVoteResponse.UserVote;
memeVoteCounterElement.innerText = memeVotes;
memeVoteCounterElement.classList.remove("positive", "negative", "neutral");
if (0 < memeVotes) {
memeVoteCounterElement.classList.add("positive");
} else if (0 > memeVotes) {
memeVoteCounterElement.classList.add("negative");
} else {
memeVoteCounterElement.classList.add("neutral");
}
memeVoteUpvoteButtonElement.classList.remove('visual_hover');
memeVoteDownvoteButtonElement.classList.remove('visual_hover');
let memeUpvoteVariant = "line";
let memeDownvoteVariant = "line";
if (0 < userVote) {
memeUpvoteVariant = "fill";
memeVoteUpvoteButtonElement.classList.add('visual_hover');
} else if (0 > userVote) {
memeDownvoteVariant = "fill";
memeVoteDownvoteButtonElement.classList.add('visual_hover');
}
await setElementClasses(memeVoteUpvoteElement, [`ri-arrow-up-circle-${memeUpvoteVariant}`]);
await setElementClasses(memeVoteDownvoteElement, [`ri-arrow-down-circle-${memeDownvoteVariant}`])
}
async function voteMeme(memeID, isUpvote) {
let memeVoteUpvoteElement = document.getElementById(`meme_votes_upvote_${memeID}`);
let memeVoteDownvoteElement = document.getElementById(`meme_votes_downvote_${memeID}`);
let memeVoteDelete = false;
if (isUpvote) {
if (memeVoteUpvoteElement.classList.contains("ri-arrow-up-circle-fill")) {
await deleteVoteMeme(memeID);
memeVoteDelete = true;
}
} else {
if (memeVoteDownvoteElement.classList.contains("ri-arrow-down-circle-fill")) {
await deleteVoteMeme(memeID);
memeVoteDelete = true;
}
}
if (!memeVoteDelete) {
await doAction("/meme", {
action: "voteMeme",
meme_id: memeID,
is_upvote: isUpvote
}, "Meme bol votovaný", "Nastala chyba pri votovaný", true);
}
await reloadMemeVotes(memeID);
}
async function deleteVoteMeme(memeId) {
await doAction("/meme", {
action: "deleteVoteMeme",
meme_id: memeId
}, "Hlas na meme bol zmazaný", "Nastala chyba pri mazaní hlasu na meme", true);
await reloadMemeVotes(memeId);
}
async function surveySubmit() {
const satisfaction = document.querySelector('input[name="satisfaction"]:checked');
const functionality = document.querySelector('input[name="functionality"]:checked');
const content = document.querySelector('input[name="content"]:checked');
const comment = document.querySelector('textarea[name="comment"]');
if (satisfaction && functionality && content && comment.value) {
await doAction("/survey", {
action: "surveySubmit",
satisfaction: satisfaction.value,
functionality: functionality.value,
content: content.value,
comment: comment.value
}, "Zaznamenané",
"Nastala chyba");
satisfaction.checked = false;
functionality.checked = false;
content.checked = false;
comment.value = "";
}
}
async function toggleRegister() {
let loginForm = document.getElementById("sign_in_form");
let registerForm = document.getElementById("sign_up_form");
loginForm.classList.toggle('hidden');
registerForm.classList.toggle('hidden');
}

View File

@@ -1,166 +1,504 @@
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700;800&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap');
:root {
--primary-bg: rgb(27, 21, 41);
--secondary-bg: #1a1a1a;
--third-bg: #383838;
--primary-text: #d2d6e5;
--error: rgb(255, 55, 0);
--primary: #2a9dd6;
--primary-hover: #2489bb;
--error: #ff3700;
--pico-primary: #2a9dd6;
--pico-primary-background: #1b1529;
--pico-primary-hover: #2489bb;
--pico-secondary: #d2d6e5;
--pico-secondary-background: #1a1a1a;
--dimmer: rgba(0, 0, 0, 0.6);
}
body {
background: linear-gradient(127deg, var(--secondary-bg), var(--primary-bg)) no-repeat fixed;
background-size: cover;
height: 100%;
display: grid;
width: 100%;
font-family: 'Poppins', sans-serif;
color: var(--primary-text);
grid-template-areas: "nav nav nav nav"
"main main main main"
"foot foot foot foot";
grid-template-rows: min-content 1fr min-content;
align-items: center;
background: linear-gradient(127deg, var(--pico-secondary-background), var(--pico-primary-background)) no-repeat fixed;
background-size: cover;
flex-direction: column;
font-family: \'Poppins\', sans-serif;
margin: 0;
padding: 0;
min-height: 100vh;
}
nav {
.dashboard {
height: 100%;
text-align: center;
width: 100%;
}
body > nav,
body > footer {
background-color: var(--dimmer);
padding: 1.2rem;
text-align: center;
}
body > nav {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 1.2rem 1rem;
background-color: rgba(0, 0, 0, 0.2);
-webkit-box-shadow: 0 20px 28px 0 rgba(0,0,0,0.2);
-moz-box-shadow: 0 20px 28px 0 rgba(0,0,0,0.2);
box-shadow: 0 20px 28px 0 rgba(0,0,0,0.2);
position: relative;
z-index: 500;
grid-area: nav;
box-shadow: 0 20px 28px 0 var(--dimmer);
margin-bottom: 20px;
}
ul {
display: flex;
flex-direction: row;
gap: 2rem;
list-style: none;
padding-left: 0;
body > footer {
grid-area: foot;
box-shadow: 0 -20px 28px 0 var(--dimmer);
margin-top: 20px;
}
body > main#page_container {
grid-area: main;
height: 100%;
}
body > main#page_container > main {
height: 100%;
}
hr {
border-color: var(--pico-primary);
opacity: 0.5;
width: 30%;
margin: var(--pico-typography-spacing-vertical) auto;
}
li {
list-style: none;
}
li a {
position: relative;
padding-bottom: 0.45rem;
color: var(--primary-text);
text-decoration: none;
transition: all 0.3s ease;
nav li {
margin: unset;
}
li a::after {
content: "";
position: absolute;
height: 4px;
width: 0;
bottom: 0;
left: 0;
background-color: var(--primary);
transition: all 0.3s ease;
border-radius: 15px;
header ul li {
list-style: circle;
width: fit-content;
}
ul {
display: flex;
flex-direction: column;
list-style: none;
padding-left: 0;
}
header {
align-items: center;
text-align: center;
width: 100%;
margin-top: 35px;
margin-bottom: 35px;
}
li a {
padding-bottom: .45rem;
position: relative;
text-decoration: none;
transition: all .3s ease;
color: var(--pico-secondary);
}
li a:hover::after {
margin: 0 auto;
width: 85%;
}
.wrapper-404 {
li.navpage_item {
padding: 0 20px;
}
table>tbody,
table>tbody>tr,
table>tbody>tr>th,
table>tbody>tr>td {
border: 2px solid var(--pico-primary);
border-collapse: collapse !important;
text-align: center;
}
.wrapper-404 h1 {
table>tbody>tr>td>button {
border: unset;
border-radius: unset;
border-collapse: unset;
padding: 0;
margin: 0;
width: 100%;
height: 100%;
}
table {
border-collapse: collapse;
}
ul.navpage_list {
gap: 10px;
border: 4px solid var(--pico-primary-hover) !important;
display: none;
flex-direction: column;
overflow: hidden;
transition: max-height .3s ease, border .325s ease !important;
z-index: 2;
position: fixed;
background: #00000066;
}
.back {
border-radius: 15px;
padding: .35rem .65rem;
text-decoration: none;
transition: all .3s ease;
}
.feature-list {
display: block;
margin: auto;
width: fit-content;
}
.feature-list-ul ul {
margin: 5px auto auto 20px;
}
.navsite_item {
align-items: center;
justify-content: center;
text-align: center;
}
.navsite_item:hover .navpage_list {
border: 4px solid var(--pico-primary-hover) !important;
display: flex !important;
max-height: unset;
width: inherit;
transition: max-height .3s ease, border .325s ease !important;
box-sizing: border-box;
}
.navsite_item:not(:hover) .navpage_list {
border: 0 solid transparent;
max-height: 0;
transition: max-height .3s ease, border .325s ease !important;
width: inherit;
}
#navsite_list {
display: flex;
flex-direction: row;
gap: 3.25rem;
text-align: center;
padding-right: 60px;
}
#toggle_button {
position: absolute;
right: 1.5rem;
display: none;
flex-direction: column;
justify-content: space-between;
width: 2rem;
height: 1.5rem;
}
#statusMessageContainer {
position: fixed;
top: 100px;
right: 20px;
z-index: 510;
display: flex;
flex-direction: column;
}
/*noinspection CssUnusedSymbol*/
.status-message {
background-color: #dff0d8;
/* Success background color */
border: 1px solid #3c763d;
/* Success border color */
color: #3c763d;
/* Success text color */
padding: 15px;
margin-bottom: 10px;
opacity: 1;
transition: opacity 0.5s ease-in-out;
}
/*noinspection CssUnusedSymbol*/
.status-message.failure {
background-color: #f2dede;
/* Failure background color */
border: 1px solid #a94442;
/* Failure border color */
color: #a94442;
/* Failure text color */
}
.wrapper-40x .error {
color: var(--error);
}
.wrapper-40x h1 {
font-size: 10rem;
margin: 0;
}
.wrapper-404 .error {
color: var(--error);
}
.wrapper-404 h3 {
.wrapper-40x h3 {
margin-bottom: 2rem;
}
.error-code {
color: var(--primary);
}
.back {
color: var(--primary-text);
text-decoration: none;
background-color: #2a9dd6;
padding: 0.35rem 0.65rem;
transition: all 0.3s ease;
border-radius: 15px;
}
.back:hover {
transition: all 0.3s ease;
background-color: var(--primary-hover);
}
header {
width: 100%;
align-items: center;
.wrapper-40x {
text-align: center;
}
header h1 {
margin: 0;
padding: 0;
}
header a {
color: var(--primary);
header a,
.error-code {
color: var(--pico-primary);
}
header hr {
border-color: var(--primary);
opacity: 0.5;
width: 30%;
input, textarea {
border: 2px solid var(--pico-primary);
}
.navsite_list{
input{
border-radius: 25px;
}
textarea{
border-radius: 10px;
}
.form-container input {
border-radius: 50px;
background: none;
border: 2px solid var(--pico-primary);
width: 175px;
}
span#ye-span:hover + body{
background: url('/assets/images/ye.jpg') repeat !important;
background-size: 20% !important;
}
#articlecreate, #memecreate {
border: 5px solid var(--pico-primary);
z-index: 5;
margin: auto;
padding: 40px;
background-color: var(--pico-primary-background);
border-radius: 50px;
}
#articlecreate > * {
margin: 10px 0;
}
#articlecreateopen {
display: none;
}
#articlecreatecontainer, #memecreatecontainer{
display: flex;
align-items: center;
justify-content: center;
position: fixed;
top: 12vh;
left: 0;
width: 100vw;
height: 88vh;
z-index: 4;
backdrop-filter: blur(2px);
}
.hidden {
display: none !important;
}
div#articleslist > article > div.articleinfo > *{
width: fit-content;
}
/*noinspection CssUnusedSymbol*/
div#articleslist > article > div.articleinfo{
display: flex;
flex-direction: row;
}
.navpage_list{
display: none;
flex-direction: column;
div#articleslist>article{
border: 4px solid var(--pico-primary);
}
.navsite_item:hover .navpage_list{
display: flex !important;
.form-content {
display: flex;
flex-direction: row;
}
.navpage_list{
background-color: var(--third-bg);
margin-top: 10px;
.form-container {
display: flex;
flex-direction: column;
overflow: hidden;
max-height: 0;
border: 0;
transition: max-height 0.3s ease;
}
.navsite_item .navpage_list {
transition-delay: 0.3s; /* Delay the border fade-out transition */
}
/* Modify this style for the hover state of the navsite_item */
.navsite_item:hover .navpage_list {
max-height: 200px; /* Adjust the value based on your content height */
border: 4px solid var(--primary-hover); /* Adjust border style as needed */
transition-delay: 0s; /* Reset delay on hover */
.meme_image {
max-width: 500px;
max-height: 300px;
width: auto;
height: auto;
}
li.navpage_item{
padding-left: 20px;
padding-right: 20px;
.meme_link {
width: fit-content;
height: fit-content;
}
ul.navpage_list{
gap: 10px;
.meme_info, .meme_topbar {
display: flex;
flex-direction: row;
height: fit-content;
width: 100%;
justify-content: right;
}
.meme, .meme_body {
display: flex;
flex-direction: column;
padding: 0;
margin: 0;
}
.positive {
color: #008000;
}
.negative {
color: #ff0000;
}
.neutral {
color: var(--pico-color);
}
.visual_hover {
--pico-background-color: var(--pico-primary-hover-background);
--pico-border-color: var(--pico-primary-hover-border);
--pico-box-shadow: var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0));
--pico-color: var(--pico-primary-inverse);
}
.visual_hover.meme_upvote {
--pico-background-color: #008000;
--pico-border-color: unset;
}
.visual_hover.meme_downvote {
--pico-background-color: #ff0000;
--pico-border-color: unset;
}
#meme_gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(600px, 1fr));
grid-auto-rows: 1fr;
gap: 20px
}
@media (max-width: 1050px) {
table .rozvrh {
overflow: auto;
}
.navsite_item .navpage_list {
max-height: unset !important;
}
div#articleslist {
width: 100vw !important;
left: 0 !important;
}
#toggle_button {
display: flex;
}
#navsite_list {
display: none;
position: fixed;
flex-direction: column;
width: 100%;
text-align: center;
padding: 0 8px;
left: 0;
right: 0;
margin: 0;
gap: 1rem;
}
#navsite_list li {
text-align: center;
padding: 0;
}
.navsite_item {
width: inherit;
}
ul.navpage_list {
border: 4px solid var(--pico-primary-hover) !important;
display: flex !important;
max-height: 200px !important;
width: inherit;
box-sizing: content-box;
transition-delay: .1s;
position: unset !important;
}
.navsite_item:not(:hover) .navpage_list {
transition-delay: .1s;
width: inherit;
}
/*noinspection CssUnusedSymbol*/
#navsite_list.active {
display: flex;
-moz-box-shadow: 0 20px 28px 0 var(--dimmer);
-webkit-box-shadow: 0 20px 28px 0 var(--dimmer);
background-color: var(--pico-primary-background);
box-shadow: 0 20px 28px 0 var(--dimmer);
top: 100px;
text-align: center;
left: 0;
}
nav {
flex-direction: column;
align-items: center;
background-color: var(--dimmer);
}
.form-content {
flex-direction: column;
}
.meme_image {
max-width: 200px;
max-height: 200px;
}
.navsite_link {
width: 100%;
}
}

45
endpoints/account.php Normal file
View File

@@ -0,0 +1,45 @@
<?php
require_once "lib/account.php";
function endpoint($endpoint_data): array
{
return match ($endpoint_data["action"]) {
//not logged in start
"login" => doLogin($endpoint_data["email"], $endpoint_data["password"]),
"register" => doRegister(
$endpoint_data["firstname"],
$endpoint_data["lastname"],
$endpoint_data["email"],
$endpoint_data["password"],
$endpoint_data["activation_token"]
),
//not logged in end
//logged in start
"logout" => doLogout(),
"change_password" => changePassword(
$endpoint_data["old_password"],
$endpoint_data["new_password"]
),
"update_user_profile" => updateUserProfile(
$endpoint_data["first_name"],
$endpoint_data["last_name"],
$endpoint_data["nickname"],
$endpoint_data["minecraft_nick"]
),
"update_user_email" => updateUserEmail(
$endpoint_data["email"]
),
"get_user_info" => getUserInfo(),
//logged in end
//admin start
"add_activation_codes" => addActivationCodes($endpoint_data["count"]),
"list_users" => listUsers(),
"list_activation_codes" => listActivationCodes(),
"delete_user" => deleteUser($endpoint_data["user_id"]),
"delete_activation_code" => deleteActivationCode($endpoint_data["activation_code"]),
//admin end
default => ["Status" => "Fail", "message" => "Invalid action"],
};
}

17
endpoints/meme.php Normal file
View File

@@ -0,0 +1,17 @@
<?php
require_once "lib/meme.php";
function endpoint($endpoint_data): array
{
return match ($endpoint_data["action"]) {
"addMeme" => addMeme($endpoint_data['meme_title'], $endpoint_data['meme_text'], $endpoint_data['meme_image_id']),
"getMemes" => getMemeGallery($endpoint_data['offset'], $endpoint_data['meme_author'], $endpoint_data['meme_id'], $endpoint_data['meme_keyword']),
"deleteMeme" => deleteMeme($endpoint_data['meme_id']),
"getMemeVotes" => getMemeVotes($endpoint_data['meme_id']),
"deleteVoteMeme" => deleteVoteMeme($endpoint_data['meme_id']),
"voteMeme" => voteMeme($endpoint_data['meme_id'], $endpoint_data['is_upvote']),
default => ["Status" => "Fail", "Message" => "Invalid action"],
};
}

22
endpoints/newsarticle.php Normal file
View File

@@ -0,0 +1,22 @@
<?php
require_once "lib/newsarticle.php";
function endpoint($endpoint_data): array
{
return match ($endpoint_data["action"]) {
"getNewsArticles" => getNewsArticles(),
"addNewsArticle" => addNewsArticle(
$endpoint_data["title"],
$endpoint_data["body"]
),
"addNewsComment" => addNewsComment(
$endpoint_data["user_id"],
$endpoint_data['news_article_id'],
$endpoint_data["comment_text"],
$endpoint_data["parent_id"]
),
default => ["Status" => "Fail", "message" => "Invalid action"],
};
}

13
endpoints/page.php Normal file
View File

@@ -0,0 +1,13 @@
<?php
require_once "lib/page.php";
require_once "lib/navigation.php";
function endpoint($endpoint_data): array
{
return match ($endpoint_data["action"]) {
"getNavigation" => getNavigationEndpoint(),
"getPage" => getPageEndpoint($endpoint_data["page"], $endpoint_data["site"]),
default => ["Status" => "Fail", "message" => "Invalid action"],
};
}

11
endpoints/survey.php Normal file
View File

@@ -0,0 +1,11 @@
<?php
require_once "lib/survey.php";
function endpoint($endpoint_data): array
{
return match ($endpoint_data["action"]) {
"surveySubmit" => submitSurvey($endpoint_data["satisfaction"], $endpoint_data["functionality"], $endpoint_data["content"], $endpoint_data["comment"]),
default => ["Status" => "Fail", "message" => "Invalid action"],
};
}

18
endpoints/upload.php Normal file
View File

@@ -0,0 +1,18 @@
<?php
require_once "lib/upload.php";
function endpoint($endpoint_data): array
{
return match ($endpoint_data["action"]) {
"getMyFiles" => listFiles(),
"getAllFiles" => listFiles(false),
"uploadFiles" => parseIncomingFiles(),
"deleteFile" => deleteFile($endpoint_data['file_id']),
"addToGroup" => addToGroup($endpoint_data['group_id'], $endpoint_data['file_id']),
"myFileExists" => fileExists($endpoint_data['file_id']),
"FileExists" => fileExists($endpoint_data['file_id'], false),
default => ["Status" => "Fail", "message" => "Invalid action"],
};
}

View File

@@ -1,22 +1,40 @@
<?php
/** @noinspection PhpIncludeInspection */
require_once "secrets/config.php";
require_once "lib/navpages.php";
require_once "lib/routing.php";
require_once "lib/config.php";
// Include essential configuration and function libraries.
require_once 'secrets/config.php'; // Load sensitive configuration such as database credentials.
require_once 'lib/config.php'; // Load general site configuration settings.
require_once 'lib/navigation.php'; // Include functions related to navigation generation.
require_once 'lib/router.php'; // Include routing functionality to manage URL routing.
require_once 'lib/page.php'; // Functions related to page content generation and management.
require_once 'lib/endpoint.php'; // Functions for handling API endpoints.
require_once 'lib/account.php'; // Include user account management functionality.
$routerConfig = array();
$routerRequest = array();
// Load configuration for the router from the configuration files.
$routerConfig = loadRouterConfig();
loadRouterConfig();
if(initRouter()) {
/** @noinspection PhpArrayIsAlwaysEmptyInspection */
session_set_cookie_params(0, '/', "." . $routerRequest["domain"] . "." . $routerRequest["tld"], true, true);
session_start();
/** @noinspection PhpArrayIsAlwaysEmptyInspection */
echo getPage($routerRequest["page_name"]);
}
else{
exit();
// Initialize the router to parse the request URI and determine the requested site/page.
$routerRequest = initRouter();
// Start or resume a session to manage user sessions across requests.
session_start();
// Set default session data if the user is not logged in.
if (!isLoggedIn()) {
setDefaultSessionData();
}
// Handle requests for the sitemap.
if ($routerRequest["site_name"] == "sitemap.xml") {
require "lib/sitemap.php"; // Include sitemap generation functions.
echo generateSitemap(); // Generate and output the sitemap XML.
exit(); // Stop script execution after sitemap generation.
}
// Handle API type requests by fetching and outputting the endpoint response.
if ($routerRequest["type"] == "api") {
echo getEndpoint($routerRequest["site_name"]);
}
// Handle page type requests by fetching and rendering the page content.
elseif ($routerRequest["type"] == "page") {
echo getPage($routerRequest["site_name"], $routerRequest["page_name"]);
}

View File

@@ -1,88 +1,582 @@
<?php
function isLoggedIn(){
return $_SESSION["ID"] > 0 && !empty($_SESSION["email"]);
use Random\RandomException;
/**
* Checks if the current session represents a logged-in user.
*
* @global array $routerConfig Global configuration array containing permission thresholds.
* @return bool Returns true if the user is logged in and meets the minimum privilege level; otherwise, false.
*/
function isLoggedIn(): bool
{
global $routerConfig;
return $_SESSION["ID"] > 0 && !empty($_SESSION["email"]) && $_SESSION["privilege_level"] >= $routerConfig["permissions"]["logged_in_default"];
}
/**
* Checks if the logged-in user is verified.
*
* @global array $routerConfig Global configuration array containing permission thresholds.
* @return bool Returns true if the user is logged in and verified; otherwise, false.
*/
function isVerified(): bool
{
global $routerConfig;
return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["permissions"]["verified"];
}
function doLogin(){
/**
* Checks if the logged-in user is trustworthy.
*
* @global array $routerConfig Global configuration array containing permission thresholds.
* @return bool Returns true if the user is logged in and considered trustworthy; otherwise, false.
*/
function isTrustWorthy(): bool
{
global $routerConfig;
return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["permissions"]["trustworthy"];
}
/**
* Checks if the logged-in user is a moderator.
*
* @global array $routerConfig Global configuration array containing permission thresholds.
* @return bool Returns true if the user is logged in and a moderator; otherwise, false.
*/
function isModerator(): bool
{
global $routerConfig;
return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["permissions"]["moderator"];
}
/**
* Checks if the logged-in user is a user admin.
*
* @global array $routerConfig Global configuration array containing permission thresholds.
* @return bool Returns true if the user is logged in and a user admin; otherwise, false.
*/
function isUserAdmin(): bool
{
global $routerConfig;
return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["permissions"]["user_admin"];
}
/**
* Checks if the logged-in user is an admin.
*
* @global array $routerConfig Global configuration array containing permission thresholds.
* @return bool Returns true if the user is logged in and an admin; otherwise, false.
*/
function isAdmin(): bool
{
global $routerConfig;
return isLoggedIn() && $_SESSION["privilege_level"] >= $routerConfig["permissions"]["admin"];
}
/**
* Generates a secure token for account activation or other purposes using cryptographic methods.
*
* @return string|null Returns a hexadecimal token or null in case of an error.
*/
function generateActivationToken(): ?string
{
try {
return bin2hex(random_bytes(16));
} catch (RandomException) {
return null;
}
}
/**
* Checks if an email address is available for registration.
*
* @param string $email The email address to check.
* @return bool Returns true if the email is not already registered; otherwise, false.
*@global mysqli $mysqli Global mysqli object for database access.
*/
function isEmailAvailable(string $email): bool
{
global $mysqli;
if(!empty($_POST["email"]) && !empty($_POST["password"])){
$email = $_POST["email"];
$pass = $_POST["password"];
/* prepare statement */
$stmt = $mysqli->prepare("SELECT ID, FirstName, LastName, Nickname, PasswordHash, MinecraftNick, isAdmin FROM Users WHERE EMAIL = ? AND isActive = 1");
$stmt = $mysqli->prepare("SELECT COUNT(*) FROM Users WHERE Email = ?");
$stmt->bind_param("s", $email);
$stmt->execute();
$count = -1;
$stmt->bind_result($count);
$stmt->fetch();
$stmt->close();
return $count === 0;
}
/**
* Sets default session data typically used for a logged-out user(includes users that have just visited the page).
*
* @global array $routerConfig Global configuration array used for setting initial privilege levels.
* @return void
*/
function setDefaultSessionData(): void
{
global $routerConfig;
$_SESSION["ID"] = 0;
$_SESSION["first_name"] = "";
$_SESSION["last_name"] = "";
$_SESSION["nickname"] = "";
$_SESSION["email"] = "";
$_SESSION["minecraft_nickname"] = "";
$_SESSION["privilege_level"] = $routerConfig["permissions"]["logged_out"];
}
/**
* Verifies if the provided password matches the stored hash for the user.
*
* @param int $userID The user ID whose password is to be verified.
* @param string $password The password to verify.
* @return bool Returns true if the password matches the stored hash; otherwise, false.
*@global mysqli $mysqli Global mysqli object for database access.
*/
function verifyPassword(int $userID, string $password): bool
{
global $mysqli;
$stmt = $mysqli->prepare("SELECT PasswordHash FROM Users WHERE ID = ?");
$stmt->bind_param("i", $userID);
$stmt->execute();
$password_hash = "";
$stmt->bind_result($password_hash);
$stmt->fetch();
$stmt->close();
return !empty($password_hash) && !empty($password) && password_verify($password, $password_hash);
}
/**
* Updates session data from the database for the logged-in user.
*
* @global mysqli $mysqli Global mysqli object for database access.
* @return void
*/
function UpdateSession(): void
{
global $mysqli;
$stmt = $mysqli->prepare("SELECT FirstName, LastName, Nickname, Email, MinecraftNick, PrivilegeLevel, LastLoginAt, LoginCount, ClassID, FavoriteColor FROM Users WHERE ID = ? AND isActivated = 1");
$stmt->bind_param("i", $_SESSION["ID"]);
$stmt->execute();
$first_name = "";
$last_name = "";
$nickname = "";
$email = "";
$minecraft_nickname = "";
$privilege_level = 0;
$class_id = 0;
$favorite_color = 0;
$lastLoginAt = null;
$loginCount = 0;
$stmt->bind_result($first_name, $last_name, $nickname, $email, $minecraft_nickname, $privilege_level, $lastLoginAt, $loginCount, $class_id, $favorite_color);
$stmt->fetch();
$stmt->close();
$_SESSION["first_name"] = $first_name;
$_SESSION["last_name"] = $last_name;
$_SESSION["nickname"] = $nickname;
$_SESSION["email"] = $email;
$_SESSION["minecraft_nickname"] = $minecraft_nickname;
$_SESSION["privilege_level"] = $privilege_level;
$_SESSION["lastLoginAt"] = $lastLoginAt;
$_SESSION["loginCount"] = $loginCount;
$_SESSION["class_id"] = $class_id;
$_SESSION["favorite_color"] = $favorite_color;
}
/**
* Attempts to log in a user with the given credentials.
*
* @param string $email The user's email address.
* @param string $password The user's password.
* @global mysqli $mysqli Global database connection object.
* @return array An array containing the status of the login attempt ('Success' or 'Fail').
*/
function doLogin(string $email, string $password): array
{
global $mysqli;
$found = false;
if (!empty($email) && !empty($password)) {
$stmt = $mysqli->prepare("SELECT ID, PasswordHash FROM Users WHERE Email = ? AND isActivated = 1");
$stmt->bind_param("s", $email);
$stmt->execute();
$idcko = 0;
$fname = "";
$lname = "";
$nickname = "";
$pwdhash = "";
$mcnick = "";
/* bind variables to prepared statement */
$stmt->bind_result($idcko, $fname, $lname, $nickname, $pwdhash, $mcnick, false);
/* fetch values */
$found = false;
if($stmt->num_rows() > 0){
$stmt->fetch();
if (password_verify($pass, $pwdhash)){
$_SESSION["ID"] = $idcko;
$_SESSION["first_name"] = $fname;
$_SESSION["last_name"] = $lname;
$_SESSION["nickname"] = $nickname;
$_SESSION["email"] = $email;
$_SESSION["mcnick"] = $mcnick;
$_SESSION["isadmin"] = false;
$found = true;
}
$uid = 0;
$password_hash = "";
$stmt->bind_result($uid, $password_hash);
$stmt->fetch();
$stmt->close();
if (password_verify($password, $password_hash)) {
$found = true;
$_SESSION["ID"] = $uid;
UpdateSession();
// Update LastLoginAt and LoginCount
$updateLoginStmt = $mysqli->prepare("UPDATE Users SET LastLoginAt = NOW(), LoginCount = LoginCount + 1 WHERE ID = ?");
$updateLoginStmt->bind_param("i", $uid);
$updateLoginStmt->execute();
$updateLoginStmt->close();
}
}
return $found ? ["Status" => "Success"] : ["Status" => "Fail"];
}
/**
* Logs out the current user by resetting session data.
* Fails when the user wasn't logged in
*
* @return array An array with the logout status ('Success' if logged out, 'Fail' otherwise).
*/
function doLogout(): array
{
if(isLoggedIn()){
setDefaultSessionData();
return ["Status" => "Success"];
} else {
return ["Status" => "Fail"];
}
}
/**
* Registers a new user with provided personal details and activation token.
*
* @param string $firstname The user's first name.
* @param string $lastname The user's last name.
* @param string $email The user's email.
* @param string $password The user's password.
* @param string $activation_token The activation token to verify the registration.
* @global mysqli $mysqli Global database connection object.
* @global array $routerConfig Global configuration settings.
* @return array An array with the registration status ('Success' or 'Fail').
*/
function doRegister(string $firstname, string $lastname, string $email, string $password, string $activation_token): array
{
global $mysqli, $routerConfig;
$status = ["Status" => "Fail"];
if (!empty($activation_token) && !empty($email) && !empty($password) && !empty($firstname) && !empty($lastname) && isEmailAvailable($email)) {
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
$stmt = $mysqli->prepare("UPDATE Users SET FirstName=?, LastName=?, Email=?, PasswordHash=?, PrivilegeLevel=?, isActivated=1, ActivationToken='', RegisteredAt=NOW() WHERE ActivationToken = ?");
$privilege_level = $routerConfig["permissions"]["logged_in_default"];
/** @noinspection SpellCheckingInspection */
$stmt->bind_param("ssssis", $firstname, $lastname, $email, $passwordHash, $privilege_level, $activation_token);
$stmt->execute();
if ($stmt->affected_rows > 0) {
$status["Status"] = "Success";
}
$stmt->close();
}
return $status;
}
/**
* Changes the user's password after verifying the old password.
*
* @param string $oldPassword The current password for verification.
* @param string $newPassword The new password to be set.
* @return array An array indicating whether the password change was successful ('Success' or 'Fail').
*@global mysqli $mysqli Global database connection object.
*/
function changePassword(string $oldPassword, string $newPassword): array
{
global $mysqli;
$status = ["Status" => "Fail"];
$userID = $_SESSION["ID"];
if(!empty($oldPassword) && !empty($newPassword) && isLoggedIn() && verifyPassword($userID, $oldPassword)){
$passwordHash = password_hash($newPassword, PASSWORD_DEFAULT);
$stmt = $mysqli->prepare("UPDATE Users SET PasswordHash = ? WHERE ID = ?");
$stmt->bind_param("si", $passwordHash, $userID);
$stmt->execute();
if ($stmt->affected_rows > 0) {
$status["Status"] = "Success";
}
$stmt->close();
if($found){
$status = ["status" => "success"];
}
else{
$status = ["status" => "fail"];
}
echo json_encode($status);
}
return $status;
}
function doLogout(){
if(isLoggedIn()){
session_destroy();
$status = ["status" => "success"];
/**
* Updates user profile information in the database.
*
* @param string $firstName The new first name.
* @param string $lastName The new last name.
* @param string $nickname The new nickname.
* @param string $minecraft_nickname The new Minecraft nickname.
* @return array Status of the profile update ('Success' or 'Fail').
*@global mysqli $mysqli Global database connection object.
*/
function updateUserProfile(string $firstName, string $lastName, string $nickname, string $minecraft_nickname): array
{
global $mysqli;
$status = ["Status" => "Fail"];
if (isLoggedIn() && !empty($firstName) && !empty($lastName) && !empty($nickname) && !empty($minecraft_nickname)) {
$stmt = $mysqli->prepare("UPDATE Users SET FirstName = ?, LastName = ?, Nickname = ?, MinecraftNick = ? WHERE ID = ?");
/** @noinspection SpellCheckingInspection */
$stmt->bind_param("ssssi", $firstName, $lastName, $nickname, $minecraft_nickname, $_SESSION["ID"]);
$stmt->execute();
if ($stmt->affected_rows > 0) {
$status["Status"] = "Success";
}
else {
$status["Status"] = "$firstName $lastName $nickname $minecraft_nickname";
}
$stmt->close();
}
else{
$status = ["status" => "fail"];
}
echo json_encode($status);
return $status;
}
function doRegister(){
$status = ["status" => "fail"];
if (!empty($_POST["activationtoken"])){
global $mysqli;
/**
* Updates the email address of the logged-in user after validation.
*
* @param string $email The new email address to update.
* @return array Status of the email update ('Success' or 'Fail').
*@global mysqli $mysqli Global database connection object.
*/
function updateUserEmail(string $email): array
{
global $mysqli;
$status = ["Status" => "Fail"];
/** @noinspection SpellCheckingInspection */
$validmail = false;
$firstName = $_POST["firstname"];
$lastName = $_POST["lastname"];
$nickname = $_POST["nickname"];
$email = $_POST["email"];
$password = $_POST["password"];
$minecraftNick = $_POST["minecraftnick"];
$activationToken = $_POST["activationtoken"];
if (!empty($firstName) && !empty($lastName) && !empty($nickname) && !empty($email) && !empty($password)) {
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
if (isLoggedIn() && !empty($email)) {
$userID = $_SESSION["ID"];
$stmt = $mysqli->prepare("UPDATE Users SET FirstName = ?, LastName = ?, Nickname = ?, Email = ?, PasswordHash = ?, MinecraftNick = ?, isAdmin = 0, isActivated = 1 WHERE isActivated = 0 AND ActivationToken = ?");
$stmt->bind_param("ssssss", $firstName, $lastName, $nickname, $email, $passwordHash, $minecraftNick, $activationToken);
$stmt->execute();
if ($stmt->affected_rows > 0) {
$status["status"] = "success";
$stmt_email_check = $mysqli->prepare("SELECT Email FROM Users WHERE ID = ?");
$stmt_email_check->bind_param("i", $userID);
$old_email = "";
$stmt_email_check->bind_result($old_email);
$stmt_email_check->execute();
$stmt_email_check->fetch();
$stmt_email_check->close();
if ($email != $old_email) {
if (isEmailAvailable($email)) {
/** @noinspection SpellCheckingInspection */
$validmail = true;
}
} else {
/** @noinspection SpellCheckingInspection */
$validmail = true;
}
if ($validmail) {
$stmt = $mysqli->prepare("UPDATE Users SET Email = ? WHERE ID = ?");
$stmt->bind_param("si", $email, $userID);
$stmt->execute();
if ($stmt->affected_rows > 0) {
$status["Status"] = "Success";
}
$stmt->close();
}
}
echo json_encode($status);
return $status;
}
/**
* Retrieves and updates the current session with user information from the database.
*
* @return array Contains user information and status if the user is logged in.
*/
function getUserInfo(): array
{
$output = ["Status" => "Fail"];
if(isLoggedIn()) {
global $mysqli;
$userID = $_SESSION["ID"];
$stmt = $mysqli->prepare("SELECT FirstName, LastName, Nickname, Email, MinecraftNick FROM Users WHERE ID = ?");
$stmt->bind_param("i", $userID);
$stmt->execute();
$firstName = "";
$lastName = "";
$nickname = "";
$email = "";
$minecraft_nickname = "";
$stmt->bind_result($firstName, $lastName, $nickname, $email, $minecraft_nickname);
$stmt->fetch();
$stmt->close();
UpdateSession();
$output["Status"] = "Success";
$output["UserInfo"] = [
"ID" => $userID,
"FirstName" => $firstName,
"LastName" => $lastName,
"Nickname" => $nickname,
"Email" => $email,
"MinecraftNick" => $minecraft_nickname
];
}
return $output;
}
/**
* Generates a specified number of activation codes for user registration and adds them to the database.
*
* @param int $count Number of activation codes to generate.
* @return array An array containing the generated codes and status ('Success' or 'Fail').
*@global mysqli $mysqli Global database connection object.
*/
function addActivationCodes(int $count): array
{
global $mysqli;
$activationCodes = [];
$output = ["Status" => "Fail"]; // Default Status is "Fail"
if (is_numeric($count) && $count > 0 && isUserAdmin() && isLoggedIn()) {
$stmt = $mysqli->prepare("INSERT INTO Users (ActivationToken, CreatedAt, CreatedBy) VALUES (?, NOW(), ?)");
for ($i = 0; $i < $count; $i++) {
$activationCode = generateActivationToken();
$stmt->bind_param("si", $activationCode, $_SESSION["ID"]);
$stmt->execute();
if ($stmt->affected_rows > 0) {
$activationCodes[] = [
"Code" => $activationCode,
"CreatedAt" => date("Y-m-d H:i:s"),
"CreatedBy" => $_SESSION["ID"]
];
$output["Status"] = "Success";
$output["ActivationCodes"] = $activationCodes;
}
}
$stmt->close();
}
return $output;
}
/**
* Lists all registered users, available only to user admins.
*
* @global mysqli $mysqli Global database connection object.
* @return array An array containing user data and status.
*/
function listUsers(): array
{
global $mysqli;
$output = ["Status" => "Fail"]; // Default Status is "Fail"
if (isUserAdmin()) {
$users = [];
$result = $mysqli->query("SELECT ID, FirstName, LastName, Nickname, Email, MinecraftNick, PrivilegeLevel, CreatedAt, RegisteredAt, LastLoginAt, LoginCount, CreatedBy FROM Users WHERE isActivated = 1");
// Check if the query executed Successfully
if ($result) {
while ($row = $result->fetch_assoc()) {
$users[] = $row;
}
$output["Status"] = "Success";
$output["Users"] = $users;
}
}
return $output;
}
/**
* Lists activation codes available for assigning to new users, available only for user admins.
*
* @global mysqli $mysqli Global database connection object.
* @return array An array containing activation codes and status.
*/
function listActivationCodes(): array
{
global $mysqli;
$output = ["Status" => "Fail"]; // Default Status is "Fail"
if (isUserAdmin()) {
$activationCodes = [];
// Use placeholders in the query
$query = "SELECT ActivationToken, CreatedAt, CreatedBy FROM Users WHERE isActivated = 0";
$stmt = $mysqli->prepare($query);
if ($stmt) {
// Bind the result variables
$activationToken = "";
$createdAt = "";
$createdBy = "";
$stmt->bind_result($activationToken, $createdAt, $createdBy);
// Execute the prepared statement
$stmt->execute();
// Fetch the results into the bound variables
while ($stmt->fetch()) {
$activationCodes[] = [
'ActivationToken' => $activationToken,
'CreatedAt' => $createdAt,
'CreatedBy' => $createdBy
];
}
// Check if any results were fetched
if (!empty($activationCodes)) {
$output["Status"] = "Success";
$output["ActivationCodes"] = $activationCodes;
}
// Close the statement
$stmt->close();
}
}
return $output;
}
/**
* Deletes a user by their ID, available only to user admins.
*
* @param int $userID The ID of the user to delete.
* @return array Status of the delete operation ('Success' or 'Fail').
*@global mysqli $mysqli Global database connection object.
*/
function deleteUser(int $userID): array
{
global $mysqli;
$status = ["Status" => "Fail"];
if (!empty($userID) && isUserAdmin()) {
$stmt = $mysqli->prepare("DELETE FROM Users WHERE ID = ?");
$stmt->bind_param("i", $userID);
$stmt->execute();
if ($stmt->affected_rows > 0) {
$status["Status"] = "Success";
}
$stmt->close();
}
return $status;
}
/**
* Deletes an activation code, available only to user admins.
*
* @param string $activationCode The activation code to delete.
* @return array Status of the delete operation ('Success' or 'Fail').
*@global mysqli $mysqli Global database connection object.
*/
function deleteActivationCode(string $activationCode): array
{
global $mysqli;
$status = ["Status" => "Fail"];
if (!empty($activationCode) && isUserAdmin()) {
$stmt = $mysqli->prepare("DELETE FROM Users WHERE ActivationToken = ?");
$stmt->bind_param("s", $activationCode);
$stmt->execute();
if ($stmt->affected_rows > 0) {
$status["Status"] = "Success";
}
$stmt->close();
}
return $status;
}

View File

@@ -1,14 +1,68 @@
<?php
function loadRouterConfig(){
global $routerConfig;
/**
* Loads and returns the configuration settings for the router.
*
* This configuration includes various paths, default settings, security levels, SEO settings,
* and other parameters essential for the operation of the router and the website's page management.
* The configuration array is structured to provide easy access to paths, protocols, permissions,
* and other critical settings that define how the router handles requests and serves content.
*
* @return array Returns an associative array containing all router configuration settings, such as:
* - 'inlining': Boolean value determining if CSS/JS should be inlined.
* - 'domain': The primary domain name of the website.
* - 'tld': Top-level domain for the website.
* - 'default_page': Default page to load if no specific page is requested.
* - 'default_site': Default site to load if no specific site is requested.
* - 'template_dir': Directory path where templates are stored.
* - 'endpoint_dir': Directory path for endpoint scripts.
* - 'page_dir': Directory path where site pages are stored.
* - 'protocol': Protocol to be used (e.g., 'https://').
* - 'site_prefix': Prefix for the site title.
* - 'permissions': Associative array of user permissions by role.
* - 'page': Default settings for pages including secret status and permissions.
* - 'newsarticle': Default permissions for news articles.
* - 'seo': Search engine optimization settings like author, description, and keywords.
*/
function loadRouterConfig(): array
{
$routerConfig["default_page"] = "domov";
return [
'inlining' => false,
'domain' => 'adlerka',
'tld' => 'top',
'default_page' => 'index',
'default_site' => 'home',
'template_dir' => 'templates/',
'endpoint_dir' => 'endpoints/',
'page_dir' => 'pages/',
'protocol' => 'https://',
'site_prefix' => 'Adlerka',
'permissions' => [
'logged_out' => 1,
'logged_in_default' => 2,
'verified' => 3,
'trustworthy' => 4,
'moderator' => 5,
'user_admin' => 254,
'admin' => 255,
],
'page' => [
'default_secret' => 1,
'default_permissions' => 255,
$routerConfig["default_site"] = "home";
$routerConfig["template_dir"] = "templates/";
$routerConfig["page_dir"] = "pages/";
$routerConfig["protocol"] = "https://";
],
'newsarticle' => [
'default_permissions' => 255,
],
'meme' => [
'per_page' => 10
],
'seo' => [
'author' => 'Tím AdlerkaTop',
'description' => 'Toto je neoficiánla študentská stránka pre Adlerku, kde môžete nájsť plno zaujímavostí.',
'keywords' => 'adlerka, alderka, studenti, studentska stranka, web, dev, webdev, web dev, skola, zabava',
'generator' => 'TurboRoute',
'robots' => 'follow, index, max-snippet:-1, max-video-preview:-1, max-image-preview:large'
]
];
}

20
lib/dynamic_style.php Normal file
View File

@@ -0,0 +1,20 @@
<?php
/**
* Generates dynamic CSS styling based on user preferences stored in the session.
* Specifically, it creates a CSS rule for the user's favorite color if it's specified in their session data.
*
* @return string Returns a string containing a `<style>` tag with custom CSS if a favorite color is set
* and the user is logged in. Returns an empty string if no conditions are met.
*/
function doDynamicStyling() :string
{
$dynamic_style = "";
if(isLoggedIn() && !empty($_SESSION["favorite_color"]) && is_int($_SESSION["favorite_color"]) && $_SESSION["favorite_color"] <= 4294967295){
$dynamic_style = "<style>";
$color = dechex($_SESSION["favorite_color"]);
$dynamic_style .= "--root{ --favorite-color: #$color;";
$dynamic_style .= "</style>";
}
return $dynamic_style;
}

69
lib/endpoint.php Normal file
View File

@@ -0,0 +1,69 @@
<?php
/**
* Executes an endpoint script and returns the results.
*
* This function includes an endpoint PHP file that defines a function named `endpoint` which
* is expected to accept an array parameter and return an array result.
* It simply scopes an external file into a function to prevent variable conflicts.
*
* @param string $endpoint_file The path to the endpoint PHP file.
* @return array|null Returns the result of the endpoint function if successful, or null if the
* endpoint function or file does not behave as expected.
*/
function runEndpoint(string $endpoint_file): ?array
{
$endpoint_data = $_POST;
require_once $endpoint_file;
return endpoint($endpoint_data);
}
/**
* Retrieves and processes the output of a specified endpoint.
*
* This function determines the appropriate endpoint PHP file based on the provided endpoint name,
* executes the endpoint, and returns its results as a JSON-encoded string. It handles and
* translates different return types into a JSON format and manages HTTP response codes based on
* success or failure of the endpoint execution.
*
* @param string $endpoint_name The name of the endpoint, which is used to construct the file path to the endpoint script.
* @return string A JSON-encoded string representing the result of the endpoint, including a status indicator and any relevant data or error messages.
*@global array $routerRequest Current request data that might influence the endpoint processing.
* @global array $routerConfig Global configuration that contains paths and settings.
*/
function getEndpoint(string $endpoint_name): string
{
$output = array();
$output["Status"] = "Fail";
global $routerConfig;
global $routerRequest;
if(!$endpoint_name){
$endpoint_name = $routerRequest["site_name"];
}
$endpoint_file = $routerConfig["endpoint_dir"] . $endpoint_name . ".php";
if (file_exists($endpoint_file)){
$output_tmp = runEndpoint($endpoint_file);
$output["Endpoint"] = $endpoint_name;
$type = gettype($output_tmp);
switch ($type) {
case 'array':
$output = $output_tmp;
break;
default:
$output['Status'] = 'Fail';
$output["Error"] = "Endpoint error";
$output["Type"] = $type;
http_response_code(500);
}
}
else{
$output["Error"] = "Not found";
http_response_code(404);
}
return json_encode($output);
}

104
lib/inliner.php Normal file
View File

@@ -0,0 +1,104 @@
<?php
/**
* Processes an HTML string to inline all linked stylesheets by replacing <link> tags
* with corresponding <style> tags containing the CSS content.
* Might be broken, currently disabled in the config
*
* @param string $inputString The HTML content containing <link> tags to stylesheets.
* @return string The modified HTML content with stylesheets inlined within <style> tags.
*/
function inlineLocalStylesFromHref(string $inputString): string
{
$pattern = '/<link[^>]*?\srel=["\']?stylesheet["\'].*?\shref=["\']?\/(.*?)["\'][^>]*?>/i';
return preg_replace_callback($pattern, function($match) {
$href = $match[1];
$cssFilePath = $_SERVER['DOCUMENT_ROOT'] . '/' . $href;
$cssContent = file_get_contents($cssFilePath);
$cssDir = dirname($cssFilePath);
$cssContent = preg_replace_callback('/url\(["\']?(\/.*?|.*?)["\']?\)/i', function($urlMatch) use ($cssDir) {
$url = $urlMatch[1];
// Check if the URL starts with any protocol or //
if (!preg_match('/^([a-zA-Z]+:)?\/\//', $url)) {
$absolutePath = $cssDir . '/' . $url;
$relativePath = ltrim(substr($absolutePath, strlen($_SERVER['DOCUMENT_ROOT'])), '/');
return 'url("' . $relativePath . '")';
} else {
// If the URL starts with a protocol or //, leave it unchanged
return 'url("' . $url . '")';
}
}, $cssContent);
// Minify the CSS content
$cssContent = minifyCss($cssContent);
return "<style>$cssContent</style>";
}, $inputString);
}
/**
* Processes an HTML string to inline all external JavaScript files by replacing <script src="..."> tags
* with <script> tags containing the JavaScript content.
* Might be broken, currently disabled in the config
*
* @param string $inputString The HTML content containing <script src=""> tags.
* @return string The modified HTML content with external scripts inlined within <script> tags.
*/
function inlineScriptFromSrc(string $inputString): string
{
$pattern = '/<script.*?src=["\']\/(.*?)["\'].*?>\s*<\/script>/i';
return preg_replace_callback($pattern, function($match) {
$src = $match[1];
$jsContent = file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/' . $src);
// Minify the JavaScript content
$jsContent = minifyJs($jsContent);
return "<script>$jsContent</script>";
}, $inputString);
}
/**
* Minifies CSS content by removing comments, unnecessary whitespaces, semicolons, and optimizing other aspects of the stylesheet.
* Might be broken, currently disabled in the config
*
* @param string $css The original CSS content.
* @return string The minified CSS content.
*/
function minifyCss(string $css): string
{
// Remove comments
$css = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $css);
// Remove whitespace
$css = preg_replace('/\s+/', ' ', $css);
// Remove unnecessary semicolons
$css = str_replace(';}', '}', $css);
// Remove spaces around colons
$css = str_replace(': ', ':', $css);
// Remove spaces around commas
$css = str_replace(', ', ',', $css);
return trim($css);
}
/**
* Minifies JavaScript content by removing comments, unnecessary whitespaces, and optimizing spaces around operators.
* Might be broken, currently disabled in the config
*
* @param string $js The original JavaScript content.
* @return string The minified JavaScript content.
*/
function minifyJs(string $js): string
{
// Remove newlines and tabs
$js = str_replace("\t", '', $js);
// Remove spaces around operators
$js = preg_replace('~\s*([=+\-*/])\s*~', '$1', $js);
return trim($js);
}

316
lib/meme.php Normal file
View File

@@ -0,0 +1,316 @@
<?php
require_once "lib/upload.php";
require_once "lib/account.php";
/**
* Adds a meme to the database with associated image and text content.
*
* @param string $title The title of the meme.
* @param string $memeText The text content of the meme.
* @param int $imageID The ID of the image associated with the meme.
* @return array Returns an associative array with the operation status and a message.
* @global mysqli $mysqli The database connection object.
*/
function addMeme(string $title, string $memeText, int $imageID): array
{
global $mysqli;
$output = ["Status" => "Fail"];
if (isLoggedIn() && fileExists($imageID, false) && !empty($title) && !empty($memeText) && !empty($imageID) && $imageID > 0) {
$stmtMemeAdd = $mysqli->prepare('INSERT INTO Memes (AuthorID, Title, TextContent, FileID) VALUES (?, ?, ?, ?)');
$stmtMemeAdd->bind_param('issi', $_SESSION['ID'], htmlspecialchars($title), htmlspecialchars($memeText), $imageID);
if ($stmtMemeAdd->execute() && $stmtMemeAdd->affected_rows > 0) {
$output["Status"] = "Success";
$output["Meme"] = "Funny";
}
}
return $output;
}
function executeAndRenderMemes(mysqli_stmt $stmt): string {
global $routerConfig;
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($memeID, $title, $textContent, $createdAt, $authorID, $filePath, $imageWidth, $imageHeight, $userNickname);
$memes_out = '';
$meme_template = file_get_contents($routerConfig['template_dir'] . "meme.html");
$meme_gallery_template = file_get_contents($routerConfig['template_dir'] . 'meme_gallery.html');
while ($stmt->fetch()) {
$memes_out .= renderMeme($memeID, $authorID, $title, $textContent, $createdAt, $filePath, $imageWidth, $imageHeight, $userNickname, $meme_template);
}
$meme_add = isLoggedIn() ? file_get_contents($routerConfig['template_dir'] . 'meme_add.html') : '';
$meme_gallery_out = str_replace('__TEMPLATE_MEMES_HERE__', $memes_out, $meme_gallery_template);
$meme_gallery_out = str_replace('__TEMPLATE_MEME_ADD__', $meme_add, $meme_gallery_out);
$stmt->close();
return $meme_gallery_out;
}
/**
* Renders a meme into HTML based on provided data and a template.
*
* @param int $id The ID of the meme.
* @param int $authorId The author's user ID.
* @param string $title The title of the meme.
* @param string $textContent The text content of the meme.
* @param string $createdAt The creation timestamp of the meme.
* @param string $filePath The file path of the associated image.
* @param int $imageWidth The width of the image.
* @param int $imageHeight The height of the image.
* @param string $userNickname The nickname of the meme's author.
* @param string $meme_template The HTML template for a meme. (used to not read the template over and over when rendering more memes)
* @return string Returns the rendered HTML of the meme.
*/
function renderMeme(int $id, int $authorId, string $title, string $textContent, string $createdAt, string $filePath, int $imageWidth, int $imageHeight, string $userNickname, string $meme_template): string
{
$meme_out = str_replace('__TEMPLATE_MEME_TITLE__', htmlspecialchars($title), $meme_template);
$meme_out = str_replace('__TEMPLATE_MEME_AUTHOR__', htmlspecialchars($userNickname), $meme_out);
$meme_out = str_replace('__TEMPLATE_MEME_DATE__', htmlspecialchars($createdAt), $meme_out);
$meme_out = str_replace('__TEMPLATE_MEME_IMAGE__', '/' . htmlspecialchars($filePath), $meme_out);
$meme_out = str_replace('__TEMPLATE_MEME_IMAGE_WIDTH__', strval($imageWidth), $meme_out);
$meme_out = str_replace('__TEMPLATE_MEME_IMAGE_HEIGHT__', strval($imageHeight), $meme_out);
$meme_out = str_replace('__TEMPLATE_MEME_DELETE_BUTTON__', (isModerator() || $_SESSION['ID'] == $authorId) ? "<button onclick=\"deleteMeme($id);\"><i class='ri-delete-bin-line'></i></button>" : '', $meme_out);
$meme_votes = calculateNetVotes($id);
$meme_net_votes = $meme_votes['NetVotes'];
if ($meme_votes['UserVote'] > 0) {
$meme_upvote_active = 'fill';
$meme_downvote_active = 'line';
$meme_vote_counter_class = 'positive';
$meme_upvote_button_class = ' visual_hover';
$meme_downvote_button_class = '';
} elseif (($meme_votes['UserVote'] < 0)) {
$meme_upvote_active = 'line';
$meme_downvote_active = 'fill';
$meme_vote_counter_class = 'negative';
$meme_upvote_button_class = '';
$meme_downvote_button_class = ' visual_hover';
} else {
$meme_downvote_active = 'line';
$meme_upvote_active = 'line';
$meme_vote_counter_class = 'neutral';
$meme_upvote_button_class = '';
$meme_downvote_button_class = '';
}
$meme_upvote = isLoggedIn() ? "<button id='meme_votes_upvote_button_$id' class='meme_upvote$meme_upvote_button_class' onclick=\"voteMeme($id, 1);\"> <i id='meme_votes_upvote_$id' class=\"ri-arrow-up-circle-$meme_upvote_active\"></i></button>" : '';
$meme_downvote = isLoggedIn() ? "<button id='meme_votes_downvote_button_$id' class='meme_downvote$meme_downvote_button_class' onclick=\"voteMeme($id, 0);\"> <i id='meme_votes_downvote_$id' class=\"ri-arrow-down-circle-$meme_downvote_active\"></i></button>" : '';
$meme_out = str_replace('__TEMPLATE_MEME_VOTES_NUMBER__', strval($meme_net_votes), $meme_out);
$meme_out = str_replace('__TEMPLATE_MEME_UPVOTE__', $meme_upvote, $meme_out);
$meme_out = str_replace('__TEMPLATE_MEME_DOWNVOTE__', $meme_downvote, $meme_out);
$meme_out = str_replace('__TEMPLATE_MEME_ID__', strval($id), $meme_out);
$meme_out = str_replace('__TEMPLATE_MEME_VOTE_COUNTER_CLASS__', $meme_vote_counter_class, $meme_out);
return str_replace('__TEMPLATE_MEME_TEXT__', htmlspecialchars($textContent), $meme_out);
}
/**
* Renders a gallery of memes, optionally filtered by author ID, meme ID, or a keyword.
*
* This function retrieves memes from the database and returns an HTML string representation.
* It supports filtering by author ID, meme ID, or a keyword that is searched in titles and text content.
* It also supports pagination through an offset parameter.
*
* @param int|null $offset Pagination offset, used to calculate the starting point for records to return.
* @param int|null $authorId Optional author ID for filtering memes by a specific author.
* @param int|null $memeId Optional meme ID for rendering a single meme.
* @param string|null $keyword Optional keyword for full-text search in meme titles and content.
* @return string Returns the complete HTML content of the meme gallery, optionally filtered.
*/
function getMemeGallery(?int $offset = null, ?int $authorId = null, ?int $memeId = null, ?string $keyword = null): array {
return [
"Status" => "Success",
"Output" => renderMemeGallery($offset, $authorId, $memeId, $keyword)
];
}
function renderMemeGallery(?int $offset = null, ?int $authorId = null, ?int $memeId = null, ?string $keyword = null): string {
global $mysqli, $routerConfig;
// Start building the SQL query
$query = 'SELECT Memes.ID, Memes.Title, Memes.TextContent, Memes.CreatedAt, Memes.AuthorID,
Files.Path, Files.Width, Files.Height, Users.Nickname
FROM Memes
INNER JOIN Users ON Memes.AuthorID = Users.ID
INNER JOIN Files ON Memes.FileID = Files.ID';
$conditions = [];
$params = [];
$types = '';
// Add conditions based on provided parameters
if ($authorId !== null) {
$conditions[] = 'Memes.AuthorID = ?';
$params[] = $authorId;
$types .= 'i';
}
if ($memeId !== null) {
$conditions[] = 'Memes.ID = ?';
$params[] = $memeId;
$types .= 'i';
}
if ($keyword !== null) {
$conditions[] = '(Memes.Title LIKE CONCAT("%", ?, "%") OR Memes.TextContent LIKE CONCAT("%", ?, "%"))';
$params[] = $keyword;
$params[] = $keyword;
$types .= 'ss';
}
// Append conditions to the query
if (!empty($conditions)) {
$query .= ' WHERE ' . join(' AND ', $conditions);
}
if($offset == null) {
$offset = 0;
}
// Add pagination and limit
$query .= ' LIMIT ? OFFSET ?';
$params[] = $routerConfig['meme']['per_page'];
$params[] = $routerConfig['meme']['per_page'] * $offset;
$types .= 'ii';
$stmt = $mysqli->prepare($query);
$stmt->bind_param($types, ...$params);
return executeAndRenderMemes($stmt);
}
/**
* Deletes a meme from the database if the current user has the right permissions.
*
* @param int $memeID The ID of the meme to delete.
* @return array Returns an associative array with the status of the operation.
* @global mysqli $mysqli The database connection object.
*/
function deleteMeme(int $memeID): array
{
global $mysqli;
$out = ["Status" => "Fail"];
if (isLoggedIn()) {
$query = !isModerator() ? 'DELETE FROM Memes WHERE ID = ? AND AuthorID = ?' : 'DELETE FROM Memes WHERE ID = ?';
$stmtDelete = $mysqli->prepare($query);
if (!isModerator()) {
$stmtDelete->bind_param('ii', $memeID, $_SESSION['ID']);
} else {
$stmtDelete->bind_param('i', $memeID);
}
$stmtDelete->execute();
if ($stmtDelete->affected_rows > 0) {
$out['Status'] = 'Success';
}
$stmtDelete->close();
}
return $out;
}
/**
* Records or updates a vote on a meme by the current user.
*
* @param int $memeID The ID of the meme to be voted on.
* @param int $isUpvote Indicates whether the vote is an upvote (1) or downvote (0).
* @return array Returns an associative array with the status of the vote operation.
* @global mysqli $mysqli The database connection object.
*/
function voteMeme(int $memeID, int $isUpvote): array
{
global $mysqli;
$out = ["Status" => "Fail"];
if ($isUpvote != 1) {
$isUpvote = 0;
}
$memeVoteConn = $mysqli->prepare('INSERT INTO MemeVotes (MemeID, UserID, isUpvote) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE isUpvote = VALUES(isUpvote)');
$memeVoteConn->bind_param('iii', $memeID, $_SESSION['ID'], $isUpvote);
$memeVoteConn->execute();
if ($memeVoteConn->affected_rows > 0) {
$out['Status'] = 'Success';
}
$memeVoteConn->close();
return $out;
}
/**
* Deletes a vote previously made by the current user on a meme.
*
* @param int $memeID The ID of the meme whose vote is to be deleted.
* @return array Returns an associative array with the status of the deletion.
* @global mysqli $mysqli The database connection object.
*/
function deleteVoteMeme(int $memeID): array
{
global $mysqli;
$out = ["Status" => "Fail"];
$memeVoteConn = $mysqli->prepare('DELETE FROM MemeVotes WHERE MemeID = ? AND UserID = ?');
$memeVoteConn->bind_param('ii', $memeID, $_SESSION['ID']);
$memeVoteConn->execute();
if ($memeVoteConn->affected_rows > 0) {
$out['Status'] = 'Success';
}
$memeVoteConn->close();
return $out;
}
/**
* Calculates the net votes for a meme and determines if the current user has voted on it.
* The array has both the net votes and the user vote(0 when the user hasn't voted)
*
* @param int $memeID The ID of the meme for which votes are being calculated.
* @return array Returns an array with net votes and the user's vote status.
* @global mysqli $mysqli The database connection object.
*/
function calculateNetVotes(int $memeID): array
{
global $mysqli;
// Adjusted query to calculate net votes and get the user's vote in one go
$query = "
SELECT
SUM(CASE WHEN isUpvote = 1 THEN 1 ELSE -1 END) AS NetVotes,
(
SELECT CASE WHEN isUpvote = 1 THEN 1 ELSE -1 END
FROM MemeVotes
WHERE MemeID = ? AND UserID = ?
) AS UserVote
FROM MemeVotes
WHERE MemeID = ?";
$stmt = $mysqli->prepare($query);
$userID = $_SESSION['ID'];
$stmt->bind_param('iii', $memeID, $userID, $memeID);
$stmt->execute();
$result = $stmt->get_result();
$data = $result->fetch_assoc();
$netVotes = $data['NetVotes'] ?? 0; // Null coalescing operator in case no votes are found
$userVote = $data['UserVote'] ?? 0; // Default to 0 if the user hasn't voted
$stmt->close();
return [
"NetVotes" => $netVotes,
"UserVote" => $userVote
];
}
/**
* Fetches the net votes and user's vote status for a specific meme.
* Essentially just a wrapper of getMemeVotes for an API call
*
* @param int $memeID The ID of the meme to fetch votes for.
* @return array Returns an array with the net votes and the user's vote status, along with operation status.
*/
function getMemeVotes(int $memeID): array
{
$voteData = calculateNetVotes($memeID);
return [
"Status" => "Success",
"NetVotes" => $voteData['NetVotes'],
"UserVote" => $voteData['UserVote']
];
}

141
lib/navigation.php Normal file
View File

@@ -0,0 +1,141 @@
<?php
/**
* Includes a PHP file that returns metadata associated with a dynamic page.
* It simply scopes an external file into a function to prevent variable conflicts.
*
* @param string $file The file path to the PHP file that contains metadata.
* @return array Returns an associative array of metadata from the included PHP file.
*/
function getDynamicMetadata(string $file): array{
return include($file);
}
/**
* Extracts and validates the minimal permission level required to access certain content,
* defaulting to system configuration if not properly set or in case of an error.
*
* @param array $metadata Metadata array that should include a 'parameters' key with 'minimal_permission_level'.
* @return int Returns the minimal permission level required to access a page.
*@global array $routerConfig Global router configuration settings.
*/
function getDynamicPermission(array $metadata): int {
global $routerConfig;
$params = $metadata["parameters"];
try {
$permission_level = $params["minimal_permission_level"];
if (!is_numeric($permission_level) || $permission_level <= 0) {
$permission_level = $routerConfig["page"]["default_permissions"];
}
}
catch (Exception){
$permission_level = $routerConfig["page"]["default_permissions"];
} finally {
return $permission_level;
}
}
/**
* Generates HTML navigation links for all sites and pages configured in the router,
* adjusting active states based on current request and filtering links by user permissions.
*
* @global array $routerConfig Global configuration that includes directory paths and default settings.
* @global array $routerRequest Current request details including site and page name.
* @return string Returns the HTML string of the navigation menu.
*/
function generateNavigation(): string
{
global $routerConfig;
global $routerRequest;
$nav = file_get_contents($routerConfig["template_dir"] . "nav.html");
$site_dirs = array_diff(scandir($routerConfig["page_dir"]), array('.', '..'));
$nav_out = "";
foreach ($site_dirs as $site_dir) {
$pages_dir = array_diff(scandir($routerConfig["page_dir"] . $site_dir), array('.', '..'));
$site_name = str_replace("_", " ", $site_dir);
$site_name = ucfirst($site_name);
$site_location = "/" . $site_dir . "/" . $routerConfig["default_page"];
if ($routerRequest["site_name"] == $site_dir) {
//this is the current page
$site_class = "class=\"navsite_link active\"";
}
else{
$site_class = "class=\"navsite_link\"";
}
$navigation_pages = "";
foreach ($pages_dir as $page_file) {
$page_file_tmp = explode(".", $page_file);
$page_basename = $page_file_tmp[0];
$page_class = "class=\"navpage_link\"";
if ($routerRequest["site_name"] == $site_dir && $routerRequest["page_name"] == $page_basename) {
$page_class = "class=\"navpage_link active\"";
}
$page_location = "/" . $site_dir . "/" . $page_basename;
$page_name = str_replace("_", " ", $page_basename);
$page_name = explode(".", $page_name)[0];
$page_name = ucfirst($page_name);
$page_file_path = $routerConfig["page_dir"] . $site_dir . "/" . $page_file ;
if($page_file_tmp[1] == "html"){
$page_tmp = file_get_contents($page_file_path);
$pageMetadata = parsePageTag($page_tmp);
if(!empty($pageMetadata["parameters"]["minimal_permission_level"])){
$page_required_permission = intval($pageMetadata["parameters"]["minimal_permission_level"]);
}
else{
$page_required_permission = $routerConfig["page"]["default_permissions"];
}
if(!empty($pageMetadata["parameters"]["page_title"])){
$page_name = $pageMetadata["parameters"]["page_title"];
}
}
elseif($page_file_tmp[1] == "php"){
$pageMetadata = getDynamicMetadata($page_file_path);
$page_required_permission = getDynamicPermission($pageMetadata);
if(!empty($pageMetadata["parameters"]["page_title"])){
$page_name = $pageMetadata["parameters"]["page_title"];
}
}
else{
$page_required_permission = $routerConfig["page"]["default_permissions"];
}
if($page_required_permission <= $_SESSION["privilege_level"]) {
$navpage_attributes = "data-site='$site_dir' data-page='$page_basename'";
$navigation_pages .= "<li class='navpage_item' $navpage_attributes ><a $navpage_attributes href='$page_location' $page_class>$page_name</a></li>";
}
}
if(!empty($navigation_pages)){
$default_page = $routerConfig["default_page"];
$navsite_attributes = "data-page='$default_page' data-site='$site_dir'";
$nav_out .= "<li class='navsite_item' ><a $navsite_attributes href='$site_location' $site_class>$site_name</a><ul class='navpage_list'>$navigation_pages</ul></li>";
}
}
return str_replace("__NAV_PAGES__", $nav_out, $nav);
}
/**
* Provides a simple API endpoint-like response for fetching generated navigation HTML.
* Wraps generateNavigation for an API.
*
* @return array Returns an associative array with the navigation HTML and a status indicating success.
*/
function getNavigationEndpoint() :array{
return [
"Status" => "Success",
"Navigation" => generateNavigation(),
];
}

View File

@@ -1,54 +0,0 @@
<?php
function generateNavigation()
{
global $routerConfig;
global $routerRequest;
$site_dirs = array_diff(scandir($routerConfig["page_dir"]), array('.', '..'));
$nav_out = "";
foreach ($site_dirs as $site_dir) {
$pages_dir = array_diff(scandir($routerConfig["page_dir"] . $site_dir), array('.', '..'));
$site_name = str_replace("_", " ", $site_dir);
if ($site_name == "global") {
$site_name = "misc";
$site_dir = $routerConfig["default_page"];
}
$site_name = ucfirst($site_name);
$site_location = $routerConfig["protocol"] . $site_dir . "." . $routerRequest["domain"] . "." . $routerRequest["tld"] . "/" . $routerConfig["default_page"];
if ($routerRequest["subdomain"] == $site_dir) {
//this is the current page
$site_class = "class=\"navsite_link active\"";
}
else{
$site_class = "class=\"navsite_link\"";
}
$navpages = "";
foreach ($pages_dir as $page_dir) {
$page_dir = explode(".", $page_dir)[0];
$page_class = "class=\"navpage_link\"";
if ($routerRequest["subdomain"] == $site_dir && $routerRequest["page_name"] == $page_dir) {
$page_class = "class=\"navpage_link active\"";
}
$page_location = $routerConfig["protocol"] . $site_dir . "." . $routerRequest["domain"] . "." . $routerRequest["tld"] . "/" . $page_dir;
$page_name = str_replace("_", " ", $page_dir);
$page_name = explode(".", $page_name)[0];
$page_name = ucfirst($page_name);
$navpages .= "<li class='navpage_item'><a href='$page_location' $page_class>$page_name</a></li>";
}
$nav_out .= "<li class='navsite_item'><a href='$site_location' $site_class>$site_name</a><ul class='navpage_list'>$navpages</ul></li>";
}
return $nav_out;
}

130
lib/newsarticle.php Normal file
View File

@@ -0,0 +1,130 @@
<?php
require_once "lib/account.php";
/**
* Retrieves news articles based on the current user's privilege level.
* The function queries the NewsArticles and Users tables to fetch articles
* that the user has the privilege to view. Articles are joined with user
* information to include the author's nickname.
*
* @global mysqli $mysqli The mysqli database connection object.
* @return array Returns an associative array with a status key indicating the success or failure,
* and an 'Articles' key containing an array of articles if successful.
*/
function getNewsArticles() :array
{
global $mysqli;
$output = ["Status" => "Fail"]; // Default Status is "Fail"
$articles = [];
$stmt = $mysqli->prepare("SELECT NewsArticles.ID, NewsArticles.WrittenAt, NewsArticles.WrittenBy, NewsArticles.Title, NewsArticles.Body, NewsArticles.FileList, Users.Nickname FROM NewsArticles INNER JOIN Users ON NewsArticles.WrittenBy = Users.ID WHERE NewsArticles.PrivilegeLevel <= ?;");
$id = 0;
$writtenAt = "";
$writtenBy = 0;
$title = "";
$body = "";
$filelist = 0;
$writtenByName = "";
$stmt->bind_param("i", $_SESSION["privilege_level"]);
$stmt->bind_result($id, $writtenAt, $writtenBy, $title, $body, $filelist, $writtenByName);
$stmt->execute();
while ($stmt->fetch()) {
$articles[] = [
'ID' => $id,
'WrittenAt' => $writtenAt,
'Title' => $title,
'Body' => $body,
'WrittenByName' =>$writtenByName
];
}
// Check if any results were fetched
if (!empty($articles)) {
$output["Status"] = "Success";
$output["Articles"] = $articles;
}
return $output;
}
/**
* Adds a new news article to the database if the user is logged in and has the appropriate
* privilege level. The function sanitizes the title and body of the article to prevent XSS attacks.
*
* @global mysqli $mysqli The mysqli database connection object.
* @global array $routerConfig Configuration array that includes default permission settings.
* @param string $title The title of the news article. Default value is "Nazov".
* @param string $body The body of the news article. Default value is "Obsah".
* @param int $privilegeLevel The privilege level required to view the article. If set to 0, uses default from configuration.
* @return array Returns an associative array with a status key that indicates the success or failure of the operation.
*/
function addNewsArticle(string $title="Nazov", string $body="Obsah", int $privilegeLevel=0) :array
{
global $mysqli;
global $routerConfig;
if ($privilegeLevel == 0){
$privilegeLevel = $routerConfig['newsarticle']['default_permissions'];
}
$output = ["Status" => "Fail"]; // Default Status is "Fail"
if (isLoggedIn() && $privilegeLevel <= $_SESSION["privilege_level"]) {
$query = $mysqli->prepare("INSERT INTO NewsArticles (WrittenBy, Title, Body, FileList, PrivilegeLevel) VALUES (?, ?, ?, 0, ?);");
$minpriv = intval($privilegeLevel);
$query->bind_param("issi", $_SESSION["ID"], htmlspecialchars($title), htmlspecialchars($body), $minpriv);
$query->execute();
if ($query->affected_rows > 0) {
$output["Status"] = "Success";
}
$query->close();
}
return $output;
}
/**
* Adds a comment to a news article.
*
* @param int $userId User who is commenting.
* @param int $newsArticleId ID of the news article.
* @param string $commentText The content of the comment.
* @param int|null $parentId ID of the parent comment if it's a reply.
* @return array Status array indicating success or failure.
* @global mysqli $mysqli The mysqli database connection object.
*/
function addNewsComment(int $userId, int $newsArticleId, string $commentText, ?int $parentId = null): array {
global $mysqli;
$output = ["Status" => "Fail"]; // Default Status is "Fail"
if (!isLoggedIn()) {
$output['Error'] = "User must be logged in.";
return $output;
}
// Prepare the SQL statement to prevent SQL injection
$stmt = $mysqli->prepare("INSERT INTO NewsComments (ParentID, UserID, NewsArticleID, CommentText) VALUES (?, ?, ?, ?);");
// Bind parameters. 'i' denotes an integer and 's' denotes a string.
$stmt->bind_param("iiis", $parentId, $userId, $newsArticleId, $commentText);
// Execute the query
if ($stmt->execute()) {
// Check if any rows were affected
if ($stmt->affected_rows > 0) {
$output["Status"] = "Success";
} else {
$output["Error"] = "No rows affected.";
}
} else {
$output["Error"] = $stmt->error;
}
// Close statement
$stmt->close();
return $output;
}

280
lib/page.php Normal file
View File

@@ -0,0 +1,280 @@
<?php
require_once "lib/dynamic_style.php";
require_once "lib/script_data.php";
/**
* Loads and returns the result of a PHP file.
* This function is typically used to process dynamic content of a page.
* It simply scopes an external file into a function to prevent variable conflicts.
*
* @param string $page_file The file path to the dynamic page.
* @return array Returns the array of data generated by including the PHP file.
*/
function renderDynamicPage(string $page_file): array
{
return require $page_file;
}
/**
* Removes all HTML comments from the provided content string.
*
* @param string $content The HTML content from which to remove comments.
* @return string The content without any HTML comments.
*/
function removeHtmlComments(string $content = '') :string {
return preg_replace('/<!--(.|\s)*?-->/', '', $content);
}
/**
* Parses custom `<page>` tags from the given input string and extracts parameters.
* Returns the input string with `<page>` tags removed and a list of parameters.
*
* @param string $input The input HTML or text containing `<page>` tags.
* @return array Returns an associative array with 'parameters' (parsed from the tag)
* and 'output' (the modified input string with `<page>` tags removed).
*/
function parsePageTag(string $input): array
{
// Define the pattern for the tag
$pattern = '/<page\s+([^>]+)><\/page>/i';
// Check if the pattern matches the input
if (preg_match($pattern, $input, $matches)) {
// Extract parameters
$parameters = [];
if (preg_match_all('/(\w+)="([^"]+)"/', $matches[1], $paramMatches, PREG_SET_ORDER)) {
foreach ($paramMatches as $paramMatch) {
$parameters[$paramMatch[1]] = $paramMatch[2];
}
}
// Remove the tag from the input
$output = preg_replace($pattern, '', $input, 1);
return ['parameters' => $parameters, 'output' => $output];
}
// If no match is found, return the original input
return ['parameters' => [], 'output' => $input];
}
/**
* Renders a page based on specified page and site names, handling dynamic and static content,
* permissions, and error pages.
*
* @param string|null $page_name The name of the page to render. If null, uses default from request.
* @param string|null $site_name The name of the site to render. If null, uses default from request.
* @return array Returns an associative array containing the rendered page content, page name, site name, and page title.
*/
function renderPage(string $page_name = null, string $site_name = null): array
{
global $routerConfig;
global $routerRequest;
if(!$site_name) {
$site_name = $routerRequest["site_name"];
}
$site_title = str_replace("_", " ", $site_name);
$site_title = ucfirst($site_title);
if(!$page_name){
$page_name = $routerRequest["page_name"];
}
$dynamic_page_file = $routerConfig["page_dir"] . $site_name . "/" . $page_name . ".php";
$page_file = $routerConfig["page_dir"] . $site_name . "/" . $page_name . ".html";
if (file_exists($dynamic_page_file)){
$pageMetadata = renderDynamicPage($dynamic_page_file);
$page = $pageMetadata["output"];
}
elseif (file_exists($page_file)){
$page_tmp = file_get_contents($page_file);
$pageMetadata = parsePageTag($page_tmp);
$page = $pageMetadata["output"];
}
else{
$page_tmp = file_get_contents($routerConfig["template_dir"] . "404.html");
$pageMetadata = parsePageTag($page_tmp);
$page = $pageMetadata["output"];
http_response_code(404);
}
if(!empty($pageMetadata["parameters"]["minimal_permission_level"])){
$page_required_permission = intval($pageMetadata["parameters"]["minimal_permission_level"]);
}
else{
$page_required_permission = $routerConfig["page"]["default_permissions"];
}
if(!empty($pageMetadata["parameters"]["secret"])){
$origSecret = $pageMetadata["parameters"]["secret"];
if ($origSecret == "yes"){
$is_secret_page = 1;
}
elseif ($origSecret == "no"){
$is_secret_page = 0;
}
else{
$is_secret_page = $routerConfig["page"]["default_secret"];
}
}
else{
$is_secret_page = $routerConfig["page"]["default_secret"];
}
if($page_required_permission > $_SESSION["privilege_level"]){
if($is_secret_page == 1) {
$page_tmp = file_get_contents($routerConfig["template_dir"] . "404.html");
$pageMetadata = parsePageTag($page_tmp);
$page = $pageMetadata["output"];
http_response_code(404);
}
else{
$page_tmp = file_get_contents($routerConfig["template_dir"] . "403.html");
$pageMetadata = parsePageTag($page_tmp);
$page = $pageMetadata["output"];
http_response_code(403);
}
}
$page = str_replace("__DEFAULT_LINK__", "/" . $routerConfig["default_site"] . "/" . $routerConfig["default_page"], $page);
if(!is_string($page)){
$page = "";
}
if(!empty($pageMetadata["parameters"]["page_title"])){
$page_title = $pageMetadata["parameters"]["page_title"];
}
else{
$page_title = $page_name;
}
$page_title = $routerConfig['site_prefix'] . " " . $site_title . " " . $page_title;
$page = str_replace("__TEMPLATE_PAGE_TITLE__", $page_title, $page);
$page = removeHtmlComments($page);
return [
"PageContent" => $page,
"PageName" => $page_name,
"SiteName" => $site_name,
"PageTitle" => $page_title,
];
}
/**
* Compiles a complete web page by injecting dynamic elements into a template skeleton,
* including headers, footers, and SEO tags.
* It is used when not going to a page by AJAX to initialize everything.
*
* @param string|null $site_name_in The site name to be used; defaults from global configuration if null.
* @param string|null $page_name_in The page name to be used; defaults from global configuration if null.
* @return string The complete HTML content of the web page ready for display.
*/
function getPage(string $site_name_in = null, string $page_name_in = null): string
{
$page_tmp = renderPage($page_name_in, $site_name_in);
$page = $page_tmp["PageContent"];
$page_name = $page_tmp["PageName"];
$site_name = $page_tmp["SiteName"];
global $routerConfig;
$skeleton = file_get_contents($routerConfig["template_dir"] . "skeleton.html");
$footer = file_get_contents($routerConfig["template_dir"] . "footer.html");
$page_title = $page_tmp["PageTitle"];
$dynamic_style = doDynamicStyling();
$dynamic_script_data = [
"currentPage" => $page_name,
"currentSite" => $site_name,
"currentTitle" => $page_title,
"defaultPage" => $routerConfig["default_page"],
"defaultSite" => $routerConfig["default_site"],
"UserInfo_Privileges" => $_SESSION["privilege_level"],
];
if(isLoggedIn()){
$dynamic_script_data += [
"UserInfo_FirstName" => $_SESSION["first_name"],
"UserInfo_LastName" => $_SESSION["last_name"],
"UserInfo_Nickname" => $_SESSION["nickname"],
"UserInfo_Email" => $_SESSION["email"],
"UserInfo_MinecraftNick" => $_SESSION["minecraft_nickname"],
];
}
$dynamic_script = generateScriptData($dynamic_script_data);
$navigation = generateNavigation();
$seo = array();
if(!empty($pageMetadata["parameters"]["author"])){
$seo["author"] = htmlspecialchars($pageMetadata["parameters"]["author"]);
}
else{
$seo["author"] = $routerConfig["seo"]["author"];
}
if(!empty($pageMetadata["parameters"]["description"])){
$seo["description"] = htmlspecialchars($pageMetadata["parameters"]["description"]);
}
else{
$seo["description"] = $routerConfig["seo"]["description"];
}
if(!empty($pageMetadata["parameters"]["keywords"])){
$seo["keywords"] = $routerConfig["seo"]["keywords"] . ", " . htmlspecialchars($pageMetadata["parameters"]["keywords"]);
}
else{
$seo["keywords"] = $routerConfig["seo"]["keywords"];
}
$seo["generator"] = $routerConfig["seo"]["generator"];
$seo["robots"] = $routerConfig["seo"]["robots"];
$seo_stuff = "";
foreach ($seo as $key => $value){
$seo_stuff .= "<meta name='$key' content='$value'>\n";
}
$out = $skeleton;
$out = str_replace("__TEMPLATE__NAV__", $navigation, $out);
$out = str_replace("__TEMPLATE__PAGE__", $page, $out);
$out = str_replace("__TEMPLATE__FOOTER__", $footer, $out);
$out = str_replace("__TEMPLATE__DYNAMIC__SCRIPT__", $dynamic_script, $out);
$out = str_replace("__TEMPLATE__DYNAMIC__STYLE__", $dynamic_style, $out);
$out = str_replace("__TEMPLATE_SEO_STUFF__", $seo_stuff, $out);
if($routerConfig["inlining"]) {
require_once "lib/inliner.php";
$out = inlineLocalStylesFromHref($out);
$out = inlineScriptFromSrc($out);
}
return str_replace("__TEMPLATE_PAGE_TITLE__", $page_title, $out);
}
/**
* Provides an API interface to get page details including content and meta-information for routing purposes.
* This is what enables the page to never refresh.
*
* @param string $page_name The name of the page.
* @param string $site_name The name of the site.
* @return array Returns an array with status, page content, location URL, and title for the requested page.
*/
function getPageEndpoint(string $page_name, string $site_name) :array
{
$page_location = "/" . $site_name . "/" . $page_name;
$page_tmp = renderPage($page_name, $site_name);
return [
"Status" => "Success",
"Page" => $page_tmp["PageContent"],
"PageLocation" => $page_location,
"PageTitle" => $page_tmp["PageTitle"],
];
}

46
lib/router.php Normal file
View File

@@ -0,0 +1,46 @@
<?php
/**
* Initializes the routing system for a web application. This function processes the incoming
* URL and determines the site name and page name based on the configuration and the URL structure.
* It handles default configurations and supports different types of requests like API or page requests.
*
* @global array $routerConfig The global configuration array that includes default site and page settings.
* @return array Returns an associative array containing the routing information, including the site name,
* page name, request type, and parsed request address from the HTTP host.
*/
function initRouter(): array
{
global $routerConfig;
$routerRequest = array();
$routerRequest["requestAddress"] = array_slice(explode('.', $_SERVER['HTTP_HOST']), -3, 3); //get the last 3 elements
$request_uri = explode("/", $_SERVER["QUERY_STRING"]);
$request_uri = array_slice($request_uri, -3, 3);
$routerRequest["site_name"] = $routerConfig["default_site"];
$routerRequest["page_name"] = $routerConfig["default_page"];
if(count($request_uri) > 2){
$routerRequest["page_name"] = basename($request_uri[2]);
}
if(count($request_uri) > 1){
$routerRequest["site_name"] = basename($request_uri[1]);
}
if($_SERVER["REQUEST_METHOD"] == "POST"){
$routerRequest["type"] = "api";
}
if(empty($routerRequest["type"])) {
$routerRequest["type"] = "page";
}
return $routerRequest;
}

View File

@@ -1,94 +0,0 @@
<?php
function initRouter(){
global $routerRequest;
global $routerConfig;
$routerRequest["requestAddress"] = array_slice(explode('.', $_SERVER['HTTP_HOST']), -3, 3); //get the last 3 elements
$needsRedirect = false;
if(count($routerRequest["requestAddress"]) < 3){
// Root domain accessed directly
$needsRedirect = true;
$routerRequest["subdomain"] = $routerConfig["default_site"];
$routerRequest["domain"] = basename($routerRequest["requestAddress"][0]);
$routerRequest["tld"] = basename($routerRequest["requestAddress"][1]);
} else {
$routerRequest["subdomain"] = basename($routerRequest["requestAddress"][0]);
$routerRequest["domain"] = basename($routerRequest["requestAddress"][1]);
$routerRequest["tld"] = basename($routerRequest["requestAddress"][2]);
$routerRequest["page_name"] = basename($_SERVER["QUERY_STRING"]);
if (empty($routerRequest["page_name"])) {
// Page name is empty
$needsRedirect = true;
$routerRequest["page_name"] = $routerConfig["default_page"];
}
}
if ($needsRedirect) {
$redirectAddress = $routerConfig["protocol"] .
$routerRequest["subdomain"] . "." .
$routerRequest["domain"] . "." .
$routerRequest["tld"] . "/" .
$routerRequest["page_name"];
// Redirect with default page name
header("Location: $redirectAddress");
}
return !$needsRedirect;
}
function renderDynamicPage($page_file)
{
require_once $page_file;
return render();
}
function getPage($page_name = null){
global $routerConfig;
global $routerRequest;
if(!$page_name){
$page_name = $routerRequest["page_name"];
}
$dynamic_page_file = $routerConfig["page_dir"] . $routerRequest["subdomain"] . "/" . $page_name . ".php";
$page_file = $routerConfig["page_dir"] . $routerRequest["subdomain"] . "/" . $page_name . ".html";
$dynamic_page_file_global = $routerConfig["page_dir"] . "global/" . $page_name . ".php";
$page_file_global = $routerConfig["page_dir"] . "global/" . $page_name . ".html";
$skeleton = file_get_contents($routerConfig["template_dir"] . "skeleton.html");
$nav = file_get_contents($routerConfig["template_dir"] . "nav.html");
if (file_exists($dynamic_page_file_global)){
$page = renderDynamicPage($dynamic_page_file_global);
}
elseif (file_exists($page_file_global)){
$page = file_get_contents($page_file_global);
}
elseif (file_exists($dynamic_page_file)){
$page = renderDynamicPage($dynamic_page_file);
}
elseif (file_exists($page_file)){
$page = file_get_contents($page_file);
}
else{
$page = file_get_contents($routerConfig["template_dir"] . "404.html");
}
$navpages = generateNavigation();
$nav = str_replace("__NAV_PAGES__", $navpages, $nav);
$out = $skeleton;
$out = str_replace("__TEMPLATE__NAV__", $nav, $out);
$out = str_replace("__TEMPLATE__PAGE__", $page, $out);
return str_replace("__TEMPLATE_PAGE_NAME__", $page_name, $out);
}

28
lib/script_data.php Normal file
View File

@@ -0,0 +1,28 @@
<?php
/**
* Generates a JavaScript script tag containing commands to store PHP array key-value pairs in local storage.
* This function is designed to translate PHP associative array data into JavaScript local storage items.
* It ensures that the array is associative and single-level before proceeding with the JavaScript code generation.
* This is used when dumping session data into local storage for use by the client script.
*
* @param array $phpArray The associative array whose data will be converted into JavaScript local storage setItem calls.
* @return string Returns a script tag with JavaScript code. If the input is not a valid single-level associative array,
* it returns a script with an error logged to the console.
*/
function generateScriptData(array $phpArray):string {
// Check if the array is associative and single-level
if (is_array($phpArray) && count($phpArray) > 0 && count(array_filter(array_keys($phpArray), 'is_string')) === count($phpArray)) {
// Generate JavaScript code to save each array element to local storage
$out = "<script>";
foreach ($phpArray as $key => $value) {
$escapedKey = addslashes(strval($key)); // Escape special characters in the key
$escapedValue = addslashes(strval($value)); // Escape special characters in the value
$out .= "localStorage.setItem('$escapedKey', '$escapedValue');";
}
$out.= "</script>";
} else {
$out = "<script>console.error('Invalid PHP array. Must be single-level and associative.');</script>";
}
return $out;
}

66
lib/sitemap.php Normal file
View File

@@ -0,0 +1,66 @@
<?php
require_once "lib/account.php";
/**
* Generates an XML sitemap as a string for a website, considering only pages that the current session
* has sufficient privileges to access. It scans directories specified in the router configuration
* for .html and .php files, and constructs a sitemap entry for each accessible page based on their
* required permission levels. This function returns the sitemap as a string and
* sets the appropriate header for XML content.
*
* @global array $routerConfig The global configuration array containing directory paths and default settings.
* @return string The XML sitemap content, properly formatted in accordance with the sitemap protocol.
*/
function generateSitemap(): string{
global $routerConfig;
$site_dirs = array_diff(scandir($routerConfig["page_dir"]), array('.', '..'));
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http";
$domain = $_SERVER['HTTP_HOST'];
$subdomain = ""; // You may need to modify this based on your subdomain logic
$sitemap = '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL;
$sitemap .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . PHP_EOL;
foreach ($site_dirs as $site_dir) {
$pages_dir = array_diff(scandir($routerConfig["page_dir"] . $site_dir), array('.', '..'));
foreach ($pages_dir as $page_file) {
$page_file_tmp = explode(".", $page_file);
$page_basename = $page_file_tmp[0];
$page_file_path = $routerConfig["page_dir"] . $site_dir . "/" . $page_file;
$page_location = $protocol . "://" . $subdomain . $domain . "/" . $site_dir . "/" . $page_basename;
if ($page_file_tmp[1] == "html") {
$page_tmp = file_get_contents($page_file_path);
$pageMetadata = parsePageTag($page_tmp);
if (!empty($pageMetadata["parameters"]["minimal_permission_level"])) {
$page_required_permission = intval($pageMetadata["parameters"]["minimal_permission_level"]);
} else {
$page_required_permission = $routerConfig["page"]["default_permissions"];
}
} elseif ($page_file_tmp[1] == "php") {
$pageMetadata = getDynamicMetadata($page_file_path);
$page_required_permission = getDynamicPermission($pageMetadata);
} else {
$page_required_permission = $routerConfig["page"]["default_permissions"];
}
// Check if the user is authorized to access the page
if ($page_required_permission <= $_SESSION["privilege_level"]) {
$sitemap .= '<url>' . PHP_EOL;
$sitemap .= '<loc>' . htmlspecialchars($page_location) . '</loc>' . PHP_EOL;
// You can add other optional tags like lastmod, changefreq, priority here if needed
$sitemap .= '</url>' . PHP_EOL;
}
}
}
$sitemap .= '</urlset>' . PHP_EOL;
header('Content-type: application/xml');
return $sitemap;
}

21
lib/survey.php Normal file
View File

@@ -0,0 +1,21 @@
<?php
require_once "lib/account.php";
function submitSurvey(int $satisfaction, int $functionality, int $content, string $comment): array
{
global $mysqli;
$output = ["Status" => "Fail", "Opinion" => "Ignored unsuccessfully!"];
if (isLoggedIn()
&& $satisfaction >= 1 && $satisfaction <= 5
&& $functionality >= 1 && $functionality <= 5
&& $content >= 1 && $content <= 5
&& !empty($comment)) {
$stmtMemeAdd = $mysqli->prepare('INSERT INTO Survey (AuthorID, Satisfaction, Functionality, Content, Comment) VALUES (?, ?, ?, ?, ?)');
$stmtMemeAdd->bind_param('iiiis', $_SESSION['ID'], $satisfaction, $functionality, $content, htmlspecialchars($comment));
if ($stmtMemeAdd->execute() && $stmtMemeAdd->affected_rows > 0) {
$output["Status"] = "Success";
$output["Opinion"] = "Ignored successfully!";
}
}
return $output;
}

350
lib/upload.php Normal file
View File

@@ -0,0 +1,350 @@
<?php
/**
* Sanitizes user input to be used as a part of a file path to prevent security vulnerabilities such as directory traversal.
*
* @param string $userInput The input string to be sanitized.
* @return string Returns a safe string where only alphanumeric characters, underscores, and hyphens are retained.
* Special characters and potential path traversal payloads are removed or replaced.
*/
function makePathSafe(string $userInput): string
{
// Keep only alphanumeric characters, underscores, and hyphens
$safeString = preg_replace('/[^\w\-]/', '', $userInput);
// Ensure no path traversal
$safeString = str_replace('..', '_', $safeString);
// Trim leading/trailing underscores
$safeString = trim($safeString, '_');
// Replace directory separator characters with underscores
$safeString = str_replace(['/', '\\'], '_', $safeString);
// Limit length for safety
return substr($safeString, 0, 255);
}
/**
* Automatically rotates an image based on its EXIF data to adjust its orientation.
*
* @param Imagick $imagick An Imagick object representing the image to be rotated.
* @return void
*/
function autoRotateImage(Imagick $imagick): void {
// Get the current orientation of the image
try {
$orientation = $imagick->getImageOrientation();
switch ($orientation) {
case Imagick::ORIENTATION_BOTTOMRIGHT: // upside down
$imagick->rotateimage("#000", 180); // rotate 180 degrees
break;
case Imagick::ORIENTATION_RIGHTTOP: // 90 degrees CW
$imagick->rotateimage("#000", 90); // rotate 90 degrees CW
break;
case Imagick::ORIENTATION_LEFTBOTTOM: // 90 degrees CCW
$imagick->rotateimage("#000", -90); // rotate 90 degrees CCW
break;
}
// Reset orientation to normal after the correction
$imagick->setImageOrientation(Imagick::ORIENTATION_TOPLEFT);
} catch (ImagickException) {
}
}
/**
* Processes the global $_FILES array to normalize the structure and filter out any files with errors.
*
* @return array Returns an array of files that are ready for further processing, structured uniformly.
*/
function getIncomingFiles(): array
{
$files = $_FILES;
$files2 = [];
foreach ($files as $infoArr) {
$filesByInput = [];
foreach ($infoArr as $key => $valueArr) {
if (is_array($valueArr)) { // file input "multiple"
foreach ($valueArr as $i => $value) {
$filesByInput[$i][$key] = $value;
}
} else { // -> string, normal file input
$filesByInput[] = $infoArr;
break;
}
}
$files2 = array_merge($files2, $filesByInput);
}
$files3 = [];
foreach ($files2 as $file) { // let's filter empty & errors
if (!$file['error']) $files3[] = $file;
}
return $files3;
}
/**
* Saves file metadata in the database.
* This creates the only record of the file existing.
*
* @param string $filePath The path where the file is stored.
* @param string $fileType The MIME type of the file.
* @param int $width The width of the image file.
* @param int $height The height of the image file.
* @return bool Returns true if the file metadata was successfully saved to the database, false otherwise.
*/
function saveUploadedFileInDatabase(string $filePath, string $fileType, int $width, int $height): bool
{
global $mysqli;
$stmt = $mysqli->prepare("INSERT INTO Files (Path, Type, UploadedBy, UploadedAt, Width, Height) VALUES (?, ?, ?, NOW(), ?, ?)");
$stmt->bind_param("ssiii", $filePath, $fileType, $_SESSION["ID"], $width, $height);
$stmt->execute();
$stat = $stmt->affected_rows > 0;
$stmt->close();
return $stat;
}
/**
* Handles the uploading process of an image, including its conversion to webp format, rotation based on orientation, and saving.
*
* @param string $inFile The temporary file path of the uploaded file.
* @param string $outFile The target file path where the processed image should be saved.
* @return bool Returns true if the file was successfully processed and saved, false otherwise.
*/
function doImageUpload(string $inFile, string $outFile): bool
{
// Create Imagick object
$width = 0;
$height = 0;
try {
$imagick = new Imagick($inFile);
$imagick->setImageFormat('webp');
autoRotateImage($imagick);
$imagick->stripImage();
$imagick->writeImage($outFile);
$width = $imagick->getImageWidth();
$height = $imagick->getImageHeight();
$imagick->destroy();
} catch (ImagickException) {
}
// Check if the reencoding was successful, if yes, save into the database.
if (file_exists($outFile)) {
return saveUploadedFileInDatabase($outFile, 'image/webp', $width, $height);
} else {
return false;
}
}
/**
* Retrieves a list of files from the database, optionally filtered to include only files uploaded by the current user.
* Access to an unfiltered list (files of all users) is only available to moderators.
*
* @param bool $onlyMine Whether to retrieve only files uploaded by the logged-in user. If false, files from all users are returned if the user is a moderator.
* @return array Returns an array containing file data along with a status message.
*/
function listFiles(bool $onlyMine = true): array
{
$output = ["Status" => "Success", "Files" => []];
require_once "lib/account.php";
if (isLoggedIn()) {
global $mysqli;
if (!$onlyMine && !isModerator()) {
$onlyMine = true;
}
$query = "SELECT Files.ID, Files.Path, Files.Type, Files.UploadedAt, Files.UploadedBy, Users.Nickname FROM Files INNER JOIN Users ON Files.UploadedBy = Users.ID ORDER BY UploadedAt DESC";
if ($onlyMine) {
$query .= " WHERE UploadedBy = ?";
}
$stmt = $mysqli->prepare($query);
if ($onlyMine) {
$stmt->bind_param("i", $_SESSION["ID"]);
}
$id = 0;
$path = "";
$type = "";
$uploadedAt = "";
$uploadedByID = 0;
$uploadedBy = "";
$stmt->bind_result($id, $path, $type, $uploadedAt, $uploadedByID, $uploadedBy);
$stmt->execute();
$files = array();
// Fetch the results into the bound variables
while ($stmt->fetch()) {
$files[] = [
'ID' => $id,
'Path' => $path,
'Type' => $type,
'UploadedAt' => $uploadedAt,
'UploadedByID' => $uploadedByID,
'UploadedBy' => $uploadedBy,
];
}
// Check if any results were fetched
$output["Files"] = $files;
$stmt->close();
}
return $output;
}
/**
* Processes incoming files from the $_FILES global (after processed by getIncomingFiles), performs checks, and attempts to upload a file based on its type.
* Currently only supports valid image files.
*
* @return array Returns an array indicating the success or failure ('Status' key) of the file processing operations.
*/
function parseIncomingFiles(): array
{
$incomingFiles = getIncomingFiles();
$success = true;
foreach ($incomingFiles as $incomingFile) {
if ($incomingFile["error"] == 0 && is_file($incomingFile["tmp_name"])) {
$type = explode("/", $incomingFile["type"]);
if ($type[0] == "image") {
$imgFname = pathinfo($incomingFile["name"], PATHINFO_FILENAME);
$uploadPath = getUploadPath("image", $imgFname);
if (!empty($uploadPath)) {
if (!doImageUpload($incomingFile["tmp_name"], $uploadPath)) {
$success = false;
}
} else {
$success = false;
}
}
}
}
$output = ["Status" => "Fail"];
if ($success) {
$output["Status"] = "Success";
}
return $output;
}
/**
* Generates a file path for uploading based on the type of the file, the ID of the uploader and the date and time of uploading.
*
* @param string $type The type of the file, typically used to categorize the file.
* @param string $filename The base name of the file, used in generating the final path.
* @return string Returns the full path for storing the file or an empty string if the type is not recognized.
*/
function getUploadPath(string $type = "unknown", string $filename = "hehe"): string
{
$type = makePathSafe($type);
$id = makePathSafe($_SESSION["ID"]);
$date = makePathSafe(date("YmdHis"));
$filename = makePathSafe($filename);
$extension = match ($type) {
'image' => 'webp',
default => 'dummy',
};
if ($extension != "dummy") {
$basepath = "uploads/$type/$id/$date";
mkdir($basepath, 755, true);
return $basepath . "/$filename.$extension";
} else {
return "";
}
}
/**
* Checks if a file with a given ID exists in the database and does permission checks.
* Access is granted to only the user's files, in order to access all files the onlyMine parameter
* must be false and the user must be a moderator.
*
* @param int $fileId The ID of the file to check.
* @param bool $onlyMine Whether to limit the search to files uploaded by the logged-in user.
* @return bool|string Returns the path of the file if it exists and meets the criteria, false otherwise.
*/
function fileExists(int $fileId, bool $onlyMine = true): bool|string
{
if (!$fileId) {
return false;
}
global $mysqli;
if (!$onlyMine && !isModerator()) {
$onlyMine = true;
}
$query = 'SELECT ID, Path FROM Files WHERE ID = ?' . ($onlyMine ? ' AND UploadedBy = ?' : '');
$stmtfileexists = $mysqli->prepare($query);
if ($onlyMine) {
$stmtfileexists->bind_param('ii', $fileId, $_SESSION['ID']);
} else {
$stmtfileexists->bind_param('i', $fileId);
}
$filePath = "";
$id = null;
$stmtfileexists->bind_result($id, $filePath);
$stmtfileexists->execute();
$stmtfileexists->fetch();
if ($id != null) {
return $filePath;
} else {
return false;
}
}
/**
* Adds a file to a specified group, if the user created the group or creates a new group if
* a group with a specified ID does not exist yet
*
* @param int $groupId The ID of the group to which the file should be added.
* @param int $fileId The ID of the file to add to the group.
* @return array Returns an associative array with a 'Status' key indicating success or failure.
*/
function addToGroup(int $groupId, int $fileId): array
{
$output = ["Status" => "Fail"];
if (!$groupId || !$fileId) {
return $output;
}
global $mysqli;
$stmtcheck = $mysqli->prepare('SELECT ID FROM FileGroups WHERE CreatorID != ? AND ID = ?');
$stmtcheck->bind_param('ii', $_SESSION['ID'], $groupId);
$stmtcheck->execute();
if ($stmtcheck->affected_rows == 0) {
if (fileExists($fileId, false)) {
$stmtadd = $mysqli->prepare('INSERT INTO FileGroups (FileID, CreatorID, ID) VALUES (?, ?, ?)');
$stmtadd->bind_param('iii', $fileId, $_SESSION['ID'], $groupId);
$stmtadd->execute();
if ($stmtadd->affected_rows > 0) {
$output["Status"] = "Success";
}
}
}
return $output;
}
/**
* Deletes a file entry from the database and the file system, a user can only delete his own files,
* except when he is a moderator, in that case he can delete all files.
*
* @param int $fileID The ID of the file to be deleted.
* @return array Returns an array with a 'Status' key indicating the success or failure of the deletion operation.
*/
function deleteFile(int $fileID): array
{
global $mysqli;
$out = ["Status" => "Fail"];
if (isLoggedIn()) {
$file_location = fileExists($fileID, !isModerator());
$query = !isModerator() ? 'DELETE FROM Files WHERE ID = ? AND UploadedBy = ?' : 'DELETE FROM Files WHERE ID = ?';
$stmtDelete = $mysqli->prepare($query);
if (!isModerator()) {
$stmtDelete->bind_param('ii', $fileID, $_SESSION['ID']);
} else {
$stmtDelete->bind_param('i', $fileID);
}
$stmtDelete->execute();
if ($file_location) {
if (unlink($file_location) && $stmtDelete->affected_rows > 0) {
$out['Status'] = 'Success';
}
}
}
return $out;
}

8
pages/account/files.html Normal file
View File

@@ -0,0 +1,8 @@
<!--suppress HtmlUnknownTag, HtmlUnknownTag -->
<page minimal_permission_level="2" secret="yes" page_title="Súbory"></page>
<div id="filelist"></div>
<form id="uploadForm" enctype="multipart/form-data" method="POST">
<label for="fileInput">Send this file: </label>
<input name="userfile" type="file" id="fileInput" multiple />
<input type="button" value="Send File" onclick="uploadFile()" />
</form>

22
pages/account/index.php Normal file
View File

@@ -0,0 +1,22 @@
<?php
require_once "lib/router.php";
require_once "lib/account.php";
global $routerConfig;
if (isLoggedIn()) {
$output = file_get_contents($routerConfig["template_dir"] . "dashboard.html");
} else {
$output = file_get_contents($routerConfig["template_dir"] . "login.html");
}
return [
"output" => $output,
"parameters" =>
[
"minimal_permission_level" => 1,
"secret" => "no",
"page_title" => "Účet"
]
];

View File

@@ -0,0 +1,21 @@
<?php
require_once "lib/router.php";
require_once "lib/account.php";
global $routerConfig;
$output = file_get_contents($routerConfig["template_dir"] . "userActions.html");
if (isUserAdmin()) {
$output .= file_get_contents($routerConfig["template_dir"] . "adminActions.html");
}
return [
"output" => $output,
"parameters" =>
[
"minimal_permission_level" => 2,
"secret" => "no",
"page_title" => "Nastavenia"
]
];

47
pages/account/survey.html Normal file
View File

@@ -0,0 +1,47 @@
<!--suppress HtmlUnknownTag, HtmlUnknownTag -->
<page minimal_permission_level="2" secret="yes" page_title="Prieskum"></page>
<form id="surveyForm">
<h3>Spokojnosť so stránkou:</h3>
<input type="radio" name="satisfaction" value="5" id="spokojnost_super">
<label for="spokojnost_super">Super</label>
<input type="radio" name="satisfaction" value="4" id="spokojnost_dobre">
<label for="spokojnost_dobre">Dobre</label>
<input type="radio" name="satisfaction" value="3" id="spokojnost_da_sa">
<label for="spokojnost_da_sa">Dá sa</label>
<input type="radio" name="satisfaction" value="2" id="spokojnost_zle">
<label for="spokojnost_zle">Zle</label>
<input type="radio" name="satisfaction" value="1" id="spokojnost_nanic">
<label for="spokojnost_nanic">Nanič</label>
<br>
<br>
<h3>Funkčnosť stránky:</h3>
<input type="radio" name="functionality" value="5" id="funkcnost_super">
<label for="funkcnost_super">Super</label>
<input type="radio" name="functionality" value="4" id="funkcnost_dobre">
<label for="funkcnost_dobre">Dobre</label>
<input type="radio" name="functionality" value="3" id="funkcnost_da_sa">
<label for="funkcnost_da_sa">Dá sa</label>
<input type="radio" name="functionality" value="2" id="funkcnost_zle">
<label for="funkcnost_zle">Zle</label>
<input type="radio" name="functionality" value="1" id="funkcnost_nanic">
<label for="funkcnost_nanic">Nanič</label>
<br>
<br>
<h3>Obsah stránky:</h3>
<input type="radio" name="content" value="5" id="content_super">
<label for="content_super">Super</label>
<input type="radio" name="content" value="4" id="content_dobre">
<label for="content_dobre">Dobre</label>
<input type="radio" name="content" value="3" id="content_da_sa">
<label for="content_da_sa">Dá sa</label>
<input type="radio" name="content" value="2" id="content_zle">
<label for="content_zle">Zle</label>
<input type="radio" name="content" value="1" id="content_nanic">
<label for="content_nanic">Nanič</label>
<br>
<br>
<textarea name="comment" placeholder="Komentár" cols="80" rows="10" required></textarea>
<br>
<br>
<button type="button" onclick="surveySubmit()">Odoslať</button>
</form>

View File

@@ -1,40 +0,0 @@
<?php
require_once "lib/routing.php";
function render()
{
global $routerConfig;
$diddoAjax = true;
switch ($_POST["action"]) {
case "login":
doLogin();
break;
case "register":
doRegister();
break;
case "logout":
doLogout();
break;
default:
$diddoAjax = false;
break;
}
if ($diddoAjax) {
exit();
}
ob_start();
if ($_SESSION["ID"] > 0) {
$account_template = file_get_contents($routerConfig["template_dir"] . "account.html");
echo $account_template;
} else {
$login_template = file_get_contents($routerConfig["template_dir"] . "login.html");
echo $login_template;
}
return ob_get_clean();
}

View File

@@ -1 +0,0 @@
<h1>Vitaj na tejto úžasnej stránke</h1>

20
pages/home/index.html Normal file
View File

@@ -0,0 +1,20 @@
<!--suppress HtmlUnknownTag, HtmlUnknownTag -->
<page minimal_permission_level="1" secret="no" page_title="Domov"></page>
<header>
<h1 class="title">Vitaj, na tejto úžasnej stránke</h1>
<p>Neoficiálna študentská stránka pre Adlerku</p>
</header>
<hr>
<div class="sliderm" id="exampe-slider">
<div class="sliderm__slider">
<div class="sliderm__slides">
<div class="sliderm__slide">1</div>
<div class="sliderm__slide">2</div>
<div class="sliderm__slide">3</div>
<div class="sliderm__slide">4</div>
<div class="sliderm__slide">5</div>
</div>
</div>
</div>

View File

@@ -1,5 +0,0 @@
<header>
<h1 class="title">Adlerka Memes</h1>
<p>Skoro ako <a href="https://reddit.com/r/adlerka" target="_blank">r/adlerka</a> - ale lepšie.</p>
<hr>
</header>

18
pages/memes/index.php Normal file
View File

@@ -0,0 +1,18 @@
<?php
require_once "lib/router.php";
require_once "lib/meme.php";
global $routerConfig;
$output = renderMemeGallery();
return [
"output" => $output,
"parameters" =>
[
"minimal_permission_level" => 1,
"secret" => "no",
"page_title" => "Galéria"
]
];

View File

@@ -1 +0,0 @@
<h1>Vitaj na oficiálnej stránke Memeov o AdlerkaSMP</h1>

44
pages/news/index.php Normal file
View File

@@ -0,0 +1,44 @@
<?php
require_once "lib/router.php";
require_once "lib/newsarticle.php";
global $routerConfig;
$output = file_get_contents($routerConfig["template_dir"] . "newsArticles.html");
$articles_out = "";
$articles = [];
$articles_tmp = getNewsArticles();
if($articles_tmp['Status'] == "Success"){
$articles = $articles_tmp["Articles"];
}
$articleTemplate = file_get_contents($routerConfig["template_dir"] . "newsArticle.html");
$output = str_replace("__TEMPLATE_FOR_ARTICLE_CONTENT__", $articleTemplate, $output);
foreach ($articles as $article){
$articleTitle = htmlspecialchars($article["Title"]);
$articleBody = htmlspecialchars($article["Body"]);
//$articleFileList = $article["FileList"];
//$articleWrittenBy = $article["WrittenBy"];
$articleWrittenAt = htmlspecialchars($article["WrittenAt"]);
$articleWrittenByName = htmlspecialchars($article["WrittenByName"]);
$articleTemplate = str_replace("__TEMPLATE_ARTICLE_TITLE__", $articleTitle, $articleTemplate);
$articleTemplate = str_replace("__TEMPLATE_ARTICLE_AUTHOR__", $articleWrittenByName, $articleTemplate);
$articleTemplate = str_replace("__TEMPLATE_ARTICLE_DATE__", $articleWrittenAt, $articleTemplate);
$articleTemplate = str_replace("__TEMPLATE_ARTICLE_BODY__", $articleBody, $articleTemplate);
$articles_out .= $articleTemplate;
}
$output = str_replace("__TEMPLATE__ARTICLES_HERE__", $articles_out, $output);
return [
"output" => $output,
"parameters" =>
[
"minimal_permission_level" => 1,
"secret" => "no",
"page_title" => "Novinky"
]
];

8
pages/notes/index.html Normal file
View File

@@ -0,0 +1,8 @@
<!--suppress HtmlUnknownTag, HtmlUnknownTag -->
<page minimal_permission_level="2" secret="no" page_title="Zošit"></page>
<header>
<h1 class="title">Adlerka Zošit</h1>
</header>
<hr>
<h2>Čoskoro bude v prevádzke</h2>
<h3>Nájdete(a pridáte) tu poznámky a úlohy zo školy</h3>

5
pages/rozvrh/index.html Normal file
View File

@@ -0,0 +1,5 @@
<page minimal_permission_level="2" secret="no" page_title="INFO"></page>
<header>
<h1>Toto sú rozvrhy niektorých Adlerákov</h1>
<h2>Zatiaľ hardcoded, potom dorobíme funkcionalitu zbierania dát z Edupage</h2>
</header>

View File

@@ -0,0 +1,62 @@
<page minimal_permission_level="2" secret="no" page_title="1.C 2.skupina"></page>
<header>
<h1>Rozvrh 1.C 2.Skupina</h1>
</header>
<main>
<table class="rozvrh">
<tbody>
<tr>
<th>0 (7:10 - 7:55)</th>
<th>1 (8:00 - 8:45)</th>
<th>2 (8:50 - 9:35)</th>
<th>3 (9:45 - 10:30)</th>
<th>4 (10:50 - 11:35)</th>
<th>5 (11:45 - 12:30)</th>
<th>6 (12:40 - 13:25)</th>
<th>7 (13:30 - 14:15)</th>
</tr>
<tr>
<td></td>
<td>MAT</td>
<td>ELK</td>
<td>SJL</td>
<td colspan="2">ZER</td>
<td>ANJ</td>
<td>MAT</td>
</tr>
<tr>
<td colspan="3">PRX</td>
<td>SJL</td>
<td colspan="2">INF</td>
<td>ELK</td>
</tr>
<tr>
<td></td>
<td>PRO</td>
<td>SJL</td>
<td>FYZ</td>
<td>ANJ</td>
<td>MAT</td>
<td>ELK</td>
<td>TSV</td>
</tr>
<tr>
<td colspan="2"></td>
<td colspan="2">PRO</td>
<td>ANJ</td>
<td>TDK</td>
<td>MAT</td>
<td>ETV</td>
</tr>
<tr>
<td></td>
<td>TDK</td>
<td>OBN</td>
<td>TSV</td>
<td>FYZ</td>
<td>PRO</td>
<td>DEJ</td>
</tr>
</tbody>
</table>
</main>

View File

@@ -0,0 +1,62 @@
<page minimal_permission_level="2" secret="no" page_title="1.C 1.skupina"></page>
<header>
<h1>Rozvrh 1.C 1.Skupina</h1>
</header>
<main>
<table class="rozvrh">
<tbody>
<tr>
<th>0 (7:10 - 7:55)</th>
<th>1 (8:00 - 8:45)</th>
<th>2 (8:50 - 9:35)</th>
<th>3 (9:45 - 10:30)</th>
<th>4 (10:50 - 11:35)</th>
<th>5 (11:45 - 12:30)</th>
<th>6 (12:40 - 13:25)</th>
<th>7 (13:30 - 14:15)</th>
</tr>
<tr>
<td></td>
<td>MAT</td>
<td>ELK</td>
<td>SJL</td>
<td colspan="2">ZER</td>
<td>MAT</td>
<td>ANJ</td>
</tr>
<tr>
<td colspan="3">PRX</td>
<td>SJL</td>
<td colspan="2">INF</td>
<td>ELK</td>
</tr>
<tr>
<td></td>
<td>PRO</td>
<td>SJL</td>
<td>TSV</td>
<td>FYZ</td>
<td>ANJ</td>
<td>MAT</td>
<td>ELK</td>
</tr>
<tr>
<td colspan="2"></td>
<td colspan="2">PRO</td>
<td>TDK</td>
<td>TSV</td>
<td>MAT</td>
<td>ETV</td>
</tr>
<tr>
<td></td>
<td>ANJ</td>
<td>OBN</td>
<td>TDK</td>
<td>FYZ</td>
<td>PRO</td>
<td>DEJ</td>
</tr>
</tbody>
</table>
</main>

View File

@@ -0,0 +1,64 @@
<page minimal_permission_level="2" secret="no" page_title="1.D 1.skupina"></page>
<header>
<h1>Rozvrh 1.D 1.Skupina</h1>
</header>
<main>
<table class="rozvrh">
<tbody>
<tr>
<th>0 (7:10 - 7:55)</th>
<th>1 (8:00 - 8:45)</th>
<th>2 (8:50 - 9:35)</th>
<th>3 (9:45 - 10:30)</th>
<th>4 (10:50 - 11:35)</th>
<th>5 (11:45 - 12:30)</th>
<th>6 (12:40 - 13:25)</th>
<th>7 (13:30 - 14:15)</th>
</tr>
<tr>
<td></td>
<td>TSV</td>
<td colspan="2">PRO</td>
<td>MAT</td>
<td>TDK</td>
<td>PRO</td>
<td>SJL</td>
</tr>
<tr>
<td></td>
<td>FYZ</td>
<td>TDK</td>
<td>MAT</td>
<td>ELK</td>
<td>OBN</td>
<td>ANJ</td>
<td>ETV</td>
</tr>
<tr>
<td></td>
<td>ELK</td>
<td>MAT</td>
<td>SJL</td>
<td>ANJ</td>
<td>ELK</td>
<td colspan="2">INF</td>
</tr>
<tr>
<td colspan="2"></td>
<td colspan="2">ZER</td>
<td>MAT</td>
<td>FYZ</td>
<td>DEJ</td>
<td>TSV</td>
</tr>
<tr>
<td></td>
<td>SJL</td>
<td>ANJ</td>
<td>PRO</td>
<td colspan="3">PRX</td>
</tr>
</tbody>
</table>
</main>

View File

@@ -1 +0,0 @@
<h1>Vitaj na oficiálnej stránke pre AdlerkaSMP</h1>

33
pages/smp/index.html Normal file
View File

@@ -0,0 +1,33 @@
<!--suppress HtmlUnknownTag, HtmlUnknownTag -->
<page minimal_permission_level="1" secret="no" page_title="Domov"></page>
<header>
<h1 class="title">Vitaj, na oficiálnej AdlerkaSMP stránke</h1>
<p>Najlepší <a href="https://minecraft.net" style="text-decoration: underline; color: #fff;" target="_blank">Minecraft®™</a>
server na Adlerke</p>
</header>
<hr>
<main>
<div class="wrapper feature-list">
<h2>Čo môžeš očakávať od AdlerkaSMP:</h2>
<ul class="feature-list-ul">
<li>Módovaný gameplay bez nutnosti sťahovať módy (niektoré módy však vylepšia zážitok)</li>
<li>Jednoduché pripojenie, hostname je <strong>adlerka.top</strong></li>
<li>
<strong>Super admini:</strong>
<ul>
<li>Starajú sa o server</li>
<li>Udržiavajú ho bezpečným miestom</li>
<li>
<strong>Zoznam adminov:</strong>
<ul>
<li>BRNSystems</li>
<li>YeahAkis_</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</main>

View File

@@ -1 +0,0 @@
<h1>Vitaj na oficiálnej stránke Informácii o AdlerkaSMP</h1>

61
pages/smp/mods.html Normal file
View File

@@ -0,0 +1,61 @@
<!--suppress HtmlUnknownTag, HtmlUnknownTag -->
<page minimal_permission_level="1" secret="no" page_title="Modlist"></page>
<main>
<h2>Launcher:</h2>
<a href="https://prismlauncher.org/download/">Prism Launcher <b>(ODPORÚČANÝ)</b></a><br>
<h2>Módy pre klienta:</h2>
<a href="/assets/adlerka_client.mrpack">Komplet ako MrPack <b>(ODPORÚČANÉ)</b></a><br>
<h3>Vizuálne:</h3>
<ol>
<li><a href="https://cdn.modrinth.com/data/zV5r3pPn/versions/kJmEO0xO/skinlayers3d-fabric-1.6.2-mc1.20.4.jar">3D Skin Layers</a></li>
<li><a href="https://cdn.modrinth.com/data/wdLuzzEP/versions/rNWb9ncY/Gamma-Utils-1.7.19-mc1.20.4.jar">Gamma Utils</a></li>
<li><a href="https://cdn.modrinth.com/data/w7ThoJFB/versions/BalILUb7/Zoomify-2.13.2.jar">Zoomify</a></li>
<li><a href="https://cdn.modrinth.com/data/M08ruV16/versions/jGGumR4a/bobby-5.1.0%2Bmc1.20.4.jar">Bobby</a></li>
<li><a href="https://cdn.modrinth.com/data/1IjD5062/versions/JXhQlDZl/continuity-3.0.0-beta.4%2B1.20.2.jar">Continuity</a></li>
<li><a href="https://cdn.modrinth.com/data/ZcR9weSm/versions/UrOG4IKT/dynamiccrosshair-7.7%2B1.20.4-fabric.jar">Dynamic Crosshair</a></li>
<li><a href="https://cdn.modrinth.com/data/rUgZvGzi/versions/AqXSvu6M/eating-animation-1.20%2B1.9.61.jar">Eating Animation</a></li>
<li><a href="https://cdn.modrinth.com/data/WhbRG4iK/versions/PNG0Dp43/fallingleaves-1.15.5%2B1.20.1.jar">Falling Leaves</a></li>
<li><a href="https://cdn.modrinth.com/data/YL57xq9U/versions/kGdJ11Rt/iris-mc1.20.4-1.6.17.jar">Iris</a></li>
<li><a href="https://cdn.modrinth.com/data/yBW8D80W/versions/mrQ8ZiyU/lambdynamiclights-2.3.4%2B1.20.4.jar">Lambdynamiclights</a></li>
<li><a href="https://cdn.modrinth.com/data/MPCX6s5C/versions/ZLjUeuU8/notenoughanimations-fabric-1.7.1-mc1.20.4.jar">Not Enough Animations</a></li>
</ol>
<h3>Performance:</h3>
<ol>
<li><a href="https://cdn.modrinth.com/data/NNAgCjsB/versions/7JR5qJ8f/entityculling-fabric-1.6.4-mc1.20.4.jar">Entity Culling</a></li>
<li><a href="https://cdn.modrinth.com/data/uXXizFIs/versions/pguEMpy9/ferritecore-6.0.3-fabric.jar">FerriteCore</a></li>
<li><a href="https://cdn.modrinth.com/data/Orvt0mRa/versions/Aouse6P7/indium-1.0.30%2Bmc1.20.4.jar">Indium</a></li>
<li><a href="https://cdn.modrinth.com/data/fQEb0iXm/versions/bRcuOnao/krypton-0.2.6.jar">Krypton</a></li>
<li><a href="https://cdn.modrinth.com/data/gvQqBUqZ/versions/nMhjKWVE/lithium-fabric-mc1.20.4-0.12.1.jar">Lithium</a></li>
<li><a href="https://cdn.modrinth.com/data/Bh37bMuy/versions/fkLiGoHs/reeses_sodium_options-1.7.2%2Bmc1.20.4-build.102.jar">Reese's Sodium Options</a></li>
<li><a href="https://cdn.modrinth.com/data/AANobbMI/versions/4GyXKCLd/sodium-fabric-0.5.8%2Bmc1.20.4.jar">Sodium</a></li>
<li><a href="https://cdn.modrinth.com/data/PtjYWJkn/versions/M0ndiav7/sodium-extra-0.5.4%2Bmc1.20.4-build.116.jar">Sodium-extra</a></li>
</ol>
<h3>Utility:</h3>
<ol>
<li><a href="https://cdn.modrinth.com/data/DFqQfIBR/versions/rGQZ2yBQ/CraftPresence-2.3.5%2B1.20.4.jar">CraftPresence</a></li>
<li><a href="https://cdn.modrinth.com/data/8shC1gFX/versions/AkivIlyi/BetterF3-9.0.2-Fabric-1.20.4.jar">BetterF3</a></li>
<li><a href="https://cdn.modrinth.com/data/nvQzSEkH/versions/fNHCa6bl/Jade-1.20.4-fabric-13.3.1.jar">Jade</a></li>
<li><a href="https://cdn.modrinth.com/data/aC3cM3Vq/versions/m0Dd8Cjy/MouseTweaks-fabric-mc1.20-2.25.jar">Mouse Tweaks</a></li>
<li><a href="https://cdn.modrinth.com/data/qQyHxfxd/versions/tfv6A4l5/NoChatReports-FABRIC-1.20.4-v2.5.0.jar">No Chat Reports</a></li>
<li><a href="https://cdn.modrinth.com/data/nfn13YXA/versions/Jhw0fDTs/RoughlyEnoughItems-14.0.688-fabric.jar">Roughly Enough Items</a></li>
<li><a href="https://cdn.modrinth.com/data/V8XJ8f5f/versions/q9eTEsvC/RoughlyEnoughProfessions-fabric-1.20.4-2.2.0.jar">Roughly Enough Professions</a></li>
<li><a href="https://cdn.modrinth.com/data/NcUtCpym/versions/2lbtkEPK/XaerosWorldMap_1.38.1_Fabric_1.20.4.jar">Xaero's WorldMap</a></li>
<li><a href="https://cdn.modrinth.com/data/1bokaNcj/versions/N5jBKzC0/Xaeros_Minimap_24.0.3_Fabric_1.20.4.jar">Xaero's_Minimap</a></li>
<li><a href="https://cdn.modrinth.com/data/EsAfCjCV/versions/pmFyu3Sz/appleskin-fabric-mc1.20.3-2.5.1.jar">Appleskin</a></li>
<li><a href="https://cdn.modrinth.com/data/mOgUt4GM/versions/sjtVVlsA/modmenu-9.0.0.jar">Modmenu</a></li>
<li><a href="https://cdn.modrinth.com/data/Nv2fQJo5/versions/TGJXKoTQ/replaymod-1.20.4-2.6.15.jar">Replaymod</a></li>
<li><a href="https://cdn.modrinth.com/data/njGhQ4fN/versions/4tIwORrJ/roughly-searchable-2.6.1%2B1.20.4.jar">Roughly Searchable</a></li>
<li><a href="https://cdn.modrinth.com/data/9eGKb6K1/versions/r7e564VW/voicechat-fabric-1.20.4-2.5.11.jar">Simple Voice Chat</a></li>
</ol>
<h3>Dependecies:</h3>
<ol>
<li><a href="https://cdn.modrinth.com/data/lhGA9TYQ/versions/kVjQWX0l/architectury-11.1.17-fabric.jar">Architectury</a></li>
<li><a href="https://cdn.modrinth.com/data/gu7yAYhd/versions/augSR8tA/cc-tweaked-1.20.4-fabric-1.110.0.jar">CC Tweaked</a></li>
<li><a href="https://cdn.modrinth.com/data/9s6osm5g/versions/eBZiZ9NS/cloth-config-13.0.121-fabric.jar">Cloth Config</a></li>
<li><a href="https://cdn.modrinth.com/data/P7dR8mSH/versions/htRy7kbI/fabric-api-0.96.11%2B1.20.4.jar">Fabric Api</a></li>
<li><a href="https://cdn.modrinth.com/data/Ha28R6CL/versions/ZMokinzs/fabric-language-kotlin-1.10.19%2Bkotlin.1.9.23.jar">Fabric Language Kotlin</a></li>
<li><a href="https://cdn.modrinth.com/data/hvFnDODi/versions/0.1.3/lazydfu-0.1.3.jar">Lazydfu</a></li>
<li><a href="https://cdn.modrinth.com/data/xGdtZczs/versions/Kk7rWLSf/polymer-bundled-0.7.7%2B1.20.4.jar">Polymer</a></li>
<li><a href="https://cdn.modrinth.com/data/1eAoo2KR/versions/StXMrAsz/yet-another-config-lib-fabric-3.3.2%2B1.20.4.jar">Yet Another Config Lib</a></li>
</ol>
</main>

7
templates/403.html Normal file
View File

@@ -0,0 +1,7 @@
<div class="wrapper-403 wrapper-40x">
<h2>TY KÁR KAM TO DEŠ</h2>
<h1 class="error-code">403</h1>
<h3><i class="ri-error-warning-line"></i> Našli sme stránku ktorú hľadáš, ale nemáš práva na ňu pristupovať: <span class="error">__TEMPLATE_PAGE_TITLE__</span>. <i class="ri-error-warning-line"></i></h3>
<!--suppress HtmlUnknownTarget -->
<a href="__DEFAULT_LINK__" role="button" class="back"><i class="ri-arrow-left-line"></i> SPÄŤ DOMOV</a>
</div>

View File

@@ -1,6 +1,7 @@
<div class="wrapper-404">
<h2>TY KÁR KAM TO DEŠ</h2>
<div class="wrapper-404 wrapper-40x">
<h2>TY KÁR KAM TO DEŠ</h2>
<h1 class="error-code">404</h1>
<h3><i class="fa-solid fa-circle-exclamation error"></i> Nenašli sme stránku ktorú hladáš: <span class="error">__TEMPLATE_PAGE_NAME__</span>. <i class="fa-solid fa-circle-exclamation error"></i></h3>
<a href="/domov" class="back"><i class="fa-solid fa-arrow-left"></i> SPÄŤ DOMOV</a>
<h3><i class="ri-error-warning-line"></i> Nenašli sme stránku ktorú hľadáš: <span class="error">__TEMPLATE_PAGE_TITLE__</span>. <i class="ri-error-warning-line"></i></h3>
<!--suppress HtmlUnknownTarget -->
<a href="__DEFAULT_LINK__" role="button" class="back"><i class="ri-arrow-left-line"></i> SPÄŤ DOMOV</a>
</div>

7
templates/500.html Normal file
View File

@@ -0,0 +1,7 @@
<div class="wrapper-500">
<h2>Niekto to pobabral</h2>
<h1 class="error-code">500</h1>
<h3><i class="ri-error-warning-line"></i> Nejaký neschopný vývojár nevedel robiť túto stránku. <i class="ri-error-warning-line"></i></h3>
<!--suppress HtmlUnknownTarget -->
<a href="__DEFAULT_LINK__" role="button" class="back"><i class="ri-arrow-left-line"></i> SPÄŤ DOMOV</a>
</div>

View File

@@ -0,0 +1,25 @@
<div id="admin-settings">
<div class="form-container" id="addActivationCodesForm">
<h1>Activation Codes</h1>
<h2>List Activation Codes</h2>
<button type="button" onclick="listActivationCodes()">List Activation Codes</button><br>
<h2>Add Activation Codes</h2>
<label for="activationCodeCount">Activation Code Count:</label>
<input type="text" id="activationCodeCount" name="activationCodeCount" value="1" required><br>
<button type="button" onclick="addActivationCodes()">Add Activation Codes</button>
<br>
<table id="codeListTable"></table>
</div>
<hr>
<div class="form-container" id="listUsersForm">
<h1>List Users</h1>
<button type="button" onclick="listUsers()">List Users</button><br>
<br>
<table id="userListTable"></table>
</div>
</div>
<hr>

5
templates/dashboard.html Normal file
View File

@@ -0,0 +1,5 @@
<div class="dashboard">
<header>
<h1 id="welcomeMsg"></h1>
</header>
</div>

1
templates/footer.html Normal file
View File

@@ -0,0 +1 @@
<p>Toto nie je oficiálna stránka <span id="ye-span">Adlerky</span>, jedná sa o neoficiálnu študentskú stránku</p>

View File

@@ -1,7 +1,28 @@
<form method="post">
<label for="login_email">Email:</label><br>
<input id="login_email" type="email" name="email"><br>
<label for="login_password">Password</label><br>
<input id="login_password" type="password" name="password"><br>
<input type="submit">
</form>
<!-- Centralized Status Message -->
<p id="StatusMessage"></p>
<main class="login-file">
<div class="container" id="container">
<div class="form-container sign-in" id="sign_in_form">
<h1>Login</h1>
<form class="form-content sign-in">
<input type="email" name="email" id="login_email" required placeholder="Email">
<input type="password" name="password" id="login_password" required placeholder="Password">
<button type="button" onclick="login()">Login</button>
</form>
<p onclick="toggleRegister()">Don't have an account</p>
</div>
<div class="form-container sign-up hidden" id="sign_up_form">
<h1>Create Account</h1>
<form class="form-content sign-up">
<input type="text" name="firstName" id="register_firstName" required placeholder="First name">
<input type="text" name="lastName" id="register_lastName" required placeholder="Last name">
<input type="email" name="email" id="register_email" required placeholder="Email">
<input type="password" name="password" id="register_password" required placeholder="Password">
<input type="text" name="activationToken" id="register_activationToken" required
placeholder="Activation Token">
<button type="button">Register</button>
</form>
<p onclick="toggleRegister()">Already have an account</p>
</div>
</div>
</main>

27
templates/meme.html Normal file
View File

@@ -0,0 +1,27 @@
<article class="meme" id="meme___TEMPLATE_MEME_ID__">
<div class="meme_header" id="meme_header___TEMPLATE_MEME_ID__">
<h2 class='meme_title' id="meme_title___TEMPLATE_MEME_ID__">__TEMPLATE_MEME_TITLE__</h2>
<div class="meme_topbar" id="meme_info___TEMPLATE_MEME_ID__">
<div class="meme_voting" id="meme_voting___TEMPLATE_MEME_ID__">
__TEMPLATE_MEME_UPVOTE__
<p class="votes_counter __TEMPLATE_MEME_VOTE_COUNTER_CLASS__"
id="meme_votes_counter___TEMPLATE_MEME_ID__">__TEMPLATE_MEME_VOTES_NUMBER__</p>
__TEMPLATE_MEME_DOWNVOTE__
</div>
<div class="meme_info">
<p class='meme_author' id="meme_author___TEMPLATE_MEME_ID__"><i class="ri-user-line"></i>__TEMPLATE_MEME_AUTHOR__
</p>
<p class='meme_date' id="meme_date___TEMPLATE_MEME_ID__"><i class="ri-calendar-line"></i>__TEMPLATE_MEME_DATE__
</p>
__TEMPLATE_MEME_DELETE_BUTTON__
</div>
</div>
</div>
<div class="meme_body" id="meme_body___TEMPLATE_MEME_ID__">
<a class="meme_link" href="__TEMPLATE_MEME_IMAGE__" download>
<img id="meme_image___TEMPLATE_MEME_ID__" src="__TEMPLATE_MEME_IMAGE__" width="__TEMPLATE_MEME_IMAGE_WIDTH__"
height="__TEMPLATE_MEME_IMAGE_HEIGHT__" alt="meme image"
class="meme_image"></a>
<p class="meme_text" id="meme_text___TEMPLATE_MEME_ID__">__TEMPLATE_MEME_TEXT__</p>
</div>
</article>

15
templates/meme_add.html Normal file
View File

@@ -0,0 +1,15 @@
<div id="memecreatecontainer" class="hidden">
<div id="memecreate">
<label for="meme_title_input">Názov meme-u: </label>
<input type="text" id="meme_title_input" placeholder="Názov meme-u"/>
<label for="meme_text_input">Text meme-u: </label>
<input type="text" id="meme_text_input" placeholder="Text meme-u"/>
<label for="meme_image_input">Obrázok meme-u</label>
<select id="meme_image_input"></select>
<button id="memecreatebutton" onclick="addMeme()"><i class="ri-add-circle-line"></i></button>
<button id="memecreateclose" onclick="togglememecreate()"><i class="ri-close-line"></i></button>
</div>
</div>

View File

@@ -0,0 +1,12 @@
<header>
<h1 class="title">Adlerka Memes</h1>
<p>Skoro, ako <a href="https://reddit.com/r/adlerka" target="_blank">r/adlerka</a>, ale lepšie.</p>
<button id="memecreateopen" onclick="togglememecreate()"><i class="ri-add-circle-line"></i></button>
<hr>
</header>
<main>
<div id="meme_gallery">
__TEMPLATE_MEMES_HERE__
</div>
__TEMPLATE_MEME_ADD__
</main>

View File

@@ -1,6 +1,13 @@
<nav>
<div class="logo"><img alt="Adlerka logo" class="standard-logo" src="https://www.adlerka.sk/wp-content/uploads/2021/09/Logo_text_Adlerka_modro_cerveno_biele-e1652431356820.png" title="Adlerka"></div>
<ul class="navsite_list">
__NAV_PAGES__
</ul>
</nav>
<div class="logo">
<a href="/home/index">
<picture id="standard-logo">
<source media="(min-width:4200px)" srcset="/assets/images/adlerka_256.png">
<source media="(min-width:2100px)" srcset="/assets/images/adlerka_128.png">
<img src="/assets/images/adlerka_64.png" alt="Adlerka logo" style="width:auto;">
</picture>
</a>
</div>
<i class="ri-menu-line" id="toggle_button"></i>
<ul id="navsite_list">
__NAV_PAGES__
</ul>

View File

@@ -0,0 +1,11 @@
<article>
<h2 class='newstitle'>__TEMPLATE_ARTICLE_TITLE__</h2>
<div class="articleinfo">
<p class='newsauthor'><i class="ri-user-line"></i>__TEMPLATE_ARTICLE_AUTHOR__</p>
<p class='newsdate'><i class="ri-calendar-line"></i>__TEMPLATE_ARTICLE_DATE__</p>
</div>
<hr>
<div class='newsbody'>
__TEMPLATE_ARTICLE_BODY__
</div>
</article>

View File

@@ -0,0 +1,24 @@
<header>
<h1 class="title"></h1>
<p>Adlerka študentské news</p>
<button id="articlecreateopen" onclick="togglearticlecreate()"><i class="ri-add-circle-line"></i></button>
</header>
<template data-template-name="article">
__TEMPLATE_FOR_ARTICLE_CONTENT__
</template>
<div id="articleslist">
__TEMPLATE__ARTICLES_HERE__
</div>
<div id="articlecreatecontainer" class="hidden">
<div id="articlecreate">
<input type="text" placeholder="Article Title" id="articletitleinput"><br>
<textarea id="articlebodyinput" placeholder="Article Body" rows="10" cols="80"></textarea><br>
<label for="articleprivilegeinput">Oprávnenie na pozretie článku:</label>
<input type="number" id="articleprivilegeinput" min="1" value="1" step="1">
<button id="articlesubmit" onclick="submitarticle()"><i class="ri-add-circle-line"></i></button>
<button id="articlecreateclose" onclick="togglearticlecreate()"><i class="ri-close-line"></i></button>
</div>
</div>

View File

@@ -1,16 +1,31 @@
<!DOCTYPE html>
<html lang="sk">
<html lang="sk" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://kit.fontawesome.com/e9016e1de1.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/remixicon/4.0.1/remixicon.min.css" integrity="sha512-dTsohxprpcruDm4sjU92K0/Gf1nTKVVskNHLOGMqxmokBSkfOAyCzYSB6+5Z9UlDafFRpy5xLhvpkOImeFbX6A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="/assets/3rdparty/fonts/remixicon/remixicon.css">
<link rel="stylesheet" href="/assets/3rdparty/pico.min.css">
<link href="/assets/3rdparty/sliderm.css" rel="stylesheet">
<link rel="stylesheet" href="/assets/style.css">
<link rel="icon" href="/assets/images/favicon.png" type="image/png">
__TEMPLATE__DYNAMIC__SCRIPT__
__TEMPLATE__DYNAMIC__STYLE__
<script async src="https://umami.brn.systems/script.js" data-website-id="95e93885-5c19-4cab-ba9b-2f746a316a2a"></script>
<title>Adlerka __TEMPLATE_PAGE_NAME__</title>
<script async src="/assets/script.js"></script>
<script async src="/assets/3rdparty/sliderm.js"></script>
<title>__TEMPLATE_PAGE_TITLE__</title>
__TEMPLATE_SEO_STUFF__
</head>
<body>
__TEMPLATE__NAV__
__TEMPLATE__PAGE__
<nav id="navbar_container">
__TEMPLATE__NAV__
</nav>
<div id="statusMessageContainer"></div>
<main id="page_container">
__TEMPLATE__PAGE__
</main>
<footer id="footer_container">
__TEMPLATE__FOOTER__
</footer>
</body>
</html>

View File

@@ -0,0 +1,50 @@
<!-- Centralized Status Message -->
<p id="StatusMessage"></p>
<div id="user-settings">
<button type="button" onclick="logout()">Logout</button>
<br>
<div class="form-container" id="updateUserProfileForm">
<h1>Update User</h1>
<h2>Profile</h2>
<div class="form-content" id="profile-form">
<label for="updateFirstName">First Name:</label>
<input type="text" id="updateFirstName" name="updateFirstName" required><br>
<label for="updateLastName">Last Name:</label>
<input type="text" id="updateLastName" name="updateLastName" required><br>
<label for="updateNickname">Nickname:</label>
<input type="text" id="updateNickname" name="updateNickname" required><br>
<label for="updateMinecraftNick">Minecraft Nick:</label>
<input type="text" id="updateMinecraftNick" name="updateMinecraftNick" required><br>
<button type="button" onclick="updateUserProfile()">Update Profile</button>
</div>
<br><br>
<h2>Email</h2>
<div class="form-content" id="email-form">
<label for="updateNewEmail">New Email:</label>
<input type="email" id="updateNewEmail" name="updateNewEmail" required><br>
<button type="button" onclick="updateEmail()">Update Email</button>
</div>
<br><br>
<h2>Password</h2>
<div class="form-content" id="password-form">
<label for="changeOldPassword">Old Password:</label>
<input type="password" id="changeOldPassword" name="changeOldPassword" required><br>
<label for="changeNewPassword">New Password:</label>
<input type="password" id="changeNewPassword" name="changeNewPassword" required><br>
<button type="button" onclick="changePassword()">Change Password</button>
</div>
</div>
</div>
<hr>