From 2f71fa5e62f8e0f76455533060f6d3429f67bfef Mon Sep 17 00:00:00 2001 From: flashwave Date: Wed, 10 Jun 2020 16:03:13 +0000 Subject: [PATCH] Initial import. --- .gitattributes | 1 + .gitignore | 4 + cron.php | 46 + public/assets/accept.png | Bin 0 -> 781 bytes public/assets/add.png | Bin 0 -> 733 bytes public/assets/arrow_down.png | Bin 0 -> 379 bytes public/assets/arrow_refresh.png | Bin 0 -> 685 bytes public/assets/arrow_rotate_clockwise.png | Bin 0 -> 602 bytes public/assets/arrow_up.png | Bin 0 -> 372 bytes public/assets/bin_closed.png | Bin 0 -> 363 bytes public/assets/bomb.png | Bin 0 -> 793 bytes public/assets/cancel.png | Bin 0 -> 587 bytes public/assets/delete.png | Bin 0 -> 715 bytes public/assets/drive_add.png | Bin 0 -> 623 bytes public/assets/editor.css | 554 +++++++++++ public/assets/editor.js | 1081 ++++++++++++++++++++++ public/assets/link.png | Bin 0 -> 343 bytes public/assets/no-screenshot.jpg | Bin 0 -> 15264 bytes public/assets/page_white.png | Bin 0 -> 294 bytes public/assets/page_white_edit.png | Bin 0 -> 618 bytes public/assets/page_white_link.png | Bin 0 -> 614 bytes public/assets/page_white_magnify.png | Bin 0 -> 554 bytes public/assets/pencil.png | Bin 0 -> 450 bytes public/assets/shared.css | 68 ++ public/assets/shared.js | 64 ++ public/assets/spinner.gif | Bin 0 -> 3208 bytes public/assets/style.css | 574 ++++++++++++ public/assets/ytkns.png | Bin 0 -> 162098 bytes public/index.php | 1071 +++++++++++++++++++++ src/Colour.php | 27 + src/Config.php | 56 ++ src/DB.php | 32 + src/Effects/BackgroundAudioEffect.php | 137 +++ src/Effects/BackgroundImageEffect.php | 401 ++++++++ src/Effects/ForegroundImageEffect.php | 153 +++ src/Effects/NewlyCreatedPageEffect.php | 45 + src/Effects/ZoomTextEffect.php | 57 ++ src/Gradient.php | 87 ++ src/GradientPoint.php | 65 ++ src/HtmlTag.php | 126 +++ src/HtmlText.php | 14 + src/HtmlTypeInterface.php | 6 + src/PageBuilder.php | 61 ++ src/PageEffectInterface.php | 14 + src/Template.php | 40 + src/Upload.php | 276 ++++++ src/User.php | 123 +++ src/UserInvite.php | 122 +++ src/UserSession.php | 149 +++ src/Zone.php | 361 ++++++++ src/ZoneEffect.php | 102 ++ src/ZoneRedirect.php | 46 + src/ZoneTask.php | 60 ++ src/ZoneView.php | 44 + startup.php | 74 ++ templates/auth/field.html | 8 + templates/auth/login.html | 11 + templates/auth/login2.html | 17 + templates/auth/register.html | 8 + templates/debug/frame.html | 0 templates/debug/index.html | 15 + templates/error.html | 3 + templates/errors/500.html | 11 + templates/footer.html | 9 + templates/header.html | 34 + templates/home.html | 2 + templates/information-redirect.html | 1 + templates/information.html | 6 + templates/menu-site-item.html | 1 + templates/menu-user-item.html | 1 + templates/profile/index.html | 3 + templates/profile/notfound.html | 2 + templates/profile/zone-item.html | 16 + templates/profile/zone-list.html | 3 + templates/profile/zone-none.html | 1 + templates/settings/index.html | 1 + templates/settings/invites-create.html | 4 + templates/settings/invites-item.html | 14 + templates/settings/invites.html | 22 + templates/zones/create.html | 20 + templates/zones/delete.html | 8 + templates/zones/edit.html | 25 + templates/zones/item.html | 16 + templates/zones/list.html | 7 + templates/zones/my-item.html | 33 + templates/zones/my-list.html | 4 + templates/zones/my-none.html | 9 + templates/zones/none.html | 4 + 88 files changed, 6460 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 cron.php create mode 100644 public/assets/accept.png create mode 100644 public/assets/add.png create mode 100644 public/assets/arrow_down.png create mode 100644 public/assets/arrow_refresh.png create mode 100644 public/assets/arrow_rotate_clockwise.png create mode 100644 public/assets/arrow_up.png create mode 100644 public/assets/bin_closed.png create mode 100644 public/assets/bomb.png create mode 100644 public/assets/cancel.png create mode 100644 public/assets/delete.png create mode 100644 public/assets/drive_add.png create mode 100644 public/assets/editor.css create mode 100644 public/assets/editor.js create mode 100644 public/assets/link.png create mode 100644 public/assets/no-screenshot.jpg create mode 100644 public/assets/page_white.png create mode 100644 public/assets/page_white_edit.png create mode 100644 public/assets/page_white_link.png create mode 100644 public/assets/page_white_magnify.png create mode 100644 public/assets/pencil.png create mode 100644 public/assets/shared.css create mode 100644 public/assets/shared.js create mode 100644 public/assets/spinner.gif create mode 100644 public/assets/style.css create mode 100644 public/assets/ytkns.png create mode 100644 public/index.php create mode 100644 src/Colour.php create mode 100644 src/Config.php create mode 100644 src/DB.php create mode 100644 src/Effects/BackgroundAudioEffect.php create mode 100644 src/Effects/BackgroundImageEffect.php create mode 100644 src/Effects/ForegroundImageEffect.php create mode 100644 src/Effects/NewlyCreatedPageEffect.php create mode 100644 src/Effects/ZoomTextEffect.php create mode 100644 src/Gradient.php create mode 100644 src/GradientPoint.php create mode 100644 src/HtmlTag.php create mode 100644 src/HtmlText.php create mode 100644 src/HtmlTypeInterface.php create mode 100644 src/PageBuilder.php create mode 100644 src/PageEffectInterface.php create mode 100644 src/Template.php create mode 100644 src/Upload.php create mode 100644 src/User.php create mode 100644 src/UserInvite.php create mode 100644 src/UserSession.php create mode 100644 src/Zone.php create mode 100644 src/ZoneEffect.php create mode 100644 src/ZoneRedirect.php create mode 100644 src/ZoneTask.php create mode 100644 src/ZoneView.php create mode 100644 startup.php create mode 100644 templates/auth/field.html create mode 100644 templates/auth/login.html create mode 100644 templates/auth/login2.html create mode 100644 templates/auth/register.html create mode 100644 templates/debug/frame.html create mode 100644 templates/debug/index.html create mode 100644 templates/error.html create mode 100644 templates/errors/500.html create mode 100644 templates/footer.html create mode 100644 templates/header.html create mode 100644 templates/home.html create mode 100644 templates/information-redirect.html create mode 100644 templates/information.html create mode 100644 templates/menu-site-item.html create mode 100644 templates/menu-user-item.html create mode 100644 templates/profile/index.html create mode 100644 templates/profile/notfound.html create mode 100644 templates/profile/zone-item.html create mode 100644 templates/profile/zone-list.html create mode 100644 templates/profile/zone-none.html create mode 100644 templates/settings/index.html create mode 100644 templates/settings/invites-create.html create mode 100644 templates/settings/invites-item.html create mode 100644 templates/settings/invites.html create mode 100644 templates/zones/create.html create mode 100644 templates/zones/delete.html create mode 100644 templates/zones/edit.html create mode 100644 templates/zones/item.html create mode 100644 templates/zones/list.html create mode 100644 templates/zones/my-item.html create mode 100644 templates/zones/my-list.html create mode 100644 templates/zones/my-none.html create mode 100644 templates/zones/none.html diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..176a458 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b35a856 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/.debug +/public/ss +/uploads +/config.php diff --git a/cron.php b/cron.php new file mode 100644 index 0000000..2a398d3 --- /dev/null +++ b/cron.php @@ -0,0 +1,46 @@ +getId() !== $task->getZoneId()) + $zoneInfo = $task->getZone(); + + switch($task->getName()) { + case 'screenshot': + $zoneInfo->takeScreenshot(); + break; + } + + $task->delete(); +} + +sem_release($semaphore); diff --git a/public/assets/accept.png b/public/assets/accept.png new file mode 100644 index 0000000000000000000000000000000000000000..89c8129a490b329f3165f32fa0781701aab417ea GIT binary patch literal 781 zcmV+o1M>WdP)4-QibtN)VXQDpczE`xXAkUjh%RI>;okxb7K@0kpyQ1k_Y(|Oe7$m(^ zNYX>mI||sUbmn+c3<&FnE=4u#()KBS^SH8e)Qs5i!#lY=$-1gbH6VluzU=m=EP78&5vQ z-?+fFP-G2l&l_QzYealK$;1Rl?FkzXR&Jv@fBPNjCr#AYRyJ7UJQ0v#?)7Ott=>3`#-pV!7>9}>Q1jL)H6h&gkP@3nI=+F3nA~M>u#(n* z8T!#8oEw&-mED4!h4s!N@Jo3S7N&Q6%6l3}nlcd~X@>;uelvPsSkXIgg~e+^T1zSf z3SNj(5%jK~i8@b;C9VHk(~TedF+gQSL8D5xnVSSWAVY>J9b+m>@{iq7_KE}go~11+5s4;8hc+i0Xa zI1j@EX5!S+Me6HNqKzU5YQwL;-W5$p%ZMKMeR<%zp69-~?<4?8|C8S?bklXr4v&Ov zb&06v2|-x?qB`90yn>Qi%Sh2^G4n)$ZdyvTPf9}1)_buUT7>`e2G&2VU@~Bb(o+Mz zi4)>IxlSY${Dj4k={-9RzU^W5g9|2V5RZ2ZulL9s2xQbZ@r6eP9Ra5u(s|C0Nj#&4>wTSkb?%#=9?@ z^oxDy-O@tyN{L@by(WWvQ3%CyEu8x{+#Jb4-h&K9Owi)2pgg+heWDyked|3R$$kL@A z#sp1v-r+=G4B8D6DqsDH0@7OztA7aT9qc1Py{()w`m``?Y0&gi2=ROcc-9+nU^I6< zT=e_Y=vSnG@?3Ue{BW5ONFttcE!R-R_W4O01|0-|K-YNXLo2`4Qv z`r1LxR6#yf3FB%T95gJnaKKivA~Z}S9A(ZxEDK}O3T04USJ P00000NkvXXu0mjf^IS-S literal 0 HcmV?d00001 diff --git a/public/assets/arrow_down.png b/public/assets/arrow_down.png new file mode 100644 index 0000000000000000000000000000000000000000..2c4e279377bf348f9cf53894e76bb673ccf067bd GIT binary patch literal 379 zcmV->0fhdEP)RB*?~^j!LKVQ>(O&A{Xr%)RXLn#U zs4LtZ6rCMFY5|B2$)yG$6aaIF`!iy8(2_#ButL^3%VaH2WCpD^U)OZxp@C)2#hU)y+@T%ZNzJigNk%37 zz-WYJwT%teVfiEI+B*@v4ey@58(ld4VY_&5-ox`e@AKg+0U-I`y79bmuw_~y6+4rZ zBG5EdFDS+@M0OSE`>d7SUDOzKZ&h*4eB1iX7tOd9RiYtW2mQ--bUahxr1`i{RG@dM zL#}_X=DDO1{;UI$pFu=dLYT_=5d8WC-sLfjr7UO-HKMAwa=!>)kEhvuwre zuW3yF@ZxFCkI*+ad|5kOX%5zu8IQjhan)UqgSrFGA_0nQFn@Z08DSEUToCSz4Z1ls z&fDbq$T&7|6iq$_uDI$@q1_kQ@dfqk*0>{SDL6V)94@)ete)j++*>bIc9sj}Y;R1o z#OpH+Yt-^4wfv{nern^iVag8`6pHR5;7E zlj%-cQ51!F4&J~xM61?D4NeiKN{NVy6X-Qqxk8=MfEX_jL}^8BK|~N7FhJC_i5-lo zIHJodz)CX1Y{)@S~!d8q1l+bMV!}{YL?7wC( z%edaW=F&F+ z($9?`K8YY%uR)_%0MQCxd3ndPa;Y~P?U+0ni$XZM7r6Hug|3%ZMP*Zkl*~_HJgCLk zA%$#B#@zrWY4GPt5fc}9icOq|ZdVP>t`f>G-FQBfO2-8VoE7L?C-B-QL8&<(Elc$< z9F9VNpaHd~Mzw{9XMM@!tpI_e9I`cn>D7RVpdQ8}3WkFbsCW7qu?h_iA<}x2LUHGS zH_L_8E5^i;Hs-n?bM4!dqv}ItRGh3xVBa~)+_!mXcGsfbDMZdlCbCp%NFPW!OYKP{ oAG`}3ZPvA6w#wHUuW*nL5>vZR zlg{G&%mT~|kL3ei%GW0*UOHUMs5XI$4uxe-L?I@SAefq*207}Iqtjm#e5*fP53AiC z)C|RQfwzxx<#_WfANRGZx{+tFDl8~Q?;~Ve=lM^*8UTTnVL?HTDz8uta0D@d28E9S z_)i8aLz^UE6PPKymi;2GJ`34{eIia-CtfAt0H61rk0 SPTNud0000C#5QQ<|d}62BjvZR2H60wE-&n>*?YcA|c6o&|A+rkb&Vsas2(@ zRZChzd<-rL%s+C)K`AUj&@4exERnTGr7g|ufjFlYo2X-lQc?WBS5|%h$H~-pP=>{CWcO1^&sC zas{(rHNEeVX?R|+D*2koKAT-1TVxuXF7Gt2pCrhe%W27Qah0e`>%4W-qEi^2ys8L$ zkzFy>C52%M!-~#H9Xt(|Evme3 znsTM3@O9jy7lMk63GZ(u8Hac+{l2Nv{)WYyc2KzeV81veQkLz}aZs=_c)I$ztaD0e F0sv?9ij)8V literal 0 HcmV?d00001 diff --git a/public/assets/bomb.png b/public/assets/bomb.png new file mode 100644 index 0000000000000000000000000000000000000000..1be37974adf9de2880e568e700cdcc9037c78804 GIT binary patch literal 793 zcmV+!1LpjRP)_K8L+b&88i{a`h`7P#Ww_cAT-3>7iG?d zKQ=FfJTtdc)9<~|e5oamr>PvpaLsiMK% z)`3JKfz0M{+kjr+tJMf=)!zW)RDC`lVzC%#5o2Rx@Or(N z+pwWr_G0Cy#KMLfiChPqPN$_Y0QGvkUkCxe-;Z!O3?8>yEmSHM!)YC#@*XrA4QQHX z0BF760&r5HP@qa>L#*q%0boI`Rx<$XRMi0R97hX46Bz=Guz(YJks&PLZ=UBdyUk`p zsZ`>ej0K>|afib(q9}^d4OeFfM;4ipp)QvTJ3Bi(j$5}tCq9+UX8HLVTi_P?ApdRO zZnqn4=W;oEbYjW^%*@QBNDxgXlO=9ZRaFCIR%heN3`nQbC8{1JXzH(jfvvZ<_c95_ zgTY{*$Kx^fYu?80?k<*>m$`D3z?1*|Or`|_fk8U72%SNQDjC;Ho*|D@?i7hr`}2PR X#`)v2r!)w#00000NkvXXu0mjfQIK%^ literal 0 HcmV?d00001 diff --git a/public/assets/cancel.png b/public/assets/cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..c149c2bc017d5ce5a8ae9330dd7dbd012482e0f4 GIT binary patch literal 587 zcmV-R0<`^!P)FS^-G}e*;M)Q6>s#cP zI`Y#S($G6W`W@NI5g|L-MKl0Zmu$m^(0~^Lwo5OO~d#(vPfzC4}Mrzlg<+1Y8PEBfUp0jJpx4B>@E+cy3`^(Gw`Mf+2&yxZm<$to~Vpgvg&QKNR z_f#1(r6svZt%iF?s+n<8X?B&!h3g9Dbb8_=MX}!;HiQSAh`bp^WMl~Z-44teO7W_Y zV4thSL{h;rJY7!l3%5J4H1!tIzB`Dv+YxO(haWeausGZYkI8^hWj6mzo=L0{%;yxzh{5!Htr?51 zvG|W62MzC8BZ76hRpCyO2zOn<%e)K>NHge!-~)Ap33OdWw6hsLYbCxGNt0%wk_2z7 zfyYvXheSG)5HRK1VB~%mq7Dmurw#bi@hEcOr3&G1ZiF*$M=&9nB#VNf&Q^r$4G5kp zTURh&s)E0%5&hyVD}sp<72~zmAY`Y(9aqO6CXF%=zFHGzO-A&I(pE}v70YQxCPJ{Y z4L+?5-crdLn3ZRPEs!A4ehEY3ZRpL~w9>@aMN+{F4dI@v&>(QDHQum!mG~E^$OS8l z!7?%Uwib*ROP67Hw`ika)gX-(8Ia`-u_IEhxG7U<13kSsMW+$lbb2dUMm5p6pa}cjgA+U$^mJ^AjD?&bdi)8~y+Q002ovPDHLkV1g8IMc@Dc literal 0 HcmV?d00001 diff --git a/public/assets/drive_add.png b/public/assets/drive_add.png new file mode 100644 index 0000000000000000000000000000000000000000..29a35d5aa8d270d0cc2e2fb29de8c8b5a82022d0 GIT binary patch literal 623 zcmV-#0+9WQP)JieWo?|tumGdpatSl}!}!5N1pKRTVx zYtQov9GK7Nn9XJZrqgMF$z(#YQ7)H@#ADO~CtouR!$~HSBI(}|Zd2EFIF5r}uUD*8 zDh=U)D?W_J;}{GEFikTcg^Bdt@Anal#gNHlNOYYqH(0CH66%RW0;AFBgk5Tdh>S6) zsw&i@(I_I327D}hnA}^In5vQP$D)maB4FVOQ z0hf5YdVgCqA3E{(5>wnYI+BvLk;4J;Fx*1L!MP-8z)TFztX+(o=;@1Xf{ z8zaR&EeYiS+`6&~Pw{a1dUSK7c$Mb_%(D;egYqi>H=r0SR#1Hd3 zbY|nm@?BBzL2(N&Tbo$0>hYiWzmcyj9o@cp-I~M4TIne^cJIJaO+4GKx~F$XWFb(ha-_q6-cG~B>a{s~x5BE5t9KSg>n~%S){RQIedSx-2#tZ-e002ov JPDHLkV1g!V6x{#- literal 0 HcmV?d00001 diff --git a/public/assets/editor.css b/public/assets/editor.css new file mode 100644 index 0000000..f7f3083 --- /dev/null +++ b/public/assets/editor.css @@ -0,0 +1,554 @@ +.ye { + display: flex; + min-height: 500px; + border-bottom: 1px solid #ddd; + margin: -1px; +} + +.ye-sidebar { + flex: 0 0 auto; + min-width: 250px; + display: flex; + flex-direction: column; +} + +.ye-sidebar-buttons { + flex: 0 0 auto; + display: flex; + background-color: #eee; + border-bottom: 1px solid #ddd; + justify-content: flex-start; + align-items: center; + height: 28px; +} +.ye-sidebar-buttons-separator { + width: 1px; + height: 22px; + margin: 1px; + background-color: #ddd; +} +.ye-sidebar-buttons-button { + margin: 1px; + width: 24px; + height: 24px; + cursor: pointer; + background-size: 16px 16px; + background-repeat: no-repeat; + background-position: center center; + background-image: url('page_white.png'); +} +.ye-sidebar-buttons-button:hover, +.ye-sidebar-buttons-button:focus { + border: 1px solid #7ca1cd; + background-color: #deecfd; +} +.ye-sidebar-buttons-button:active { + background-color: #f1f7fe; +} +.ye-sidebar-buttons-button--save { background-image: url('accept.png'); } +.ye-sidebar-buttons-button--cancel { background-image: url('cancel.png'); } +.ye-sidebar-buttons-button--add { background-image: url('add.png'); } +.ye-sidebar-buttons-button--preview { background-image: url('page_white_magnify.png'); } +.ye-sidebar-buttons-button--edit { background-image: url('page_white_edit.png'); } +.ye-sidebar-buttons-button--live { background-image: url('page_white_link.png'); } +.ye-sidebar-buttons-button--reset { background-image: url('arrow_refresh.png'); } + +.ye-sidebar-effects { + flex: 1 1 auto; +} +.ye-sidebar-effects-empty { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; +} +.ye-sidebar-effects-effect { + display: flex; + justify-content: space-between; + border-bottom: 1px solid #ddd; + padding: 5px; +} +.ye-sidebar-effects-effect-name { + padding: 0 5px; +} +.ye-sidebar-effects-effect-actions { + flex: 0 0 auto; + display: flex; + justify-content: flex-end; + align-items: center; +} +.ye-sidebar-effects-effect-actions-action { + margin: 1px; + width: 20px; + height: 20px; + cursor: pointer; + background-size: 16px 16px; + background-repeat: no-repeat; + background-position: center center; + background-image: url('page_white.png'); +} +.ye-sidebar-effects-effect-actions-action--edit { background-image: url('pencil.png'); } +.ye-sidebar-effects-effect-actions-action--delete { background-image: url('delete.png'); } + +.ye-main { + flex: 1 1 auto; + border-left: 1px solid #ddd; + display: flex; + flex-direction: column; +} + +.ye-main-title { + flex: 0 0 auto; + border-left: 1px solid #ddd; + display: flex; + flex-direction: column; + justify-content: center; + background-color: #eee; + border-bottom: 1px solid #ddd; + height: 28px; + font-size: 1.2em; + padding: 0 6px; +} + +.ye-main-container { + flex: 1 1 auto; + display: flex; + flex-direction: column; + align-items: stretch; +} + +.ye-main-welcome { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; +} + +.ye-main-effect-delete-confirm { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; +} +.ye-main-effect-delete-confirm-text { + font-size: 1.2em; + line-height: 1.5em; +} +.ye-main-effect-delete-confirm-actions { + display: flex; + margin: 5px; +} +.ye-main-effect-delete-confirm-actions-action { + cursor: pointer; + margin: 0 5px; + padding: 5px 10px; + font-size: 1.1em; + border-radius: 5px; + min-width: 100px; + text-align: center; + color: #fff; + transition: background-color .1s; +} +.ye-main-effect-delete-confirm-actions-action--accept { background-color: #28a745; } +.ye-main-effect-delete-confirm-actions-action--accept:hover, +.ye-main-effect-delete-confirm-actions-action--accept:focus { background-color: #218838; } +.ye-main-effect-delete-confirm-actions-action--accept:active { background-color: #1e7e34; } +.ye-main-effect-delete-confirm-actions-action--deny { background-color: #dc3545; } +.ye-main-effect-delete-confirm-actions-action--deny:hover, +.ye-main-effect-delete-confirm-actions-action--deny:focus { background-color: #c82333; } +.ye-main-effect-delete-confirm-actions-action--deny:active { background-color: #bc2130; } + +.ye-main-details { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + max-width: 400px; + margin: 0 auto; +} +.ye-main-details-fields { + width: 100%; +} +.ye-main-details-fields-field { + display: flex; + margin: 5px 0; +} +.ye-main-details-fields-field-name { + min-width: 100px; +} +.ye-main-details-fields-field-wrap { + border: 1px solid #000; + min-width: 300px; + height: 22px; + display: flex; + padding: 0 2px; + background-color: #eee; + color: #444; +} +.ye-main-details-fields-field-input { + border-width: 0; + flex-grow: 1; + font: 12px/20px Tahoma, Geneva, 'Dejavu Sans', Arial, Helvetica, sans-serif; + background-color: #eee; + color: #000; +} +.ye-main-details-fields-field--readonly .ye-main-details-fields-field-wrap, +.ye-main-details-fields-field--readonly .ye-main-details-fields-field-input { + color: #666; +} + +.ye-applet-effects-list { + display: flex; + flex-direction: column; +} +.ye-applet-effects-list-item { + display: flex; + justify-content: space-between; + padding: 5px; + border-bottom: 1px solid #ddd; +} +.ye-applet-effects-list-item--used { + opacity: .5; +} +.ye-applet-effects-list-item-name { + padding: 1px 5px; +} +.ye-applet-effects-list-item-actions { + flex: 0 0 auto; + display: flex; + justify-content: flex-end; + align-items: center; +} +.ye-applet-effects-list-item-actions-action { + margin: 1px; + width: 20px; + height: 20px; + cursor: pointer; + background-size: 16px 16px; + background-repeat: no-repeat; + background-position: center center; + background-image: url('page_white.png'); +} +.ye-applet-effects-list-item-actions-action--add { background-image: url('add.png'); } + +.ye-applet-uploads { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; +} +.ye-applet-uploads-progress { + width: 100%; + height: 100%; + flex: 1 1 auto; + display: flex; + justify-content: center; + align-items: center; + padding: 10px; +} +.ye-applet-uploads-progress-bar { + height: 24px; + width: 100%; + flex: 0 0 auto; +} +.ye-applet-uploads-cancel { + margin: 5px; + margin-top: 0; + font-size: 1.2em; + padding: 5px; + cursor: pointer; +} +.ye-applet-uploads-dropzone { + flex: 1 1 auto; + min-height: 200px; + padding: 5px; + cursor: pointer; +} +.ye-applet-uploads-dropzone-inner { + width: 100%; + height: 100%; + background-color: #ccc; + background-image: radial-gradient(circle at center, #eee, #bbb); + border: 5px dashed #888; + border-radius: 5px; + display: flex; + justify-content: center; + align-items: center; + font-size: 1.5em; + line-height: 1.5em; + transition: border-color .2s; +} +.ye-applet-uploads-dropzone--active .ye-applet-uploads-dropzone-inner { + border-color: #444; +} +.ye-applet-uploads-hidden { + display: none; +} + +.ye-applet-editor { +} +.ye-applet-editor--fill { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; +} +.ye-applet-editor-empty { + font-size: 1.2em; + line-height: 1.5em; +} +.ye-applet-editor-properties { + display: flex; + flex-direction: column; +} +.ye-applet-editor-properties-property { + display: flex; + flex-direction: column; + border-bottom: 1px solid #ddd; +} +.ye-applet-editor-properties-property-title { + font-size: 1.1em; + line-height: 1.5em; + padding: 2px 5px; +} +.ye-applet-editor-properties-property-wrap { + display: flex; + flex: 1 1 auto; + justify-content: space-between; +} +.ye-applet-editor-properties-property-none { + font-size: .9em; + padding: 2px 10px; +} +.ye-applet-editor-properties-property-reset { + flex: 0 0 auto; + width: 20px; + height: 20px; + margin: 4px; + background-image: url('bomb.png'); + background-size: 16px 16px; + background-position: center center; + background-repeat: no-repeat; + cursor: pointer; +} +.ye-applet-editor-properties-property-bool { + display: block; + width: 100%; +} +.ye-applet-editor-properties-property-bool-toggle { + margin: 7px; +} +.ye-applet-editor-properties-property-int { + display: block; + width: 100%; +} +.ye-applet-editor-properties-property-int-input { + margin: 2px 5px; + padding: 2px; + border: 1px solid #aaa; +} +.ye-applet-editor-properties-property-int-input:focus { + border-color: #f78f2e; +} +.ye-applet-editor-properties-property-upload { + display: flex; + align-items: center; +} +.ye-applet-editor-properties-property-upload-id { + font-size: .9em; + padding: 2px 10px; +} +.ye-applet-editor-properties-property-upload-select { + flex: 0 0 auto; + width: 20px; + height: 20px; + margin: 4px; + background-image: url('drive_add.png'); + background-size: 16px 16px; + background-position: center center; + background-repeat: no-repeat; + cursor: pointer; +} +.ye-applet-editor-properties-property-select { + display: block; + width: 100%; +} +.ye-applet-editor-properties-property-select-input { + margin: 5px; + padding: 2px; + border: 1px solid #aaa; +} +.ye-applet-editor-properties-property-select-input:focus { + border-color: #f78f2e; +} +.ye-applet-editor-properties-property-colour { + display: block; + width: 100%; +} +.ye-applet-editor-properties-property-colour-input { + margin: 5px; + padding: 2px; + border: 1px solid #aaa; +} +.ye-applet-editor-properties-property-colour-input:focus { + border-color: #f78f2e; +} +.ye-applet-editor-properties-property-string { + display: flex; + width: 100%; +} +.ye-applet-editor-properties-property-string-input { + margin: 5px; + padding: 2px; + border: 1px solid #aaa; + flex: 1 1 auto; +} +.ye-applet-editor-properties-property-string-input:focus { + border-color: #f78f2e; +} +.ye-applet-editor-properties-property-gradient { + flex-grow: 1; +} +.ye-applet-editor-properties-property-gradient-preview { + margin: 0 5px; + height: 50px; + border: 1px solid #aaa; +} +.ye-applet-editor-properties-property-gradient-direction { + display: flex; + align-items: center; +} +.ye-applet-editor-properties-property-gradient-direction-circle { + margin: 6px; + width: 34px; + height: 34px; + border-radius: 100%; + border: 1px solid #8c8c8c; + padding: 1px; + background: #fefefe linear-gradient(0deg, #fdfdfd, #d9d9d9); + box-shadow: inset 0 0 0 1px #fff; +} +.ye-applet-editor-properties-property-gradient-direction-circle:active { + border-color: #f78f2e; +} +.ye-applet-editor-properties-property-gradient-direction-circle-value { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: flex-start; + pointer-events: none; +} +.ye-applet-editor-properties-property-gradient-direction-circle-indicator { + background: #999; + width: 2px; + height: 50%; + border-radius: 1px; + pointer-events: none; +} +.ye-applet-editor-properties-property-gradient-direction-input { + margin: 5px; + margin-left: 0; + padding: 2px; + border: 1px solid #aaa; + width: 60px; +} +.ye-applet-editor-properties-property-gradient-direction-input:focus { + border-color: #f78f2e; +} +.ye-applet-editor-properties-property-gradient-points-add { + margin: 1px; + margin-left: auto; + border: 1px solid #707070; + border-radius: 2px; + background: linear-gradient(180deg, #fcfcfc 0%, #ebebeb 50%, #dbdbdb 50%) #ebebeb; + cursor: pointer; + padding: 0 5px; + font: 12px/20px Tahoma, Geneva, 'Dejavu Sans', Arial, Helvetica, sans-serif; + display: flex; + align-items: center; + color: #000; + text-decoration: none; + transition: border-color .2s; +} +.ye-applet-editor-properties-property-gradient-points-add:hover, +.ye-applet-editor-properties-property-gradient-points-add:focus { + border-color: #f78f2e; +} +.ye-applet-editor-properties-property-gradient-points-add:active { + background: linear-gradient(0deg, #fcfcfc 0%, #ebebeb 50%, #dbdbdb 50%) #ebebeb; +} +.ye-applet-editor-properties-property-gradient-points-add-icon { + width: 16px; + height: 16px; + background-image: url('add.png'); + background-size: 16px 16px; + background-position: center center; + background-repeat: no-repeat; + margin-right: 4px; +} +.ye-applet-editor-properties-property-gradient-points { + display: flex; + flex-direction: column; + max-height: 400px; + overflow: auto; +} +.ye-applet-editor-properties-property-gradient-points-point { + display: flex; + align-items: center; +} +.ye-applet-editor-properties-property-gradient-points-point-actions { + flex: 0 0 auto; + display: flex; + justify-content: flex-end; + align-items: center; +} +.ye-applet-editor-properties-property-gradient-points-point-actions-action { + margin: 1px; + width: 20px; + height: 20px; + cursor: pointer; + background-size: 16px 16px; + background-repeat: no-repeat; + background-position: center center; + background-image: url('page_white.png'); +} +.ye-applet-editor-properties-property-gradient-points-point-actions-action--delete { background-image: url('delete.png'); } +.ye-applet-editor-properties-property-gradient-points-point-colour { + margin: 2px 5px; + display: block; + overflow: hidden; + width: 41px; + height: 21px; + border: 1px solid #aaa; + box-shadow: inset 0 0 0 1px #fff; +} +.ye-applet-editor-properties-property-gradient-points-point-colour:active, +.ye-applet-editor-properties-property-gradient-points-point-colour:focus-within { + border-color: #f78f2e; +} +.ye-applet-editor-properties-property-gradient-points-point-colour-value { + position: relative; + top: -500px; +} +.ye-applet-editor-properties-property-gradient-points-point-offset-range { + flex: 1 1 auto; +} +.ye-applet-editor-properties-property-gradient-points-point-offset-numeric { + margin: 5px; + padding: 2px; + border: 1px solid #aaa; + width: 60px; +} +.ye-applet-editor-properties-property-gradient-points-point-offset-numeric:focus { + border-color: #f78f2e; +} diff --git a/public/assets/editor.js b/public/assets/editor.js new file mode 100644 index 0000000..2a9c269 --- /dev/null +++ b/public/assets/editor.js @@ -0,0 +1,1081 @@ +function ytknsRequestJson(url, callback, method, data, headers, handleErr, upload) { + if(!callback) + callback = function(){}; + + var xhr = new XMLHttpRequest; + + if(xhr.upload && upload) { + xhr.upload.onloadstart = function(ev) { upload('loadstart', ev); }; + xhr.upload.onprogress = function(ev) { upload('progress', ev) }; + xhr.upload.onload = function(ev) { upload('load', ev); }; + } + + xhr.onreadystatechange = function() { + if(xhr.readyState !== 4) + return; + + var json = JSON.parse(xhr.responseText); + + if(json.err && !handleErr) { + alert(json.err); + callback([]); + } else + callback(json); + }; + xhr.open(method || 'GET', url); + + if(headers && headers.length > 0) + for(var i = 0; i < headers.length; i++) + xhr.setRequestHeader(headers[i].name, headers[i].value); + + xhr.send(data || null); +} + +function ytknsEditorLoadEffects(callback) { + ytknsRequestJson('/zones/_effects', callback); +} + +function ytknsLoadZoneInfo(zoneId, callback) { + ytknsRequestJson('/zones/' + parseInt(zoneId).toString() + '.json', callback); +} + +function ytknsLoadUploadInfo(uploadId, callback) { + ytknsRequestJson('/uploads/' + uploadId.toString() + '.json', callback); +} + +function ytknsCreateUrlString(set) { + var parts = []; + + for(var i = 0; i < set.length; i++) + parts.push(encodeURIComponent(set[i].name) + '=' + encodeURIComponent(set[i].value)); + + return parts.join('&'); +} +function ytknsCreateUrlStringPart(name, value) { + return { 'name': name.toString(), 'value': ( + (typeof value).toLowerCase() === 'object' + ? JSON.stringify(value) + : value.toString() + ) }; +} + +function ytknsZoneInfoToUrlString(zoneInfo) { + return ytknsCreateUrlString(ytknsZoneInfoSerialise(zoneInfo, ytknsCreateUrlStringPart)); +} +function ytknsZoneInfoSerialise(zoneInfo, callback) { + var set = []; + + if(zoneInfo) { + set.push(callback('zone_token', ytknsEditorToken || '')); + set.push(callback('zone_id', zoneInfo.id)); + set.push(callback('zone_name', zoneInfo.name)); + set.push(callback('zone_title', zoneInfo.title)); + + if(zoneInfo.effects && zoneInfo.effects.length > 0) { + for(var i = 0; i < zoneInfo.effects.length; i++) { + var format = 'zone_effect[%1][%2]'.replace('%1', zoneInfo.effects[i].type), + keys = Object.keys(zoneInfo.effects[i].values); + + if(keys.length < 1) { + set.push(callback(format.replace('%2', '_'), '_')); + } else + for(var j = 0; j < keys.length; j++) { + var key = format.replace('%2', keys[j]); + set.push(callback(key, zoneInfo.effects[i].values[keys[j]] || '')); + } + } + } + } + + return set; +} + +function ytknsSaveZoneInfo(zoneInfo, callback) { + if(!zoneInfo || !zoneInfo.id) + return; + + ytknsRequestJson( + '/zones/' + parseInt(zoneInfo.id).toString() + '.json', + callback, + 'POST', + ytknsZoneInfoToUrlString(zoneInfo), + [{ name: 'Content-Type', value: 'application/x-www-form-urlencoded' }] + ); +} + +function ytknsEditorPreview(zoneInfo) { + var formElement = document.createElement('form'); + formElement.action = '/zones/_preview'; + formElement.method = 'post'; + formElement.target = '_blank'; + formElement.style.display = 'none'; + + var elements = ytknsZoneInfoSerialise(zoneInfo, function(name, value) { + var inputElement = document.createElement('input'); + inputElement.name = name; + inputElement.type = 'hidden'; + inputElement.value = ( + (typeof value).toLowerCase() === 'object' + ? JSON.stringify(value) + : value.toString() + ); + formElement.appendChild(inputElement); + }); + + document.body.appendChild(formElement); + formElement.submit(); + formElement.parentNode.removeChild(formElement); +} + +function ytknsEditorCreateUpload(file, callback, status) { + var formData = new FormData; + formData.append('upload_token', ytknsEditorUploadToken || ''); + formData.append('upload_file', file); + + ytknsRequestJson( + '/uploads', + callback, + 'POST', + formData, + null, + true, + status + ); +} + +var ytknsZoneInfo = null, + ytknsEditorToken = '', + ytknsEditorUploadToken = '', + ytknsEditorElemSidebar = null, + ytknsEditorElemSidebarButtons = null, + ytknsEditorElemSidebarEffects = null, + ytknsEditorElemMain = null, + ytknsEditorElemMainTitle = null, + ytknsEditorElemMainContainer = null, + ytknsEditorEffects = [], + ytknsEditorIgnoreHashChange = false, + ytknsEditorCleanExit = true; + +function ytknsEditorChangeHash(hash) { + ytknsEditorIgnoreHashChange = true; + location.hash = hash; +} + +function ytknsEditorBeforeUnload(ev) { + if(!ytknsEditorCleanExit) { + ev.preventDefault(); + ev.returnValue = ''; + } +} + +function ytknsGetEffectInfoByType(type) { + for(var i = 0; i < ytknsEditorEffects.length; i++) + if(ytknsEditorEffects[i].type === type) + return ytknsEditorEffects[i]; + return null; +} + +function ytknsGetEffectValueByType(type) { + if(ytknsZoneInfo === null || ytknsZoneInfo.effects === null || ytknsZoneInfo.effects.length < 1) + return null; + + for(var i = 0; i < ytknsZoneInfo.effects.length; i++) + if(ytknsZoneInfo.effects[i].type === type) + return ytknsZoneInfo.effects[i]; + + return null; +} +function ytknsRemoveEffectValueByType(type) { + var info = ytknsGetEffectValueByType(type); + + if(!info) { + alert('Tried to remove an effect that doesn\'t exist.'); + return; + } + + var index = ytknsZoneInfo.effects.indexOf(info); + + if(index >= 0) + ytknsZoneInfo.effects.splice(index, 1); + + ytknsEditorUpdateSidebarEffects(); +} + +function ytknsEditorFilePickerDoUpload(files, callback, status) { + if(files.length < 1) { + alert('No file selected.'); + return; + } + + var file = files[0]; + + ytknsEditorCreateUpload(file, function(resp) { + if(resp.file) { + ytknsLoadUploadInfo(resp.file, function(uploadInfo) { + callback(uploadInfo); + }); + } else if(resp.err) { + alert(resp.err); + if(status) + status(-1, -1); + } + }, function(type, ev) { + if(status) + status(ev.loaded, ev.total); + }); +} + +function ytknsEditorShowFilePicker(mimes, title, callback) { + var container = document.createElement('div'); + container.classList.add('ye-applet-uploads'); + + var progressBarContainer = document.createElement('div'); + progressBarContainer.classList.add('ye-applet-uploads-progress'); + progressBarContainer.classList.add('ye-applet-uploads-hidden'); + container.appendChild(progressBarContainer); + + var progressBar = document.createElement('progress'); + progressBar.min = 0; + progressBar.max = 100; + progressBar.value = 0; + progressBar.classList.add('ye-applet-uploads-progress-bar'); + progressBarContainer.appendChild(progressBar); + + var dropZone = document.createElement('div'), + dropZoneInner = document.createElement('div'); + dropZone.classList.add('ye-applet-uploads-dropzone'); + dropZoneInner.classList.add('ye-applet-uploads-dropzone-inner'); + dropZoneInner.textContent = 'Drop a file or click here!'; + dropZone.appendChild(dropZoneInner); + container.appendChild(dropZone); + + var cancel = document.createElement('input'); + cancel.classList.add('ye-applet-uploads-cancel'); + cancel.type = 'button'; + cancel.value = 'Cancel'; + cancel.onclick = function() { callback(null); }; + container.appendChild(cancel); + + var doUpload = function(files) { + dropZone.classList.add('ye-applet-uploads-hidden'); + cancel.classList.add('ye-applet-uploads-hidden'); + progressBarContainer.classList.remove('ye-applet-uploads-hidden'); + + ytknsEditorFilePickerDoUpload(files, function(resp) { + dropZone.classList.remove('ye-applet-uploads-hidden'); + cancel.classList.remove('ye-applet-uploads-hidden'); + progressBarContainer.classList.add('ye-applet-uploads-hidden'); + callback(resp); + }, function(loaded, total) { + if(loaded < 0 && total < 0) { + dropZone.classList.remove('ye-applet-uploads-hidden'); + cancel.classList.remove('ye-applet-uploads-hidden'); + progressBarContainer.classList.add('ye-applet-uploads-hidden'); + return; + } + + progressBar.value = Math.ceil((loaded / total) * 100); + }); + }; + + dropZone.onclick = function() { + var selector = document.createElement('input'); + selector.type = 'file'; + selector.onchange = function() { doUpload(selector.files); }; + + if(mimes) + selector.accept = mimes.join(','); + + selector.click(); + }; + dropZone.ondragenter = function(ev) { + ev.preventDefault(); + ev.stopPropagation(); + dropZone.classList.add('ye-applet-uploads-dropzone--active'); + }; + dropZone.ondragleave = function(ev) { + ev.preventDefault(); + ev.stopPropagation(); + dropZone.classList.remove('ye-applet-uploads-dropzone--active'); + }; + dropZone.ondragover = function(ev) { + ev.preventDefault(); + ev.stopPropagation(); + dropZone.classList.add('ye-applet-uploads-dropzone--active'); + }; + dropZone.ondrop = function(ev) { + ev.preventDefault(); + ev.stopPropagation(); + dropZone.classList.remove('ye-applet-uploads-dropzone--active'); + doUpload(ev.dataTransfer.files); + }; + + ytknsEditorMainSetContainer(container, title || 'Upload File'); +} + +function ytknsLoadEffectEditor(effectInfo, effectValue) { + ytknsEditorChangeHash(effectInfo.type); + + var editor = document.createElement('div'); + editor.classList.add('ye-applet-editor'); + + if(effectInfo.props.length < 1) { + var empty = document.createElement('div'); + editor.classList.add('ye-applet-editor--fill'); + empty.classList.add('ye-applet-editor-empty'); + empty.textContent = 'This effect has no properties.'; + editor.appendChild(empty); + } else { + var properties = document.createElement('div'); + properties.classList.add('ye-applet-editor-properties'); + editor.appendChild(properties); + + for(var i = 0; i < effectInfo.props.length; i++) { + ytknsAddEffectEditorProperty( + properties, + effectInfo, effectValue, effectInfo.props[i], + function() { ytknsLoadEffectEditor(effectInfo, effectValue); } // there's gotta be a better way + ); + } + } + + ytknsEditorMainSetContainer(editor, effectInfo.name); +} +function ytknsAddEffectEditorProperty(listContainer, effectInfo, effectValue, propInfo, redraw) { + var container = document.createElement('div'); + container.classList.add('ye-applet-editor-properties-property'); + listContainer.appendChild(container); + + var propTitle = document.createElement('div'); + propTitle.classList.add('ye-applet-editor-properties-property-title'); + propTitle.textContent = propInfo.title; + container.appendChild(propTitle); + + var propWrap = document.createElement('div'); + propWrap.classList.add('ye-applet-editor-properties-property-wrap'); + container.appendChild(propWrap); + + switch(propInfo.type.name) { + case 'upload': + var propUpload = document.createElement('div'); + propUpload.classList.add('ye-applet-editor-properties-property-upload'); + propWrap.appendChild(propUpload); + + var propUploadId = document.createElement('div'); + propUploadId.classList.add('ye-applet-editor-properties-property-upload-id'); + propUploadId.textContent = effectValue.values[propInfo.name] || '(none)'; + propUpload.appendChild(propUploadId); + + var propUploadSelect = document.createElement('div'); + propUploadSelect.classList.add('ye-applet-editor-properties-property-upload-select'); + propUploadSelect.title = 'Upload file'; + propUploadSelect.onclick = function() { + ytknsEditorShowFilePicker(propInfo.type.allowed || [], ('Selecting ' + propInfo.title + ' for ' + effectInfo.name), function(uploadInfo) { + if(uploadInfo) + effectValue.values[propInfo.name] = uploadInfo.id || null; + + redraw(); + }); + }; + propUpload.appendChild(propUploadSelect); + break; + + case 'bool': + var propBool = document.createElement('label'); + propBool.classList.add('ye-applet-editor-properties-property-bool'); + propWrap.appendChild(propBool); + + var propBoolToggle = document.createElement('input'); + propBoolToggle.classList.add('ye-applet-editor-properties-property-bool-toggle'); + propBoolToggle.type = 'checkbox'; + propBoolToggle.checked = (typeof effectValue.values[propInfo.name]).toLowerCase() === 'undefined' + ? (propInfo.default || false) + : effectValue.values[propInfo.name]; + propBoolToggle.onchange = function() { + effectValue.values[propInfo.name] = propBoolToggle.checked; + redraw(); + }; + propBool.appendChild(propBoolToggle); + break; + + case 'int': + case 'float': + var propInt = document.createElement('label'); + propInt.classList.add('ye-applet-editor-properties-property-int'); + propWrap.appendChild(propInt); + + var propIntInput = document.createElement('input'); + propIntInput.classList.add('ye-applet-editor-properties-property-int-input'); + propIntInput.type = 'number'; + + if(propInfo.min) + propIntInput.min = propInfo.min; + if(propInfo.max) + propIntInput.max = propInfo.max; + if(propInfo.type.name === 'float') + propIntInput.step = 0.01; + + propIntInput.value = effectValue.values[propInfo.name] || propInfo.default || 0; + propIntInput.onchange = function() { + effectValue.values[propInfo.name] = propInfo.type.name === 'int' ? parseInt(propIntInput.value) : parseFloat(propIntInput.value); + redraw(); + }; + propInt.appendChild(propIntInput); + break; + + case 'select': + var propSelect = document.createElement('label'); + propSelect.classList.add('ye-applet-editor-properties-property-select'); + propWrap.appendChild(propSelect); + + var propSelectInput = document.createElement('select'); + propSelectInput.classList.add('ye-applet-editor-properties-property-select-input'); + propSelectInput.onchange = function() { + effectValue.values[propInfo.name] = propSelectInput.value; + redraw(); + }; + propSelect.appendChild(propSelectInput); + + var propSelectOptionsKeys = Object.keys(propInfo.type.options), + propSelectOptionsValues = Object.values(propInfo.type.options); + + for(var i = 0; i < propSelectOptionsKeys.length; i++) { + var propSelectOption = document.createElement('option'); + propSelectOption.value = propSelectOptionsKeys[i]; + propSelectOption.textContent = propSelectOptionsValues[i]; + propSelectOption.selected = (effectValue.values[propInfo.name] || propInfo.default) === propSelectOptionsKeys[i]; + propSelectInput.appendChild(propSelectOption); + } + break; + + case 'colour': + var propColour = document.createElement('label'); + propColour.classList.add('ye-applet-editor-properties-property-colour'); + propWrap.appendChild(propColour); + + var propColourInput = document.createElement('input'); + propColourInput.type = 'color'; + propColourInput.value = '#' + (effectValue.values[propInfo.name] || 0).toString(16).padStart(6, '0'); + propColourInput.classList.add('ye-applet-editor-properties-property-colour-input'); + propColourInput.onchange = function() { + effectValue.values[propInfo.name] = parseInt(propColourInput.value.substring(1), 16); + redraw(); + }; + propColour.appendChild(propColourInput); + break; + + case 'string': + var propString = document.createElement('label'); + propString.classList.add('ye-applet-editor-properties-property-string'); + propWrap.appendChild(propString); + + var propStringInput = document.createElement('input'); + propStringInput.type = 'text'; + + if(propInfo.type.min) + propStringInput.minLength = propInfo.type.min; + if(propInfo.type.max) + propStringInput.maxLength = propInfo.type.max; + + propStringInput.value = effectValue.values[propInfo.name] || ''; + propStringInput.classList.add('ye-applet-editor-properties-property-string-input'); + propStringInput.onchange = function() { + effectValue.values[propInfo.name] = propStringInput.value; + redraw(); + }; + propString.appendChild(propStringInput); + break; + + case 'gradient': + if(!effectValue.values[propInfo.name]) + effectValue.values[propInfo.name] = {}; + + var propGrad = document.createElement('div'); + propGrad.classList.add('ye-applet-editor-properties-property-gradient'); + propWrap.appendChild(propGrad); + + var propGradPreview = document.createElement('div'); + propGradPreview.classList.add('ye-applet-editor-properties-property-gradient-preview'); + propGrad.appendChild(propGradPreview); + + var propGradDirection = document.createElement('div'); + propGradDirection.classList.add('ye-applet-editor-properties-property-gradient-direction'); + propGrad.appendChild(propGradDirection); + + var propGradDirectionCircle = document.createElement('div'); + propGradDirectionCircle.classList.add('ye-applet-editor-properties-property-gradient-direction-circle'); + propGradDirection.appendChild(propGradDirectionCircle); + + var propGradDirectionCircleValue = document.createElement('div'); + propGradDirectionCircleValue.classList.add('ye-applet-editor-properties-property-gradient-direction-circle-value'); + propGradDirectionCircle.appendChild(propGradDirectionCircleValue); + + var propGradDirectionCircleIndicator = document.createElement('div'); + propGradDirectionCircleIndicator.classList.add('ye-applet-editor-properties-property-gradient-direction-circle-indicator'); + propGradDirectionCircleValue.appendChild(propGradDirectionCircleIndicator); + + var propGradDirectionValue = document.createElement('input'); + propGradDirectionValue.classList.add('ye-applet-editor-properties-property-gradient-direction-input'); + propGradDirectionValue.type = 'number'; + propGradDirectionValue.min = 0; + propGradDirectionValue.max = 359; + propGradDirectionValue.value = 0; + propGradDirection.appendChild(propGradDirectionValue); + + var propGradRedrawPreview = function() { + propGradPreview.style.backgroundImage = ytknsEditorGradientCSS(effectValue.values[propInfo.name]); + }; + + var propGradDirectionSet = function(val) { + val = parseInt(val); + effectValue.values[propInfo.name].d = val; + if(propGradDirectionValue.value != val) + propGradDirectionValue.value = val; + propGradDirectionCircleValue.style.transform = 'rotate(' + val + 'deg)'; + propGradRedrawPreview(); + }; + + propGradDirectionSet(effectValue.values[propInfo.name].d || 0); + + propGradDirectionValue.onchange = function() { + propGradDirectionSet(propGradDirectionValue.value); + }; + + var propGradDirectionCircleMouseEvent = function(ev) { + if((ev.buttons & 1) < 1) + return; + + var rect = ev.target.getBoundingClientRect(), + y = ev.layerY - (rect.height / 2), + x = ev.layerX - (rect.width / 2), + deg = Math.atan2(x, y) * 180 / Math.PI, + val = 180 - parseInt(deg); + + propGradDirectionSet(val); + }; + + propGradDirectionCircle.onmousedown = propGradDirectionCircleMouseEvent; + propGradDirectionCircle.onmousemove = propGradDirectionCircleMouseEvent; + + var propGradPoints = document.createElement('div'); + propGradPoints.classList.add('ye-applet-editor-properties-property-gradient-points'); + propGrad.appendChild(propGradPoints); + + var propGradPointAdd = function(point) { + if(!point) + point = {}; + if((typeof point.c).toLowerCase() === 'undefined') + point.c = parseInt(0xFFFFFF * Math.random()); + if((typeof point.o).toLowerCase() === 'undefined') + point.o = parseInt(100 * Math.random()); + + var propGradPoint = document.createElement('div'); + propGradPoint.classList.add('ye-applet-editor-properties-property-gradient-points-point'); + propGradPoints.appendChild(propGradPoint); + + var propGradPointActions = document.createElement('div'); + propGradPointActions.classList.add('ye-applet-editor-properties-property-gradient-points-point-actions'); + propGradPoint.appendChild(propGradPointActions); + + var propGradPointActionsAdd = function(name, title, action) { + var propGradPointAction = document.createElement('div'); + propGradPointAction.classList.add('ye-applet-editor-properties-property-gradient-points-point-actions-action'); + propGradPointAction.classList.add('ye-applet-editor-properties-property-gradient-points-point-actions-action--' + name); + propGradPointAction.title = title; + propGradPointAction.onclick = function() { if(action) action(); }; + propGradPointActions.appendChild(propGradPointAction); + }; + + propGradPointActionsAdd('delete', 'Delete', function() { + var index = effectValue.values[propInfo.name].p.indexOf(point); + if(index < 0) + return; + effectValue.values[propInfo.name].p.splice(index, 1); + propGradPoints.removeChild(propGradPoint); + propGradRedrawPreview(); + }); + + var propGradPointColour = document.createElement('label'); + propGradPointColour.classList.add('ye-applet-editor-properties-property-gradient-points-point-colour'); + propGradPoint.appendChild(propGradPointColour); + + var propGradPointColourValue = document.createElement('input'); + propGradPointColourValue.type = 'color'; + propGradPointColourValue.value = '#' + point.c.toString(16).padStart(6, '0'); + propGradPointColourValue.classList.add('ye-applet-editor-properties-property-gradient-points-point-colour-value'); + propGradPointColourValue.onchange = function() { + point.c = parseInt(propGradPointColourValue.value.substring(1), 16); + propGradPointColour.style.backgroundColor = propGradPointColourValue.value; + propGradRedrawPreview(); + }; + propGradPointColour.appendChild(propGradPointColourValue); + propGradPointColour.style.backgroundColor = propGradPointColourValue.value; + + var propGradPointOffset = document.createElement('input'); + propGradPointOffset.type = 'range'; + propGradPointOffset.classList.add('ye-applet-editor-properties-property-gradient-points-point-offset-range'); + propGradPointOffset.value = 0; + propGradPointOffset.min = 0; + propGradPointOffset.max = 100; + propGradPoint.appendChild(propGradPointOffset); + + var propGradPointOffsetNum = document.createElement('input'); + propGradPointOffsetNum.type = 'number'; + propGradPointOffsetNum.classList.add('ye-applet-editor-properties-property-gradient-points-point-offset-numeric'); + propGradPointOffsetNum.value = 0; + propGradPointOffsetNum.min = 0; + propGradPointOffsetNum.max = 100; + propGradPoint.appendChild(propGradPointOffsetNum); + + var propGradPointOffsetIsSetting = false, + propGradPointOffsetSet = function(val) { + if(propGradPointOffsetIsSetting) + return; + propGradPointOffsetIsSetting = true; + + val = parseInt(val); + propGradPointOffset.value = propGradPointOffsetNum.value = point.o = val; + propGradRedrawPreview(); + + propGradPointOffsetIsSetting = false; + }; + + propGradPointOffsetSet(point.o); + propGradPointOffset.onchange = function() { + propGradPointOffsetSet(propGradPointOffset.value); + }; + propGradPointOffsetNum.onchange = function() { + propGradPointOffsetSet(propGradPointOffsetNum.value); + }; + }; + + var propGradPointAddButton = document.createElement('button'); + propGradPointAddButton.classList.add('ye-applet-editor-properties-property-gradient-points-add'); + propGradPointAddButton.onclick = function() { + var point = {}; + if(!effectValue.values[propInfo.name].p) + effectValue.values[propInfo.name].p = []; + effectValue.values[propInfo.name].p.push(point); + propGradPointAdd(point); + propGradRedrawPreview(); + }; + propGradDirection.appendChild(propGradPointAddButton); + + var propGradPointAddButtonIcon = document.createElement('div'); + propGradPointAddButtonIcon.classList.add('ye-applet-editor-properties-property-gradient-points-add-icon'); + propGradPointAddButton.appendChild(propGradPointAddButtonIcon); + + var propGradPointAddButtonText = document.createElement('div'); + propGradPointAddButtonText.classList.add('ye-applet-editor-properties-property-gradient-points-add-text'); + propGradPointAddButtonText.textContent = 'Add gradient point'; + propGradPointAddButton.appendChild(propGradPointAddButtonText); + + if(effectValue.values[propInfo.name].p) + for(var i = 0; i < effectValue.values[propInfo.name].p.length; i++) + propGradPointAdd(effectValue.values[propInfo.name].p[i]); + break; + + default: + var propNone = document.createElement('div'); + propNone.classList.add('ye-applet-editor-properties-property-none'); + propNone.textContent = 'There is no handler available for this property type.'; + propWrap.appendChild(propNone); + break; + } + + var propDefault = document.createElement('div'); + propDefault.classList.add('ye-applet-editor-properties-property-reset'); + propDefault.title = 'Reset'; + propDefault.onclick = function() { + effectValue.values[propInfo.name] = propInfo.default; + redraw(); + }; + propWrap.appendChild(propDefault); +} + +function ytknsEditorGradientCSS(obj) { + var str = 'linear-gradient(' + (obj.d || 0).toString() + 'deg, ', + points = []; + + if(obj.p) + for(var i = 0; i < obj.p.length; i++) + points.push('#' + (obj.p[i].c || 0).toString(16).padStart(6, '0') + ' ' + (obj.p[i].o || 0).toString() + '%'); + + return str + points.join(', ') + ')'; +} + +function ytknsEditorAddSidebarButton(className, title, callback) { + var button = document.createElement('div'); + button.classList.add('ye-sidebar-buttons-button'); + button.classList.add('ye-sidebar-buttons-button--' + className); + button.title = title; + button.onclick = callback; + ytknsEditorElemSidebarButtons.appendChild(button); +} +function ytknsEditorAddSidebarButtonSeparator() { + var separator = document.createElement('div'); + separator.classList.add('ye-sidebar-buttons-separator'); + ytknsEditorElemSidebarButtons.appendChild(separator); +} + +function ytknsEditorAddSidebarEffect(effectValue) { + var effectInfo = ytknsGetEffectInfoByType(effectValue.type); + + if(!effectInfo) { + alert('Attempted to add unregistered effect.'); + return; + } + + var effect = document.createElement('div'); + effect.classList.add('ye-sidebar-effects-effect'); + ytknsEditorElemSidebarEffects.appendChild(effect); + + var effectName = document.createElement('div'); + effectName.classList.add('ye-sidebar-effects-effect-name'); + effectName.textContent = effectInfo.name; + effect.appendChild(effectName); + + var effectActions = document.createElement('div'); + effectActions.classList.add('ye-sidebar-effects-effect-actions'); + effect.appendChild(effectActions); + + ytknsEditorAddSidebarEffectAction(effectActions, 'edit', 'Edit Effect', function() { + ytknsLoadEffectEditor(effectInfo, effectValue); + }); + ytknsEditorAddSidebarEffectAction(effectActions, 'delete', 'Delete Effect', function() { + var previous = ytknsEditorMainClone(); + ytknsEditorMainShowEffectDeleteConfirm(effectInfo, function(accept) { + if(accept) { + ytknsRemoveEffectValueByType(effectValue.type); + ytknsEditorMainShowWelcome(); + } else { + ytknsEditorMainRestore(previous); + } + }); + }); +} +function ytknsEditorMainShowEffectDeleteConfirm(effectInfo, callback) { + var confirmation = document.createElement('div'), + text = confirmation.appendChild(document.createElement('div')), + actions = confirmation.appendChild(document.createElement('div')); + confirmation.classList.add('ye-main-effect-delete-confirm'); + text.classList.add('ye-main-effect-delete-confirm-text'); + text.textContent = 'Are you sure you want to delete "' + effectInfo.name + '"?'; + actions.classList.add('ye-main-effect-delete-confirm-actions'); + + var accept = document.createElement('div'); + accept.classList.add('ye-main-effect-delete-confirm-actions-action'); + accept.classList.add('ye-main-effect-delete-confirm-actions-action--accept'); + accept.textContent = 'Yes'; + accept.onclick = function() { callback(true); }; + actions.appendChild(accept); + + var deny = document.createElement('div'); + deny.classList.add('ye-main-effect-delete-confirm-actions-action'); + deny.classList.add('ye-main-effect-delete-confirm-actions-action--deny'); + deny.textContent = 'No'; + deny.onclick = function() { callback(false); }; + actions.appendChild(deny); + + ytknsEditorMainSetContainer(confirmation, 'Delete effect'); +} +function ytknsEditorAddSidebarEffectAction(container, className, title, callback) { + var action = document.createElement('div'); + action.classList.add('ye-sidebar-effects-effect-actions-action'); + action.classList.add('ye-sidebar-effects-effect-actions-action--' + className); + action.title = title; + action.onclick = callback; + container.appendChild(action); +} +function ytknsEditorUpdateSidebarEffects() { + ytknsEditorElemSidebarEffects.innerHTML = ''; + + if(ytknsZoneInfo == null || ytknsZoneInfo.effects == null || ytknsZoneInfo.effects.length < 1) { + var nothing = document.createElement('div'); + nothing.classList.add('ye-sidebar-effects-empty'); + nothing.textContent = 'You have not added any effects yet.'; + ytknsEditorElemSidebarEffects.appendChild(nothing); + } else { + for(var i = 0; i < ytknsZoneInfo.effects.length; i++) + ytknsEditorAddSidebarEffect(ytknsZoneInfo.effects[i]); + } +} + +function ytknsShowDetailsEdit() { + ytknsEditorChangeHash('details'); + + var container = document.createElement('div'); + container.classList.add('ye-main-details'); + + var fieldsElem = document.createElement('div'); + fieldsElem.classList.add('ye-main-details-fields'); + container.appendChild(fieldsElem); + + var fields = [ + { name: 'Zone ID', value: ytknsZoneInfo.id }, + { name: 'Subdomain', value: ytknsZoneInfo.name, suffix: '.' + location.host }, + { name: 'Title', value: ytknsZoneInfo.title, max: 255, edit: function(val) { + ytknsZoneInfo.title = val; + } }, + ]; + + for(var i = 0; i < fields.length; i++) { + var field = fields[i], + fieldElem = document.createElement('div'); + fieldElem.classList.add('ye-main-details-fields-field'); + + var fieldName = document.createElement('div'); + fieldName.classList.add('ye-main-details-fields-field-name'); + fieldName.textContent = field.name + ':'; + fieldElem.appendChild(fieldName); + + var fieldWrap = document.createElement('div'); + fieldWrap.classList.add('ye-main-details-fields-field-wrap'); + fieldElem.appendChild(fieldWrap); + + var fieldInput = document.createElement('input'); + fieldInput.classList.add('ye-main-details-fields-field-input'); + fieldInput.type = 'text'; + fieldInput.value = field.value; + + if(field.max) + fieldInput.maxLength = field.max; + + if(field.edit) { + var onedit = field.edit; + fieldInput.onchange = function(ev) { + onedit(ev.target.value); + }; + } else { + fieldInput.readOnly = true; + fieldElem.classList.add('ye-main-details-fields-field--readonly'); + } + + fieldWrap.appendChild(fieldInput); + + if(field.suffix) + fieldWrap.appendChild(document.createTextNode(field.suffix)); + + fieldsElem.appendChild(fieldElem); + } + + ytknsEditorMainSetContainer(container, 'Zone information'); +} + +function ytknsEditorMainGetTitle() { + return ytknsEditorElemMainTitle.textContent; +} +function ytknsEditorMainSetTitle(title) { + ytknsEditorElemMainTitle.textContent = title; +} +function ytknsEditorMainGetContainer() { + return ytknsEditorElemMainContainer.firstChild; +} +function ytknsEditorMainSetContainer(child, title) { + if(title) + ytknsEditorMainSetTitle(title); + + ytknsEditorElemMainContainer.innerHTML = ''; + ytknsEditorElemMainContainer.appendChild(child); +} +function ytknsEditorMainClone() { + return { + 'title': ytknsEditorMainGetTitle(), + 'content': ytknsEditorMainGetContainer().cloneNode(true), + }; +} +function ytknsEditorMainRestore(clone) { + ytknsEditorMainSetContainer(clone.content, clone.title); +} + +function ytknsEditorMainShowWelcome() { + var welcome = document.createElement('div'); + welcome.classList.add('ye-main-welcome'); + + var welcomeH1 = document.createElement('h1'); + welcomeH1.classList.add('ye-main-welcome-h1'); + welcomeH1.textContent = 'Welcome!'; + welcome.appendChild(welcomeH1); + + var welcomeP = document.createElement('p'); + welcomeP.classList.add('ye-main-welcome-p'); + welcomeP.textContent = 'Select a tool in the sidebar to get started.'; + welcome.appendChild(welcomeP); + + ytknsEditorMainSetContainer(welcome, 'Welcome to the YTKNS Editor'); +} + +function ytknsEditorMainShowEffectList() { + ytknsEditorChangeHash('add'); + + var container = document.createElement('div'); + container.classList.add('ye-applet-effects'); + + var effects = document.createElement('div'); + effects.classList.add('ye-applet-effects-list'); + container.appendChild(effects); + + for(var i = 0; i < ytknsEditorEffects.length; i++) { + var effectInfo = ytknsEditorEffects[i], + effectValue = ytknsGetEffectValueByType(effectInfo.type), + effectElement = document.createElement('div'), + effectUsed = effectValue !== null; + effectElement.classList.add('ye-applet-effects-list-item'); + + if(effectUsed) + effectElement.classList.add('ye-applet-effects-list-item--used'); + + var effectName = document.createElement('div'); + effectName.classList.add('ye-applet-effects-list-item-name'); + effectName.textContent = effectInfo.name; + effectElement.appendChild(effectName); + + var effectActions = document.createElement('div'); + effectActions.classList.add('ye-applet-effects-list-item-actions'); + effectElement.appendChild(effectActions); + + if(!effectUsed) { + var effectActionAdd = document.createElement('div'); + effectActionAdd.classList.add('ye-applet-effects-list-item-actions-action'); + effectActionAdd.classList.add('ye-applet-effects-list-item-actions-action--add'); + effectActionAdd.title = 'Add Effect'; + (function() { // javascript is very cool and good + var effectInfoCopy = effectInfo; + effectActionAdd.onclick = function() { + var effectValueNew = ytknsEditorAddNewEffect(effectInfoCopy); + ytknsEditorUpdateSidebarEffects(); + ytknsLoadEffectEditor(effectInfoCopy, effectValueNew); + }; + })(); + effectActions.appendChild(effectActionAdd); + } + + effects.appendChild(effectElement); + } + + ytknsEditorMainSetContainer(container, 'Available effects'); +} + +function ytknsEditorAddNewEffect(effectInfo) { + var effectValue = { + type: effectInfo.type, + values: {}, + }; + + for(var i = 0; i < effectInfo.props.length; i++) { + var propInfo = effectInfo.props[i]; + effectValue.values[propInfo.name] = propInfo.default || ''; + } + + ytknsZoneInfo.effects.push(effectValue); + + return effectValue; +} + +function ytknsEditorConfirmReloadZoneInfo(zoneId) { + if(confirm('Are you sure you want to undo any changes you\'ve made?')) + ytknsEditorReloadZoneInfo(zoneId); +} +function ytknsEditorReloadZoneInfo(zoneId, onComplete) { + ytknsEditorChangeHash(''); + ytknsEditorMainShowWelcome(); + + ytknsEditorLoadEffects(function(effects) { + ytknsEditorEffects = effects; + + ytknsLoadZoneInfo(zoneId, function(zoneInfo) { + ytknsZoneInfo = zoneInfo; + ytknsEditorUpdateSidebarEffects(); + + if(onComplete) + onComplete(); + }); + }); +} + +function ytknsEditorSwitchString(str) { + switch(str) { + case '': + ytknsEditorMainShowWelcome(); + break; + + case 'add': + ytknsEditorMainShowEffectList(); + break; + + case 'details': + ytknsShowDetailsEdit(); + break; + + default: + var openEffectInfo = ytknsGetEffectInfoByType(str); + + if(openEffectInfo) { + var openEffectValue = ytknsGetEffectValueByType(str); + ytknsLoadEffectEditor(openEffectInfo, openEffectValue); + } + break; + } +} + +function ytknsEditorHashChange(ev) { + if(!ytknsEditorIgnoreHashChange) + ytknsEditorSwitchString(location.hash.substring(1)); + + ytknsEditorIgnoreHashChange = false; +} + +function ytknsEditorMain(container, zoneId, editorToken, uploadToken) { + if(navigator.userAgent.match(/mobile/gi) && !confirm("The editor is not designed to be used on phones whatsoever.\r\nHit OK to continue anyway or cancel to whereever you came from.")) { + history.go(-1); + return; + } + + window.onhashchange = ytknsEditorHashChange; + window.onbeforeunload = ytknsEditorBeforeUnload; + ytknsEditorToken = editorToken; + ytknsEditorUploadToken = uploadToken; + + container.innerHTML = ''; + container.classList.add('ye'); + + ytknsEditorElemSidebar = container.appendChild(document.createElement('div')); + ytknsEditorElemSidebar.classList.add('ye-sidebar'); + + ytknsEditorElemSidebarButtons = ytknsEditorElemSidebar.appendChild(document.createElement('div')); + ytknsEditorElemSidebarButtons.classList.add('ye-sidebar-buttons'); + + ytknsEditorAddSidebarButton('save', 'Save', function() { + ytknsSaveZoneInfo(ytknsZoneInfo, function(res) { + if(res.msg) + alert(res.msg); + }); + }); + ytknsEditorAddSidebarButton('cancel', 'Cancel', function() { location.assign('/zones?f=my'); }); + ytknsEditorAddSidebarButton('reset', 'Undo Changes', function() { ytknsEditorConfirmReloadZoneInfo(zoneId); }); + ytknsEditorAddSidebarButton('edit', 'Edit Details', function() { ytknsShowDetailsEdit(); }); + ytknsEditorAddSidebarButtonSeparator(); + ytknsEditorAddSidebarButton('preview', 'Show Preview', function() { ytknsEditorPreview(ytknsZoneInfo); }); + ytknsEditorAddSidebarButton('live', 'View Live', function() { window.open('//%1.%2'.replace('%2', location.host).replace('%1', ytknsZoneInfo.name)); }); + ytknsEditorAddSidebarButtonSeparator(); + ytknsEditorAddSidebarButton('add', 'Add Effect', function() { ytknsEditorMainShowEffectList(); }); + + ytknsEditorElemSidebarEffects = ytknsEditorElemSidebar.appendChild(document.createElement('div')); + ytknsEditorElemSidebarEffects.classList.add('ye-sidebar-effects'); + ytknsEditorUpdateSidebarEffects(); + + ytknsEditorElemMain = container.appendChild(document.createElement('div')); + ytknsEditorElemMain.classList.add('ye-main'); + + ytknsEditorElemMainTitle = ytknsEditorElemMain.appendChild(document.createElement('div')); + ytknsEditorElemMainTitle.classList.add('ye-main-title'); + + ytknsEditorElemMainContainer = ytknsEditorElemMain.appendChild(document.createElement('div')); + ytknsEditorElemMainContainer.classList.add('ye-main-container'); + + var goToEditor = location.hash.substring(1); + + ytknsEditorReloadZoneInfo(zoneId, function() { + ytknsEditorSwitchString(goToEditor); + }); +} diff --git a/public/assets/link.png b/public/assets/link.png new file mode 100644 index 0000000000000000000000000000000000000000..25eacb7c2524142262d68bf729c5e2b61adfd6d4 GIT binary patch literal 343 zcmV-d0jU0oP)$`dXYaZs9=SbAto%g@>T~?_bH&lTUn@`uo|1bXE{eSR(AO)ESb=V4`uk}mK|39Px&03WLbv~pzk+s7D@lK^ zn+aB+sp)&Y_x-B3>;6ywU--WQNUr<8>TU0P-|L#1U&;A)67w(+> pDf@fM7q9#F25QXo3rUI;002ro52U44e~JJA002ovPDHLkV1l;_q@Mr) literal 0 HcmV?d00001 diff --git a/public/assets/no-screenshot.jpg b/public/assets/no-screenshot.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6834d5fcb706ebd03fd3e8a3521b2e262b0a23d1 GIT binary patch literal 15264 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 literal 0 HcmV?d00001 diff --git a/public/assets/page_white_edit.png b/public/assets/page_white_edit.png new file mode 100644 index 0000000000000000000000000000000000000000..b93e77600def75c9a144d3d0a5088a62c02cbb0b GIT binary patch literal 618 zcmV-w0+s!VP)$>5Y&axjp2O=VLu>*f>1L;s0)kkvKC!*u?s6CVL=HJ6oP~pNfZc; zsKr=bq;7MITw8NXw{SZm%59TId2x_9BQ zV86`NuvGI!>o^V!Na!=$7GJE{Cq`b+XwknM{UcGHFTTfmuS+ zm-zYC!P3+zmY;SG$?!fYkOih`QYaLxyF}A86h$GGN}kFj)_o*0e zjPMP%zTG7FYMAfO2Nn1D`D0Cj?Wl>5q%@CE10nX)KxpNmwk+!IWkzywiYD( zqUXiYYIq3qcRyMGJ;IY`(Gz~E$J$zu2+R{)xGlE*88b3WK6V*J>}2iPY1HH|tER0W z_+^^FdppY?o)Gt5M2`%xwRDH@R3G}^i1l4|6uchm0X0f!@&YdVLB5K&dd7Rv{)DXX zt^&vP;}kqj3f>94j+4xd93>s|Q!Ezi>?r8(Il$P}PFxSqu{d*!Y%*#cX(R0f|Juz# z3o0_xI14Al->1uky@W-rCI_%l&>OOAS;jTeL{ZSdz-%)SMH9tDF;N4B6%j=d15J&5qy`F#vB?Ar zqS1nH@%ny_XSI*Y>) z1f5QYdmzT>YciP<3WehS<{GovEaLGv27>{*-7f0&I$yJ^L%ZGPv1YT$V|u;*+ZCWz ztHI~CDVsuy($SfR6-`N~K?9GTB#l%%0h7 z-q`K-y~E)+s8lMyTrPL8^_pUo)9G|SluG5pPqw6!LJB_PzyJUM07*qoM6N<$f^=yZ AYybcN literal 0 HcmV?d00001 diff --git a/public/assets/page_white_magnify.png b/public/assets/page_white_magnify.png new file mode 100644 index 0000000000000000000000000000000000000000..f6b74cc40f82fc83e4dfa6e9647ccc1b34e6ed7e GIT binary patch literal 554 zcmV+_0@eMAP)Vb2f>2}Fa82O3m(Ob=t*sniin`NpInLyMJgI`saru@YOPfh zy4g0#G*cV!#N%;Gq9_VH9?v%kjS3Rb1j8^;C={$Gp=lbj z*(?%?geA!5^Pok%UauwjA)v4g2`HedDw4_Mk4hhBQt?e7YJ5(hcj|3dNu^TOPGnjB zTTsqd3GIZ=Bb`n=7no)dflv&K(lsWw?lH6T1Yht0F9qgIuzh}ym0%n<3d3EBWB*pg z+G!I0lbAEXyd>k|QNuwr4=KX1D+tLPv)j@C1=N4sA4NF9A>HcO3G47*Y6!+SrUH-7 z1hb;^#S=r|`aMh>J#dWruAEf}gcR(DRUC`ZUev&$Sbh0SgLiTXeeHEU<$_YV;9281 zym`igIE%Sm8DpDw7@71Tv^EB5xSdUR*0$Mqp+Wq8OoaZtOg52&)zZ;;M=7#C1Yd6x svjx>8ad4e2x|*xHHwRjcjs6zA0XLDUqKT6dS^xk507*qoM6N<$fRq1}l<=psl5*5Xz9i;M}s*NP=ugs7Q#8Z;Dyx|}!`#}xw_C3!B-yaPC&0j)XcpuX@rNfq|q}N(wJOjA& z>u+z?dfJEuLePrqzy!)73pvLjxk4d6XNZt?hm_iYES{i}J5y3l?}PPNYDBR7oPc~6 zL^d)Bi4Q2L3pnp!nFxN9c2E+=@XAl&+;2m6a~kZj1r3Mz3C=hmUG<{+vWR@t4q?fJ zhFc(ozZD#Mx`^Q~g1v=K6!QnfuqyD4>U4EjF0eamL}Jx| z%&`kR-H+3GBYr*Qx}frLU4`%n9(`uSomzw)t%%NagXkA*R5Mbv9VLDp1wMo$cOMa~ s3Wm%r7^bwK$2$}-<~D8p`#1iScU4^XCLAA~0ssI207*qoM6N<$g3sK(Qvd(} literal 0 HcmV?d00001 diff --git a/public/assets/shared.css b/public/assets/shared.css new file mode 100644 index 0000000..8460385 --- /dev/null +++ b/public/assets/shared.css @@ -0,0 +1,68 @@ +* { + position: relative; + box-sizing: border-box; + outline-style: none !important; +} + +@keyframes SharedAnimation_Spin360 { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +html, +body, +#container { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + overflow: hidden; +} + +.NewCreatePageEffect_Main { + font-family: sans-serif; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.ZoomText { + font-family: sans-serif; + position: absolute; + left: 0; + top: 0; + width: 810px; + z-index: 10; + text-align: center; +} +.ZoomText_Child { + position: absolute; + width: 810px; +} + +.BackgroundImage { + position: absolute; + width: 100%; + height: 100%; + z-index: 0; +} +.BackgroundImage.Cover { + width: 10000px; + height: 10000px; + top: -5000px; + left: -5000px; +} + +.ForegroundImage { + position: absolute; + overflow: hidden; + width: 100%; + height: 100%; + z-index: 100; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/public/assets/shared.js b/public/assets/shared.js new file mode 100644 index 0000000..8980ffa --- /dev/null +++ b/public/assets/shared.js @@ -0,0 +1,64 @@ +var _paq = window._paq || []; +_paq.push(['disableCookies']); +_paq.push(['trackPageView']); +_paq.push(['enableLinkTracking']); +_paq.push(['enableHeartBeatTimer']); +(function() { + _paq.push(['setTrackerUrl', '//uiharu.railgun.sh/mtm']); + _paq.push(['setSiteId', 'zlwmGOjMBk5J']); + var g = document.createElement('script'); + g.type = 'text/javascript'; g.async = true; + g.defer = true; g.src = '//uiharu.railgun.sh/mtm.js'; + document.head.appendChild(g); +})(); + +function synchroniseBackgroundWithAudio(bgi, target) { + var cnt = document.getElementById(target || 'BackgroundImage') || document.getElementById('container'), + bga = document.getElementById('BackgroundAudio'), + rdy = false, + bgs = []; + + if(!cnt) + return; + + var computed = getComputedStyle(cnt); + + if(computed.backgroundImage && target !== 'ForegroundImage') + bgs = computed.backgroundImage.split(','); + + if(!bga) { + bgs.splice(0, 0, 'url(' + bgi + ')'); + cnt.style.backgroundImage = bgs.join(','); + return; + } + + var onReady = function(url) { + bgs.splice(0, 0, 'url(' + url + ')'); + cnt.style.backgroundImage = bgs.join(','); + bga.play(); + }; + + bga.addEventListener('canplay', function() { + rdy = true; + }); + + bga.pause(); + bga.currentTime = 0; + + var xhr = new XMLHttpRequest; + xhr.responseType = 'blob'; + xhr.onreadystatechange = function() { + if(xhr.readyState !== 4) + return; + bgi = URL.createObjectURL(xhr.response); + + if(rdy) + onReady(bgi); + else + bga.addEventListener('canplay', function() { + onReady(bgi); + }); + }; + xhr.open('GET', bgi); + xhr.send(); +} diff --git a/public/assets/spinner.gif b/public/assets/spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..3288d1035d70bb86517e2c233f1a904e41f06b29 GIT binary patch literal 3208 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%0Aj(CBEzQ%zca?5)c@w7SM1%?S!V33Z{kC^$<9 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'; + } + + if(!empty($vars['styles']) && is_array($vars['styles'])) { + foreach($vars['styles'] as $style) + $vars['head'] .= sprintf('', $style); + } + + $vars['menu_site'] = Template::renderSet('menu-site-item', [ + ['text' => 'Home', 'link' => page_url('/')], + ['text' => 'Create a Zone', 'link' => page_url('/zones/create')], + ['text' => 'Zones', 'link' => page_url('/zones')], + ]); + + $userMenu = []; + + if(UserSession::hasInstance()) { + $userName = UserSession::instance()->getUser()->getUsername(); + + $userMenu = [ + ['text' => "@{$userName}", 'link' => page_url("/@{$userName}")], + ['text' => 'My Zones', 'link' => page_url('/zones?f=my')], + ['text' => 'Settings', 'link' => page_url('/settings')], + ]; + + if(Config::get('user.invite_only', Config::TYPE_BOOL)) + $userMenu[] = ['text' => 'Invites', 'link' => page_url('/settings/invites')]; + + $userMenu[] = ['text' => 'Log out', 'link' => page_url('/auth/logout', ['s' => UserSession::instance()->getSmallToken()])]; + } else { + $userMenu = [ + ['text' => 'Log in', 'link' => page_url('/auth/login')], + ['text' => 'Register', 'link' => page_url('/auth/register')], + ]; + } + + $vars['menu_user'] = Template::renderSet('menu-user-item', $userMenu); + + return Template::renderRaw('header', $vars); +} + +function html_footer(array $vars = []): string { + $vars['footer_took'] = number_format(microtime(true) - YTKNS_STARTUP, 5); + $vars['footer_year'] = date('Y'); + + $scripts = $vars['scripts'] ?? null; + $vars['scripts'] = ''; + + if(!empty($scripts) && is_array($scripts)) { + foreach($scripts as $script) + $vars['scripts'] .= sprintf('', $script); + } + + return Template::renderRaw('footer', $vars); +} + +function html_information(string $message, string $title = 'Information', ?string $redirect = null, int $redirectTimeout = 2): string { + $html = html_header([ + 'title' => $title . ' - YTKNS', + 'redirect' => $redirect, + 'redirect_timeout' => $redirectTimeout, + ]); + + if(!empty($redirect)) + $message .= Template::renderRaw('information-redirect', [ + 'info_redirect' => $redirect, + ]); + + $html .= Template::renderRaw('information', [ + 'info_title' => $title, + 'info_content' => $message, + ]); + + $html .= html_footer(); + + return $html; +} + +function html_pagination(int $pages, int $current, string $urlFormat): string { + $html = ''; +} + +if(!empty($_COOKIE['ytkns_login']) && is_string($_COOKIE['ytkns_login'])) { + try { + $session = UserSession::byToken($_COOKIE['ytkns_login']); + $session->update(); + $session->setInstance(); + + if($session->getBump()) + setcookie('ytkns_login', $session->getToken(), $session->getExpires(), '/', '.' . Config::get('domain.main'), false, true); + + unset($session); + } catch(UserSessionNotFoundException $ex) {} +} + +$zoneName = strtolower(substr($_SERVER['HTTP_HOST'], 0, -strlen(Config::get('domain.main')) - 1)); + +if(!empty($zoneName)) { + $redirect = ZoneRedirect::find($zoneName); + + if($redirect !== null) { + $redirect->execute(); + return; + } + + try { + $zoneInfo = Zone::byName($zoneName); + } catch(ZoneNotFoundException $ex) { + http_response_code(404); + echo html_header(['title' => 'Zone not found!']); + Template::render('zones/none', [ + 'zone_create_url' => page_url('/zones/create', ['name' => $zoneName]), + ]); + echo html_footer(); + return; + } + + if(!empty($_GET['_refresh_screenshot'])) { + header('Location: /'); + $zoneInfo->takeScreenshot(); + return; + } + + ZoneView::increment($zoneInfo, $_SERVER['REMOTE_ADDR']); + + echo (string)$zoneInfo->getPageBuilder(true); + return; +} + +$reqMethod = $_SERVER['REQUEST_METHOD']; +$reqPath = '/' . trim(parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH), '/'); + +if($reqPath === '/') { + echo html_header(); + Template::render('home'); + echo html_footer(); + return; +} + +if(substr($reqPath, 0, 4) === '/ss/') { + header('X-Accel-Redirect: /assets/no-screenshot.jpg'); + return; +} + +if(preg_match('#^/@([A-Za-z0-9-_]+)$#', $reqPath, $matches)) { + try { + $profile = User::forProfile($matches[1]); + } catch(Exception $ex) { + http_response_code(404); + echo html_header(['title' => 'User not found - YTKNS']); + Template::render('profile/notfound'); + echo html_footer(); + return; + } + + $zones = Zone::byUser($profile, 'zone_views', false); + + if(count($zones) < 1) { + $profileZones = Template::renderRaw('profile/zone-none'); + } else { + $profileZones = []; + + foreach($zones as $zone) + $profileZones[] = [ + 'zone_id' => $zone->getId(), + 'zone_name' => $zone->getName(), + 'zone_title' => $zone->getTitle(), + 'zone_views' => number_format($zone->getViews()), + 'zone_url' => $zone->getUrl(), + 'zone_screenshot' => $zone->getScreenshotUrl(), + ]; + + $profileZones = Template::renderRaw('profile/zone-list', [ + 'zone_items' => Template::renderSet('profile/zone-item', $profileZones), + ]); + } + + echo html_header(['title' => $profile->username . ' @ YTKNS']); + Template::render('profile/index', [ + 'profile_username' => $profile->username, + 'profile_zones' => $profileZones, + ]); + echo html_footer(); + return; +} + +if($reqPath === '/zones') { + $zoneFilter = filter_input(INPUT_GET, 'f'); + + if($zoneFilter === 'my' && !UserSession::hasInstance()) { + echo html_information('You must be logged in to do this.'); + return; + } + + $zoneTake = 20; + $zonePage = max(filter_input(INPUT_GET, 'page', FILTER_SANITIZE_NUMBER_INT) - 1, 0); + $zoneOffset = $zonePage * $zoneTake; + $zoneSort = filter_input(INPUT_GET, 'sort'); + $zoneSortDesc = filter_input(INPUT_GET, 'asc', FILTER_SANITIZE_NUMBER_INT) < 1; + $zoneOrderings = [ + 'creation' => 'zone_created', + 'updated' => 'zone_updated', + 'views' => 'zone_views', + 'user' => 'user_id', + ]; + + if(!array_key_exists($zoneSort, $zoneOrderings)) { + switch($zoneFilter) { + case 'my': + $zoneSort = 'creation'; + break; + + default: + $zoneSort = 'views'; + $zoneSortDesc = false; + break; + } + } + + switch($zoneFilter) { + case 'my': + $zones = Zone::byUser(UserSession::instance()->getUser(), $zoneOrderings[$zoneSort], $zoneSortDesc, $zoneTake, $zoneOffset); + $zoneListTemplate = 'zones/my-item'; + $zoneListTitle = 'My Zones'; + break; + + default: + $zones = Zone::all($zoneOrderings[$zoneSort], $zoneSortDesc, $zoneTake, $zoneOffset); + $zoneListTemplate = 'zones/item'; + $zoneListTitle = 'Zones'; + $zoneFilter = ''; + break; + } + + $zoneCount = Zone::count(); + $zonePages = ceil($zoneCount / $zoneTake); + $zoneList = []; + + foreach($zones as $zone) + $zoneList[] = [ + 'zone_id' => $zone->getId(), + 'zone_name' => $zone->getName(), + 'zone_title' => $zone->getTitle(), + 'zone_views' => number_format($zone->getViews()), + 'zone_url' => $zone->getUrl(), + 'zone_edit_url' => page_url(sprintf('/zones/%d', $zone->getId())), + 'zone_delete_url' => page_url(sprintf('/zones/%d/delete', $zone->getId())), + 'zone_screenshot' => $zone->getScreenshotUrl(), + 'zone_user_url' => page_url('/@' . $zone->getUser()->getUsername()), + 'zone_user_name' => $zone->getUser()->getUsername(), + ]; + + $zoneSortings = ''; + + foreach(array_keys($zoneOrderings) as $order) { + $orderCurrent = $zoneSort === $order; + $zoneSortings .= sprintf( + '%3$s%5$s', + $order, $orderCurrent ? $zoneSortDesc : !$zoneSortDesc, ucfirst($order), + $orderCurrent ? ' zones-sorts-current' : '', + $orderCurrent ? sprintf( + ' %s', + $zoneSortDesc ? 'down' : 'up', + $zoneSortDesc ? 'Descending' : 'Ascending' + ) : '', $zoneFilter + ); + } + + echo html_header(['title' => $zoneListTitle . ' - YTKNS']); + Template::render('zones/list', [ + 'zone_list_title' => $zoneListTitle, + 'zone_list' => Template::renderSet($zoneListTemplate, $zoneList), + 'zone_sortings' => $zoneSortings, + ]); + echo html_pagination($zonePages, $zonePage + 1, sprintf('/zones?f=%s&sort=%s&asc=%d&page=%%s', $zoneFilter, $zoneSort, !$zoneSortDesc)); + echo html_footer(); + return; +} + +if($reqPath === '/zones/create') { + if(!UserSession::hasInstance()) { + http_response_code(403); + echo html_information('You must be logged in to do this.'); + return; + } + + $createToken = UserSession::instance()->getSmallToken(4); + + if($reqMethod === 'POST') { + $zoneToken = filter_input(INPUT_POST, 'zone_token'); + $zoneName = filter_input(INPUT_POST, 'zone_subdomain'); + $zoneTitle = filter_input(INPUT_POST, 'zone_title'); + + if($zoneToken !== $createToken) { + $createError = 'Invalid request.'; + } else { + if(empty($zoneName) || empty($zoneTitle)) { + $createError = 'Please fill in all fields.'; + } else { + if(!Zone::validName($zoneName)) { + $createError = 'Name contains invalid characters or is too long.'; + } elseif(ZoneRedirect::exists($zoneName) || Zone::exists($zoneName)) { + $createError = 'A Zone with this name already exists.'; + } elseif(!ctype_alpha($zoneName) + || mb_strlen($zoneTitle) > 255) { + $createError = 'Invalid data.'; + } else { + $createZone = Zone::create(UserSession::instance()->getUser(), $zoneName, $zoneTitle); + $createZone->addEffect(new \YTKNS\Effects\NewlyCreatedPageEffect); + + echo html_information('Zone created!', 'Information - YTKNS', "/zones/{$createZone->getId()}"); + return; + } + } + } + } elseif($reqMethod === 'GET') { + $zoneName = filter_input(INPUT_GET, 'name'); + } + + if(isset($createError)) { + $createError = Template::renderRaw('error', [ + 'error_text' => $createError, + ]); + } + + echo html_header(['title' => 'Create a Zone - YTKNS']); + + Template::render('zones/create', [ + 'create_action' => page_url('/zones/create'), + 'create_domain' => Config::get('domain.main'), + 'create_token' => $createToken, + 'create_error' => $createError ?? '', + 'create_subdomain' => $zoneName ?? '', + 'create_title' => $zoneTitle ?? '', + ]); + + echo html_footer(); + return; +} + +if($reqPath === '/zones/_effects') { + header('Content-Type: application/json; charset=utf-8'); + + if(!UserSession::hasInstance()) { + http_response_code(403); + echo json_encode([ + 'err' => 'You must be logged in.', + ]); + return; + } + + $effects = []; + + foreach(SITE_EFFECTS as $effectClass) { + $instance = new $effectClass; + $effects[] = [ + 'type' => trim(substr($effectClass, 14, -6), '\\'), + 'name' => $instance->getEffectName(), + 'props' => $instance->getEffectProperties(), + ]; + } + + echo json_encode($effects); + return; +} + +if($reqPath === '/zones/_preview') { + if(!UserSession::hasInstance()) { + http_response_code(403); + echo html_information('You must be logged in to do this.'); + return; + } + + if($reqMethod !== 'POST') { + http_response_code(405); + echo html_information('Must be a POST request.'); + return; + } + + $zoneToken = filter_input(INPUT_POST, 'zone_token'); + + if($zoneToken !== UserSession::instance()->getSmallToken(10)) { + http_response_code(403); + echo html_information('Invalid token.'); + return; + } + + $zoneTitle = filter_input(INPUT_POST, 'zone_title'); + $zoneEffects = filter_input(INPUT_POST, 'zone_effect', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY); + + try { + $zoneInfo = new Zone; + $zoneInfo->setTitle($zoneTitle); + $zoneInfo->setPassiveMode(); + + foreach($zoneEffects as $effectName => $effectValues) { + $effectInfo = ZoneEffect::effectClass($effectName); + $effectInfo->setEffectParams($effectValues); + $zoneInfo->addEffect($effectInfo); + } + } catch(ZoneInvalidTitleException $ex) { + http_response_code(400); + echo html_information('Invalid title.'); + return; + } catch(ZoneEffectClassNotFoundException $ex) { + http_response_code(400); + echo html_information(sprintf('Invalid effect name: %s.', $ex->getMessage())); + return; + } catch(PageEffectException $ex) { + http_response_code(400); + echo html_information(sprintf('%s: %s', $effectName ?? '', $ex->getMessage())); + return; + } catch(Exception $ex) { + http_response_code(500); + echo html_information(sprintf('Failed to generate preview.
%s: %s', get_class($ex), $ex->getMessage())); + return; + } + + echo (string)$zoneInfo->getPageBuilder(); + return; +} + +if(preg_match('#^/zones/([0-9]+)/delete$#', $reqPath, $matches)) { + if(!UserSession::hasInstance()) { + http_response_code(403); + echo html_information('You must be logged in to do this.'); + return; + } + + try { + $zoneInfo = Zone::byId($matches[1]); + } catch(ZoneNotFoundException $ex) { + http_response_code(404); + echo html_information('This zone doesn\'t exist.'); + return; + } + + if($zoneInfo->getUserId() !== UserSession::instance()->getUserId()) { + http_response_code(403); + echo html_information('You aren\'t allowed to touch this zone.'); + return; + } + + $deleteToken = UserSession::instance()->getSmallToken(20); + + if($reqMethod === 'POST') { + if(filter_input(INPUT_POST, 'zone_token') !== $deleteToken) { + http_response_code(403); + echo html_information('Invalid token.'); + return; + } + + header('Location: /zones?f=my'); + $zoneInfo->delete(); + return; + } + + echo html_header(['title' => sprintf('Deleting zone %s - YTKNS', $zoneInfo->getName())]); + Template::render('zones/delete', [ + 'delete_zone_id' => $zoneInfo->getId(), + 'delete_zone_name' => $zoneInfo->getName(), + 'delete_zone_token' => $deleteToken, + ]); + echo html_footer(); + return; +} + +if(preg_match('#^/zones/([0-9]+)$#', $reqPath, $matches)) { + if(!UserSession::hasInstance()) { + http_response_code(403); + echo html_information('You must be logged in to do this.'); + return; + } + + try { + $zoneInfo = Zone::byId($matches[1]); + } catch(ZoneNotFoundException $ex) { + http_response_code(404); + echo html_information('This zone doesn\'t exist.'); + return; + } + + if(UserSession::instance()->getUserId() !== $zoneInfo->getUserId() + && UserSession::instance()->getUserId() !== 1) { + http_response_code(403); + echo html_information('You aren\'t allowed to touch this zone.'); + return; + } + + $cssHash = hash_file('sha256', YTKNS_PUB . '/assets/editor.css'); + $jsHash = hash_file('sha256', YTKNS_PUB . '/assets/editor.js'); + + echo html_header([ + 'title' => 'Editing Zone - YTKNS', + 'styles' => [ + page_url('/assets/editor.css', ['v' => $cssHash]), + ], + ]); + Template::render('zones/edit', [ + 'edit_id' => $zoneInfo->getId(), + 'edit_token' => UserSession::instance()->getSmallToken(10), + 'edit_css_ver' => substr($cssHash, 0, 16), + 'edit_js_ver' => substr($jsHash, 0, 16), + 'upload_token' => UserSession::instance()->getSmallToken(6), + ]); + echo html_footer([ + 'scripts' => [ + page_url('/assets/editor.js', ['v' => $jsHash]), + ], + ]); + return; +} + +if(preg_match('#^/zones/([0-9]+).json$#', $reqPath, $matches)) { + header('Content-Type: application/json; charset=utf-8'); + + if(!UserSession::hasInstance()) { + http_response_code(403); + echo json_encode([ + 'err' => 'You must be logged in to do this.', + ]); + return; + } + + try { + $zoneInfo = Zone::byId($matches[1]); + } catch(ZoneNotFoundException $ex) { + http_response_code(404); + echo json_encode([ + 'err' => 'This zone doesn\'t exist', + ]); + return; + } + + if(UserSession::instance()->getUserId() !== $zoneInfo->getUserId() + && UserSession::instance()->getUserId() !== 1) { + http_response_code(403); + echo json_encode([ + 'err' => 'You aren\'t allowed to touch this zone.', + ]); + return; + } + + if($reqMethod === 'GET') { + echo $zoneInfo->toJson(true); + return; + } + + if($reqMethod !== 'POST') { + http_response_code(405); + echo json_encode([ + 'err' => 'Invalid request method.', + ]); + return; + } + + $zoneToken = filter_input(INPUT_POST, 'zone_token'); + + if($zoneToken !== UserSession::instance()->getSmallToken(10)) { + http_response_code(403); + echo json_encode([ + 'err' => 'Invalid token.', + ]); + return; + } + + $zoneId = filter_input(INPUT_POST, 'zone_id', FILTER_VALIDATE_INT); + + if($zoneId !== $zoneInfo->getId()) { + http_response_code(400); + echo json_encode([ + 'err' => 'Zone ID in POST data does not match the target zone ID.', + ]); + return; + } + + $zoneTitle = filter_input(INPUT_POST, 'zone_title'); + $zoneEffects = filter_input(INPUT_POST, 'zone_effect', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY); + + try { + $zoneInfo->setTitle($zoneTitle); + $zoneInfo->setPassiveMode(); + + if(is_array($zoneEffects)) { + foreach($zoneEffects as $effectName => $effectValues) { + $effectInfo = ZoneEffect::effectClass($effectName); + $effectInfo->setEffectParams($effectValues); + $zoneInfo->addEffect($effectInfo); + } + } + + $zoneInfo->update(['zone_title']); + + // Schedule a screenshot to be taken + $zoneInfo->queueTask('screenshot'); + } catch(ZoneInvalidTitleException $ex) { + http_response_code(400); + echo json_encode([ + 'err' => 'Invalid title.', + ]); + return; + } catch(ZoneEffectClassNotFoundException $ex) { + http_response_code(400); + echo json_encode([ + 'err' => sprintf('Invalid effect name: %s.', $ex->getMessage()), + ]); + return; + } catch(PageEffectException $ex) { + http_response_code(400); + echo json_encode([ + 'err' => sprintf('%s: %s', $effectName ?? '', $ex->getMessage()), + ]); + return; + } catch(Exception $ex) { + http_response_code(500); + echo json_encode([ + 'err' => sprintf("An unexpected error occurred.\r\n%s: %s", get_class($ex), $ex->getMessage()), + ]); + return; + } + + echo json_encode([ + 'msg' => 'Saved!', + ]); + return; +} + +if($reqPath === '/uploads') { + header('Content-Type: application/json; charset=utf-8'); + + if($reqMethod !== 'POST') { + http_response_code(405); + echo json_encode([ + 'err' => 'Unsupported request method.', + ]); + return; + } + + if(!UserSession::hasInstance()) { + http_response_code(403); + echo json_encode([ + 'err' => 'You must be logged in to upload files.', + ]); + return; + } + + if(filter_input(INPUT_POST, 'upload_token') !== UserSession::instance()->getSmallToken(6)) { + http_response_code(403); + echo json_encode([ + 'err' => 'Invalid upload token.', + ]); + return; + } + + if(empty($_FILES['upload_file']['tmp_name'])) { + http_response_code(400); + echo json_encode([ + 'err' => 'Missing file.', + ]); + return; + } + + $hash = hash_file('sha256', $_FILES['upload_file']['tmp_name']); + $existing = Upload::byHash($hash); + + if($existing !== null) { + if($existing->getDMCA() > 0) { + http_response_code(451); + echo json_encode([ + 'err' => 'This file has been removed in response to a DMCA takedown request. It may not be used.', + ]); + return; + } + + if($existing->getDeleted() > 0) { + http_response_code(404); + echo json_encode([ + 'err' => 'This file has been flagged for deletion, it cannot be reuploaded at this time.', + ]); + return; + } + + http_response_code(200); + echo json_encode([ + 'err' => 'File has been uploaded already.', + 'file' => $existing->getId(), + ]); + return; + } + + if($_FILES['upload_file']['size'] > 5242880) { + http_response_code(413); + echo json_encode([ + 'err' => 'Upload is too large.', + ]); + return; + } + + $contentType = mime_content_type($_FILES['upload_file']['tmp_name']); + + if(!in_array($contentType, ALLOWED_UPLOADS)) { + http_response_code(400); + echo json_encode([ + 'err' => sprintf('File type not allowed. (Must be %s)', implode(', ', ALLOWED_UPLOADS)), + ]); + return; + } + + try { + $upload = Upload::create(UserSession::instance()->getUser(), $_FILES['upload_file']['name'], $contentType, $hash); + } catch(UploadCreationFailedException $ex) { + http_response_code(500); + echo json_encode([ + 'err' => 'Failed to create upload record.', + ]); + return; + } + + if(!move_uploaded_file($_FILES['upload_file']['tmp_name'], $upload->getPath())) { + http_response_code(500); + echo json_encode([ + 'err' => 'Upload failed.', + ]); + return; + } + + http_response_code(201); + echo json_encode([ + 'file' => $upload->getId(), + ]); + return; +} + +if(preg_match('#^/uploads/([a-zA-Z0-9-_]{16})$#', $reqPath, $matches)) { + try { + $uploadInfo = Upload::byId($matches[1]); + } catch(UploadNotFoundException $ex) { + http_response_code(404); + return; + } + + if(!empty($_SERVER['HTTP_ORIGIN'])) { + $zoneDomain = sprintf(Config::get('domain.zone'), ''); + $originPart = substr($_SERVER['HTTP_ORIGIN'], strlen($_SERVER['HTTP_ORIGIN']) - strlen($zoneDomain)); + + if($originPart === $zoneDomain) { + header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']); + } + } + + if($_SERVER['REQUEST_METHOD'] !== 'HEAD' && $_SERVER['REQUEST_METHOD'] !== 'OPTIONS') + header('X-Accel-Redirect: /raw-uploads/' . $uploadInfo->getId()); + + header('Content-Type: ' . $uploadInfo->getType()); + header('Content-Disposition: inline; filename="' . $uploadInfo->getName() . '"'); + return; +} + +if(preg_match('#^/uploads/([a-zA-Z0-9-_]{16}).json$#', $reqPath, $matches)) { + header('Content-Type: application/json; charset=utf-8'); + + if(!UserSession::hasInstance()) { + http_response_code(403); + echo json_encode([ + 'err' => 'You must be logged in to do this.', + ]); + return; + } + + try { + $uploadInfo = Upload::byId($matches[1]); + } catch(UploadNotFoundException $ex) { + http_response_code(404); + echo json_encode([ + 'err' => 'Upload not found.', + ]); + return; + } + + echo $uploadInfo->toJson(true); + return; +} + +if($reqPath === '/settings') { + if(!UserSession::hasInstance()) { + http_response_code(404); + echo html_information('You must be logged in to access this page.'); + return; + } + + echo html_header(['title' => 'Settings - YTKNS']); + Template::render('settings/index'); + echo html_footer(); + return; +} + +if($reqPath === '/settings/invites') { + if(!UserSession::hasInstance()) { + http_response_code(404); + echo html_information('You must be logged in to access this page.'); + return; + } + + $currentUser = UserSession::instance()->getUser(); + $createdInvites = UserInvite::fromCreator($currentUser); + $createToken = $currentUser->getId() !== 1 /*&& count($createdInvites) >= 5*/ ? '' : UserSession::instance()->getSmallToken(11); + + if($reqMethod === 'POST') { + if(empty($createToken)) { + $inviteError = 'You\'ve reached the maximum amount of invites you can generate.'; + } elseif($createToken !== filter_input(INPUT_POST, 'invite_token')) { + $inviteError = 'Cannot create invite.'; + } else { + try { + $createdInvites[] = $createdInvite = UserInvite::create($currentUser); + $inviteError = $createdInvite->getToken(); + } catch(UserInviteCreationFailedException $ex) { + $inviteError = 'Invite creation failed.'; + } + } + } + + if(isset($inviteError)) + $inviteError = Template::renderRaw('error', [ + 'error_text' => $inviteError, + ]); + + if(!empty($createToken)) + $inviteCreate = Template::renderRaw('settings/invites-create', [ + 'create_token' => $createToken, + ]); + + $invitesItems = []; + foreach($createdInvites as $inviteItem) { + $invitesItems[] = [ + 'invite_created' => date('c', $inviteItem->getCreated()), + 'invite_used' => (($_used = $inviteItem->getUsed()) === null ? 'Unused' : date('c', $_used)), + 'invite_user' => (($_user = $inviteItem->getUser()) === null ? 'Unused' : sprintf('%1$s', $_user->getUsername())), + 'invite_token' => $inviteItem->getToken(), + ]; + } + + echo html_header(['title' => 'Invites - YTKNS']); + Template::render('settings/invites', [ + 'invite_error' => $inviteError ?? '', + 'invite_create' => $inviteCreate ?? '', + 'invite_list' => Template::renderSet('settings/invites-item', $invitesItems), + ]); + echo html_footer(); + return; +} + +if($reqPath === '/auth/login') { + if(UserSession::hasInstance()) { + http_response_code(404); + echo html_information('You are logged in already.'); + return; + } + + if($reqMethod === 'POST') { + $loginUsername = filter_input(INPUT_POST, 'username'); + $loginPassword = filter_input(INPUT_POST, 'password'); + $loginRemember = !empty(filter_input(INPUT_POST, 'remember')); + + if(empty($loginUsername) || empty($loginPassword)) { + $authError = 'Username or password missing.'; + } else { + try { + $loginUser = User::forLogin($loginUsername); + } catch(\Exception $ex) {} + + if(empty($loginUser) || empty($loginUser->password) || !password_verify($loginPassword, $loginUser->password)) { + $authError = 'Username or password was invalid.'; + } else { + $session = UserSession::create($loginUser, $loginRemember); + $session->setInstance(); + setcookie('ytkns_login', $session->getToken(), $session->getExpires(), '/', '.' . Config::get('domain.main'), false, true); + echo html_information('You are now logged in!', 'Welcome', '/'); + return; + } + } + } + + if(isset($authError)) + $authError = Template::renderRaw('error', [ + 'error_text' => $authError, + ]); + + $authFields = [ + [ + 'field_title' => 'Username or E-Mail Address', + 'field_type' => 'text', + 'field_name' => 'username', + 'field_value' => ($loginUsername ?? ''), + ], + [ + 'field_title' => 'Password', + 'field_type' => 'password', + 'field_name' => 'password', + 'field_value' => '', + ], + ]; + + echo html_header(['title' => 'Log in - YTKNS']); + Template::render('auth/login' . (isset($_GET['new']) ? '2' : ''), [ + 'auth_error' => $authError ?? '', + 'auth_fields' => Template::renderSet('auth/field', $authFields), + 'auth_remember' => ($loginRemember ?? false) ? ' checked' : '', + ]); + echo html_footer(); + return; +} + +if($reqPath === '/auth/register') { + if(UserSession::hasInstance()) { + http_response_code(404); + echo html_information('You are logged in already.'); + return; + } + + $inviteOnly = Config::get('user.invite_only', Config::TYPE_BOOL); + + if($reqMethod === 'POST') { + $registerUsername = filter_input(INPUT_POST, 'username'); + $registerPassword = filter_input(INPUT_POST, 'password'); + $registerEMail = filter_input(INPUT_POST, 'email'); + $registerInvite = filter_input(INPUT_POST, 'invite'); + + if(empty($registerUsername) || empty($registerPassword) || empty($registerEMail) || ($inviteOnly && empty($registerInvite))) { + $authError = 'You must fill in all fields.'; + } else { + if($inviteOnly) { + try { + $userInvite = UserInvite::byToken($registerInvite); + + if($userInvite->isUsed()) { + $authError = 'Invalid invite token.'; + } + } catch(UserInviteNotFoundException $ex) { + $authError = 'Invalid invite token.'; + } + } + + if(!isset($authError)) { + if(!preg_match('#^([a-zA-Z0-9-_]{1,20})$#', $registerUsername)) { + } + + try { + $createdUser = User::create( + $registerUsername, + $registerPassword, + $registerEMail + ); + + if(isset($userInvite)) + $userInvite->markUsed($createdUser); + } catch(UserCreationInvalidNameException $ex) { + $authError = 'Your username contains invalid characters or is too short or long.
Must be between 1 and 20 characters and may only contains alphanumeric characters as well as - and _.'; + } catch(UserCreationInvalidPasswordException $ex) { + $authError = 'Your password must have at least 6 unique characters.'; + } catch(UserCreationInvalidMailException $ex) { + $authError = 'Your e-mail address isn\'t real.'; + } catch(UserCreationFailedException $ex) { + $authError = 'Failed to create user.'; + } + } + } + } elseif($reqMethod === 'GET') { + $registerInvite = filter_input(INPUT_GET, 'inv', FILTER_SANITIZE_STRING); + } + + $authFields = [ + [ + 'field_title' => 'Username', + 'field_type' => 'text', + 'field_name' => 'username', + 'field_value' => ($registerUsername ?? ''), + ], + [ + 'field_title' => 'Password', + 'field_type' => 'password', + 'field_name' => 'password', + 'field_value' => '', + ], + [ + 'field_title' => 'E-Mail Address', + 'field_type' => 'email', + 'field_name' => 'email', + 'field_value' => ($registerEMail ?? ''), + ], + ]; + + if($inviteOnly) + $authFields[] = [ + 'field_title' => 'Invitation', + 'field_type' => 'password', + 'field_name' => 'invite', + 'field_value' => ($registerInvite ?? ''), + ]; + + if(isset($authError)) + $authError = Template::renderRaw('error', [ + 'error_text' => $authError, + ]); + + echo html_header(['title' => 'Register - YTKNS']); + Template::render('auth/register', [ + 'auth_error' => $authError ?? '', + 'auth_fields' => Template::renderSet('auth/field', $authFields), + ]); + echo html_footer(); + return; +} + +if($reqPath === '/auth/logout') { + if(!UserSession::hasInstance()) { + http_response_code(404); + echo html_information('You are not logged in.'); + return; + } + + if(filter_input(INPUT_GET, 's') !== UserSession::instance()->getSmallToken()) { + http_response_code(403); + echo html_information('Log out verification failed, please try again.'); + return; + } + + UserSession::instance()->destroy(); + UserSession::unsetInstance(); + echo html_information('You have been logged out.', 'Log out', '/'); + return; +} + +http_response_code(404); +echo html_information('The requested page does not exist.', 'Page not found'); diff --git a/src/Colour.php b/src/Colour.php new file mode 100644 index 0000000..fa1bbee --- /dev/null +++ b/src/Colour.php @@ -0,0 +1,27 @@ +setRaw($raw); + } + + public static function create($value): ?Colour { + $colour = is_int($value) ? $value : (is_string($value) && ctype_digit($value) ? (int)$value : -1); + return $colour < 0 || $colour > 0xFFFFFF ? null : new static($colour); + } + + public function getRaw(): int { + return $this->raw; + } + public function setRaw(int $raw): self { + $this->raw = $raw & 0xFFFFFF; + return $this; + } + + public function getHex(): string { + return str_pad(dechex($this->raw), 6, '0', STR_PAD_LEFT); + } +} diff --git a/src/Config.php b/src/Config.php new file mode 100644 index 0000000..4cb0216 --- /dev/null +++ b/src/Config.php @@ -0,0 +1,56 @@ + null, + self::TYPE_STR => '', + self::TYPE_INT => 0, + self::TYPE_BOOL => false, + self::TYPE_ARR => [], + ]; + + private static array $config = []; + + public static function init(): void { + $raw = DB::prepare('SELECT `config_key`, `config_value` FROM `ytkns_config`'); + $raw->execute(); + $raw = $raw->fetchAll(); + + foreach($raw as $entry) + self::$config[$entry['config_key']] = unserialize($entry['config_value']); + } + + public static function get(string $key, string $type = self::TYPE_ANY, $default = null) { + $value = self::$config[$key] ?? null; + + if($type !== self::TYPE_ANY && gettype($value) !== $type) + $value = null; + + return $value ?? $default ?? self::DEFAULTS[$type]; + } + + public static function set(string $key, $value, bool $soft = false): void { + self::$config[$key] = $value; + + if(!$soft) { + $value = serialize($value); + + $save = DB::prepare('REPLACE INTO `ytkns_config` (`config_key`, `config_value`) VALUES (:key, :value)'); + $save->bindValue('key', $key); + $save->bindValue('value', $value); + $save->execute(); + } + } + + public static function setDefault(string $key, $value, bool $soft = false): void { + if($value !== null && self::get($key) === null) + self::set($key, $value, $soft); + } +} diff --git a/src/DB.php b/src/DB.php new file mode 100644 index 0000000..396fd20 --- /dev/null +++ b/src/DB.php @@ -0,0 +1,32 @@ + PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + PDO::ATTR_EMULATE_PREPARES => false, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::MYSQL_ATTR_INIT_COMMAND => " + SET SESSION + sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION', + time_zone = '+00:00'; + ", + ]; + + public static $instance = null; + + public static function init(...$args) { + self::$instance = new PDO(...$args); + } + + public static function __callStatic(string $name, array $args) { + return self::$instance->{$name}(...$args); + } +} diff --git a/src/Effects/BackgroundAudioEffect.php b/src/Effects/BackgroundAudioEffect.php new file mode 100644 index 0000000..090b463 --- /dev/null +++ b/src/Effects/BackgroundAudioEffect.php @@ -0,0 +1,137 @@ + 'ogg', + 'title' => 'Ogg Audio Source', + 'type' => [ + 'name' => 'upload', + 'allowed' => self::OGG_MIME, + ], + ], + [ + 'name' => 'mp3', + 'title' => 'MP3 Audio Source', + 'type' => [ + 'name' => 'upload', + 'allowed' => self::MP3_MIME, + ], + ], + [ + 'name' => 'autoplay', + 'title' => 'Auto Play', + 'default' => true, + 'type' => [ + 'name' => 'bool', + ], + ], + [ + 'name' => 'loop', + 'title' => 'Loop', + 'default' => true, + 'type' => [ + 'name' => 'bool', + ], + ], + ]; + } + + public function setEffectParams(array $vars, bool $quiet = false): void { + try { + if(isset($vars['ogg']) && is_string($vars['ogg'])) { + try { + $this->oggUpload = Upload::byId($vars['ogg']); + } catch(Exception $ex) { + if(!$quiet) + throw $ex; + } + + if(!$quiet && !in_array($this->oggUpload->getType(), self::OGG_MIME)) + throw new PageEffectException('Ogg source upload was of invalid type.'); + } + } catch(UploadNotFoundException $ex) { + $noOGG = true; + } + + try { + if(isset($vars['mp3']) && is_string($vars['mp3'])) { + try { + $this->mp3Upload = Upload::byId($vars['mp3']); + } catch(Exception $ex) { + if(!$quiet) + throw $ex; + } + + if(!$quiet && !in_array($this->mp3Upload->getType(), self::MP3_MIME)) + throw new PageEffectException('MP3 source upload was of invalid type.'); + } + } catch(UploadNotFoundException $ex) { + $noMP3 = true; + } + + if(!empty($noMP3) && !empty($noOGG)) + throw new PageEffectException('No audio source uploaded.'); + + if(isset($vars['autoplay'])) + $this->autoPlay = is_bool($vars['autoplay']) ? $vars['autoplay'] : (is_string($vars['autoplay']) ? boolval($vars['autoplay']) : false); + + if(isset($vars['loop'])) + $this->loop = is_bool($vars['loop']) ? $vars['loop'] : (is_string($vars['loop']) ? boolval($vars['loop']) : false); + } + + public function getEffectParams(): array { + return [ + 'ogg' => empty($this->oggUpload) ? null : $this->oggUpload->getId(), + 'mp3' => empty($this->mp3Upload) ? null : $this->mp3Upload->getId(), + 'autoplay' => $this->autoPlay, + 'loop' => $this->loop, + ]; + } + + public function applyEffect(PageBuilder $builder): void { + $audioTag = new HtmlTag('audio'); + $audioTag->setAttribute('id', 'BackgroundAudio'); + + if($this->autoPlay) + $audioTag->setAttribute('autoplay', 'autoplay'); + if($this->loop) + $audioTag->setAttribute('loop', 'loop'); + + if(!empty($this->oggUpload)) + $audioTag->appendChild(new HtmlTag('source', ['type' => 'audio/ogg', 'src' => $this->oggUpload->getUrl()], true)); + if(!empty($this->mp3Upload)) + $audioTag->appendChild(new HtmlTag('source', ['type' => 'audio/mpeg', 'src' => $this->mp3Upload->getUrl()], true)); + + $builder->getBody()->appendChild($audioTag); + } +} diff --git a/src/Effects/BackgroundImageEffect.php b/src/Effects/BackgroundImageEffect.php new file mode 100644 index 0000000..51b706e --- /dev/null +++ b/src/Effects/BackgroundImageEffect.php @@ -0,0 +1,401 @@ + 'Relative to browser', + 'fixed' => 'Fixed to browser', + 'local' => 'Local to browser', + ]; + private const POSITION_OPTS = [ + 'top left' => 'Top left', + 'top center' => 'Top center', + 'top right' => 'Top right', + 'center left' => 'Center left', + 'center center' => 'Center', + 'center right' => 'Center right', + 'bottom left' => 'Bottom left', + 'bottom center' => 'Bottom center', + 'bottom right' => 'Bottom right', + ]; + private const REPEAT_OPTS = [ + 'repeat' => 'Repeat', + 'repeat-x' => 'Repeat horizontally', + 'repeat-y' => 'Repeat vertically', + 'space' => 'Repeat without clipping', + 'round' => 'Repeat with stretching', + 'no-repeat' => 'Don\'t repeat', + ]; + private const SIZE_OPTS = [ + 'auto auto' => 'Determine automatically', + 'cover' => 'Cover entire screen', + 'contain' => 'Contain within screen', + ]; + private const SLIDE_DIRS = [ + 'tl' => 'Top left', + 'tc' => 'Top', + 'tr' => 'Top right', + 'cl' => 'Left', + 'cr' => 'Right', + 'bl' => 'Bottom left', + 'bc' => 'Bottom', + 'br' => 'Bottom right', + ]; + private const SLIDE_MIN = 0.01; + private const SLIDE_MAX = 1000; + private const SPIN_DIRS = [ + 'cw' => 'Clockwise', + 'ccw' => 'Counterclockwise', + ]; + private const SPIN_MIN = 0.01; + private const SPIN_MAX = 1000; + + private $imageUpload = null; + private $slide = false; + private $slideSpeed = 1; + private $slideDirection = 'br'; + private $spin = false; + private $spinSpeed = 1; + private $spinDirection = 'cw'; + private $attachment = null; + private $colour = null; + private $position = null; + private $repeat = null; + private $size = null; + private $syncWithAudio = false; + private $gradient = null; + private $gradientAnimate = false; + + public function getEffectName(): string { + return 'Background Image'; + } + + public function getEffectProperties(): array { + return [ + [ + 'name' => 'img', + 'title' => 'Image', + 'type' => [ + 'name' => 'upload', + 'allowed' => self::IMG_MIME, + ], + ], + [ + 'name' => 'sld', + 'title' => 'Slide Animation', + 'default' => false, + 'type' => [ + 'name' => 'bool', + ], + ], + [ + 'name' => 'sldspd', + 'title' => 'Slide Animation Period', + 'default' => 1, + 'type' => [ + 'name' => 'float', + 'min' => self::SLIDE_MIN, + 'max' => self::SLIDE_MAX, + ], + ], + [ + 'name' => 'slddir', + 'title' => 'Slide Animation Direction', + 'default' => 'br', + 'type' => [ + 'name' => 'select', + 'options' => self::SLIDE_DIRS, + ], + ], + [ + 'name' => 'spin', + 'title' => 'Spin Animation', + 'default' => false, + 'type' => [ + 'name' => 'bool', + ], + ], + [ + 'name' => 'spnspd', + 'title' => 'Spin Animation Period', + 'default' => 1, + 'type' => [ + 'name' => 'float', + 'min' => self::SPIN_MIN, + 'max' => self::SPIN_MAX, + ], + ], + [ + 'name' => 'spndir', + 'title' => 'Spin Animation Direction', + 'default' => 'br', + 'type' => [ + 'name' => 'select', + 'options' => self::SPIN_DIRS, + ], + ], + [ + 'name' => 'attach', + 'title' => 'Attachment', + 'type' => [ + 'name' => 'select', + 'options' => self::ATTACH_OPTS, + ], + ], + [ + 'name' => 'col', + 'title' => 'Colour', + 'type' => [ + 'name' => 'colour', + ], + ], + [ + 'name' => 'pos', + 'title' => 'Position', + 'type' => [ + 'name' => 'select', + 'options' => self::POSITION_OPTS, + ], + ], + [ + 'name' => 'rep', + 'title' => 'Repeat', + 'type' => [ + 'name' => 'select', + 'options' => self::REPEAT_OPTS, + ], + ], + [ + 'name' => 'size', + 'title' => 'Size', + 'type' => [ + 'name' => 'select', + 'options' => self::SIZE_OPTS, + ], + ], + [ + 'name' => 'sync', + 'title' => 'Synchronise with Background Audio', + 'default' => false, + 'type' => [ + 'name' => 'bool', + ], + ], + [ + 'name' => 'grad', + 'title' => 'Gradient', + 'default' => [], + 'type' => [ + 'name' => 'gradient', + ], + ], + /*[ + 'name' => 'grdanm', + 'title' => 'Apply animations to gradient', + 'default' => false, + 'type' => [ + 'name' => 'bool', + ], + ],*/ + ]; + } + + public function setEffectParams(array $vars, bool $quiet = false): void { + try { + if(isset($vars['img']) && is_string($vars['img'])) { + try { + $this->imageUpload = Upload::byId($vars['img']); + } catch(Exception $ex) { + if(!$quiet) + throw $ex; + } + + if(!$quiet && !in_array($this->imageUpload->getType(), self::IMG_MIME)) + throw new PageEffectException('Image upload was of invalid type.'); + } + } catch(UploadNotFoundException $ex) { + $this->imageUpload = null; + } + + if(isset($vars['sld'])) + $this->slide = is_bool($vars['sld']) ? $vars['sld'] : (is_string($vars['sld']) ? boolval($vars['sld']) : false); + if(isset($vars['spin'])) + $this->spin = is_bool($vars['spin']) ? $vars['spin'] : (is_string($vars['spin']) ? boolval($vars['spin']) : false); + if(isset($vars['sync'])) + $this->syncWithAudio = is_bool($vars['sync']) ? $vars['sync'] : (is_string($vars['sync']) ? boolval($vars['sync']) : false); + if(isset($vars['grdanm'])) + $this->gradientAnimate = is_bool($vars['grdanm']) ? $vars['grdanm'] : (is_string($vars['grdanm']) ? boolval($vars['grdanm']) : false); + + if(isset($vars['sldspd'])) { + $this->slideSpeed = is_int($vars['sldspd']) || is_float($vars['sldspd']) ? $vars['sldspd'] : (is_string($vars['sldspd']) ? floatval($vars['sldspd']) : 1); + + if(!$quiet && ($this->slideSpeed < self::SLIDE_MIN || $this->slideSpeed > self::SLIDE_MAX)) + throw new PageEffectException(sprintf('Slide speed may not be less than %d or more than %d', self::SLIDE_MIN, self::SLIDE_MAX)); + } + if(isset($vars['spnspd'])) { + $this->spinSpeed = is_int($vars['spnspd']) || is_float($vars['spnspd']) ? $vars['spnspd'] : (is_string($vars['spnspd']) ? floatval($vars['spnspd']) : 1); + + if(!$quiet && ($this->spinSpeed < self::SPIN_MIN || $this->spinSpeed > self::SPIN_MAX)) + throw new PageEffectException(sprintf('Spin speed may not be less than %d or more than %d', self::SPIN_MIN, self::SPIN_MAX)); + } + + if(isset($vars['slddir']) && is_string($vars['slddir']) && array_key_exists($vars['slddir'], self::SLIDE_DIRS)) + $this->slideDirection = $vars['slddir']; + if(isset($vars['spndir']) && is_string($vars['spndir']) && array_key_exists($vars['spndir'], self::SPIN_DIRS)) + $this->spinDirection = $vars['spndir']; + + if(isset($vars['col'])) + $this->colour = Colour::create($vars['col']); + + try { + if(isset($vars['grad'])) + $this->gradient = Gradient::fromArray($vars['grad']); + } catch(Exception $ex) { + if(!$quiet) + throw $ex; + } + + if(isset($vars['attach']) && is_string($vars['attach']) && array_key_exists($vars['attach'], self::ATTACH_OPTS)) + $this->attachment = $vars['attach']; + if(isset($vars['pos']) && is_string($vars['pos']) && array_key_exists($vars['pos'], self::POSITION_OPTS)) + $this->position = $vars['pos']; + if(isset($vars['rep']) && is_string($vars['rep']) && array_key_exists($vars['rep'], self::REPEAT_OPTS)) + $this->repeat = $vars['rep']; + if(isset($vars['size']) && is_string($vars['size']) && array_key_exists($vars['size'], self::SIZE_OPTS)) + $this->size = $vars['size']; + } + + public function getEffectParams(): array { + return [ + 'img' => empty($this->imageUpload) ? null : $this->imageUpload->getId(), + 'sld' => $this->slide, + 'sldspd' => $this->slideSpeed, + 'slddir' => $this->slideDirection, + 'spin' => $this->spin, + 'spnspd' => $this->spinSpeed, + 'spndir' => $this->spinDirection, + 'attach' => $this->attachment, + 'col' => empty($this->colour) ? 0 : $this->colour->getRaw(), + 'pos' => $this->position, + 'rep' => $this->repeat, + 'size' => $this->size, + 'sync' => $this->syncWithAudio, + 'grad' => empty($this->gradient) ? null : $this->gradient->getArray(), + 'grdanm' => $this->gradientAnimate, + ]; + } + + public function applyEffect(PageBuilder $builder): void { + $head = $builder->getHead(); + $body = $builder->getContainer(); + $bgTarget = $body; + $styleText = ''; + + if(empty($_GET['preview']) && !$this->gradientAnimate) { + $bgTargetClasses = ['BackgroundImage']; + if($this->spin && $this->slide) + $bgTargetClasses[] = 'Cover'; + + $bgTarget = $body->appendChild(new HtmlTag('div', ['class' => implode(' ', $bgTargetClasses), 'id' => 'BackgroundImage'])); + } + + if(empty($_GET['preview']) && !empty($this->imageUpload)) { + if($this->slide) { + $imageSize = getimagesize($this->imageUpload->getPath()); + $imageWidth = $imageSize[0]; + $imageHeight = $imageSize[1]; + + $animSX = $this->slideDirection[1] === 'l' ? $imageWidth : 0; + $animSY = $this->slideDirection[0] === 't' ? $imageHeight : 0; + $animEX = $this->slideDirection[1] === 'r' ? $imageWidth : 0; + $animEY = $this->slideDirection[0] === 'b' ? $imageHeight : 0; + + if($imageSize !== false) { + $slideAnim = '_' . bin2hex(random_bytes(8)); + $styleText .= sprintf( + '@keyframes %s { 0%% { background-position: %dpx %dpx; } 100%% { background-position: %dpx %dpx; } } ', + $slideAnim, $animSX, $animSY, $animEX, $animEY + ); + } + } + } + + $styleBgColour = isset($this->colour) + ? sprintf(' background-color: #%s;', $this->colour->getHex()) + : ' background-color: #000;'; + + $animation = []; + $backgroundImage = []; + + if($this->spin) + $animation[] = sprintf('SharedAnimation_Spin360 infinite linear %s %Fs', $this->spinDirection === 'cw' ? 'normal' : 'reverse', $this->spinSpeed); + if(isset($slideAnim)) + $animation[] = sprintf('%s infinite linear %Fs', $slideAnim, $this->slideSpeed); + + $syncWithAudio = empty($_GET['preview']) && $this->syncWithAudio; + + if(!empty($this->imageUpload) && !$syncWithAudio) + $backgroundImage[] = sprintf('url(\'%s\')', $this->imageUpload->getUrl()); + + if($bgTarget !== $body) { + $styleText .= '#container {'; + if(!empty($this->gradient)) + $styleText .= 'background-image: ' . $this->gradient->getCSS() . ';'; + $styleText .= $styleBgColour; + $styleText .= '}'; + } + + $styleText .= '#' . ($bgTarget->getAttribute('id')) . ' {'; + + if($bgTarget === $body) { + if(!empty($this->gradient)) + $backgroundImage[] = $this->gradient->getCSS(); + + $styleText .= $styleBgColour; + } + + if(!empty($backgroundImage)) + $styleText .= sprintf(' background-image: %s;', implode(', ', $backgroundImage)); + + if(!empty($this->attachment)) + $styleText .= sprintf(' background-attachment: %s;', $this->attachment); + if(!empty($this->position)) + $styleText .= sprintf(' background-position: %s;', $this->position); + if(!empty($this->repeat)) + $styleText .= sprintf(' background-repeat: %s;', $this->repeat); + if(!empty($this->size)) + $styleText .= sprintf(' background-size: %s;', $this->size); + if(!empty($animation)) + $styleText .= sprintf(' animation: %s;', implode(', ', $animation)); + + $styleText .= ' }'; + + $styleTag = new HtmlTag('style', ['type' => 'text/css']); + $styleTag->setTextContent($styleText); + $head->appendChild($styleTag); + + if(!empty($this->imageUpload) && $syncWithAudio) { + $scriptText = 'window.addEventListener(\'DOMContentLoaded\', function() {'; + $scriptText .= 'synchroniseBackgroundWithAudio(\'' . $this->imageUpload->getUrl() . '\');'; + $scriptText .= '});'; + $scriptTag = new HtmlTag('script', ['type' => 'text/javascript']); + $scriptTag->setTextContent($scriptText); + $head->appendChild($scriptTag); + } + } +} diff --git a/src/Effects/ForegroundImageEffect.php b/src/Effects/ForegroundImageEffect.php new file mode 100644 index 0000000..7789cc7 --- /dev/null +++ b/src/Effects/ForegroundImageEffect.php @@ -0,0 +1,153 @@ + 'Clockwise', + 'ccw' => 'Counterclockwise', + ]; + private const SPIN_MIN = 0.01; + private const SPIN_MAX = 1000; + + private $imageUpload = null; + private $spin = false; + private $spinSpeed = 1; + private $spinDirection = 'cw'; + private $syncWithAudio = false; + + public function getEffectName(): string { + return 'Foreground Image'; + } + + public function getEffectProperties(): array { + return [ + [ + 'name' => 'img', + 'title' => 'Image', + 'type' => [ + 'name' => 'upload', + 'allowed' => self::IMG_MIME, + ], + ], + [ + 'name' => 'spin', + 'title' => 'Spin Animation', + 'default' => false, + 'type' => [ + 'name' => 'bool', + ], + ], + [ + 'name' => 'spnspd', + 'title' => 'Spin Animation Period', + 'default' => 1, + 'type' => [ + 'name' => 'float', + 'min' => self::SPIN_MIN, + 'max' => self::SPIN_MAX, + ], + ], + [ + 'name' => 'spndir', + 'title' => 'Spin Animation Direction', + 'default' => 'br', + 'type' => [ + 'name' => 'select', + 'options' => self::SPIN_DIRS, + ], + ], + [ + 'name' => 'sync', + 'title' => 'Synchronise with Background Audio', + 'default' => false, + 'type' => [ + 'name' => 'bool', + ], + ], + ]; + } + + public function setEffectParams(array $vars, bool $quiet = false): void { + try { + if(isset($vars['img']) && is_string($vars['img'])) { + try { + $this->imageUpload = Upload::byId($vars['img']); + } catch(Exception $ex) { + if(!$quiet) + throw $ex; + } + + if(!$quiet && !in_array($this->imageUpload->getType(), self::IMG_MIME)) + throw new PageEffectException('Image upload was of invalid type.'); + } + } catch(UploadNotFoundException $ex) { + $this->imageUpload = null; + } + + if(isset($vars['spin'])) + $this->spin = is_bool($vars['spin']) ? $vars['spin'] : (is_string($vars['spin']) ? boolval($vars['spin']) : false); + if(isset($vars['sync'])) + $this->syncWithAudio = is_bool($vars['sync']) ? $vars['sync'] : (is_string($vars['sync']) ? boolval($vars['sync']) : false); + + if(isset($vars['spnspd'])) { + $this->spinSpeed = is_int($vars['spnspd']) || is_float($vars['spnspd']) ? $vars['spnspd'] : (is_string($vars['spnspd']) ? floatval($vars['spnspd']) : 1); + + if(!$quiet && ($this->spinSpeed < self::SPIN_MIN || $this->spinSpeed > self::SPIN_MAX)) + throw new PageEffectException(sprintf('Spin speed may not be less than %d or more than %d', self::SPIN_MIN, self::SPIN_MAX)); + } + + if(isset($vars['spndir']) && is_string($vars['spndir']) && array_key_exists($vars['spndir'], self::SPIN_DIRS)) + $this->spinDirection = $vars['spndir']; + } + + public function getEffectParams(): array { + return [ + 'img' => empty($this->imageUpload) ? null : $this->imageUpload->getId(), + 'spin' => $this->spin, + 'spnspd' => $this->spinSpeed, + 'spndir' => $this->spinDirection, + 'sync' => $this->syncWithAudio, + ]; + } + + public function applyEffect(PageBuilder $builder): void { + $element = $builder->getContainer()->appendChild(new HtmlTag('div', ['class' => 'ForegroundImage'])); + $imageTarget = $element->appendChild(new HtmlTag('div', ['class' => 'ForegroundImage_Image', 'id' => 'ForegroundImage'])); + + if(!empty($this->imageUpload)) { + $imageSize = getimagesize($this->imageUpload->getPath()); + + if($imageSize !== false) { + $styleText = sprintf('width: %dpx; height: %dpx', $imageSize[0], $imageSize[1]); + + if(!empty($_GET['preview']) || !$this->syncWithAudio) + $styleText .= sprintf(';background-image:url(\'%s\')', $this->imageUpload->getUrl()); + + if(empty($_GET['preview']) && $this->spin) + $styleText .= sprintf(';animation: SharedAnimation_Spin360 infinite linear %s %Fs', $this->spinDirection === 'cw' ? 'normal' : 'reverse', $this->spinSpeed); + + $imageTarget->setAttribute('style', $styleText); + + $scriptText = 'window.addEventListener(\'DOMContentLoaded\', function() {'; + $scriptText .= 'synchroniseBackgroundWithAudio(\'' . $this->imageUpload->getUrl() . '\', \'ForegroundImage\');'; + $scriptText .= '});'; + $scriptTag = new HtmlTag('script', ['type' => 'text/javascript']); + $scriptTag->setTextContent($scriptText); + $builder->getHead()->appendChild($scriptTag); + } + } + } +} diff --git a/src/Effects/NewlyCreatedPageEffect.php b/src/Effects/NewlyCreatedPageEffect.php new file mode 100644 index 0000000..30067a7 --- /dev/null +++ b/src/Effects/NewlyCreatedPageEffect.php @@ -0,0 +1,45 @@ +headerText = $vars['h']; + if(!empty($vars['s'])) + $this->subText = $vars['s']; + } + + public function getEffectParams(): array { + $vars = []; + + if(!empty($this->headerText)) + $vars['h'] = $this->headerText; + if(!empty($this->subText)) + $vars['s'] = $this->subText; + + return $vars; + } + + public function applyEffect(PageBuilder $builder): void { + $builder->getContainer()->appendChild(new HtmlTag('div', ['class' => 'NewCreatePageEffect_Main'], [ + new HtmlTag('h1', [], [new HtmlText($this->headerText ?? 'This page is still empty')]), + new HtmlTag('p', [], [new HtmlText($this->subText ?? 'Please come back later')]), + ])); + } +} diff --git a/src/Effects/ZoomTextEffect.php b/src/Effects/ZoomTextEffect.php new file mode 100644 index 0000000..bc123f8 --- /dev/null +++ b/src/Effects/ZoomTextEffect.php @@ -0,0 +1,57 @@ + 'txt', + 'title' => 'Text', + 'type' => [ + 'name' => 'string', + 'min' => self::TEXT_MIN, + 'max' => self::TEXT_MAX, + ], + ], + ]; + } + + public function setEffectParams(array $vars, bool $quiet = false): void { + if(isset($vars['txt']) && is_string($vars['txt'])) { + if(!$quiet && (mb_strlen($vars['txt']) < self::TEXT_MIN || mb_strlen($vars['txt']) > self::TEXT_MAX)) + throw new PageEffectException('Your text is too long or too short.'); + $this->text = $vars['txt']; + } + } + + public function getEffectParams(): array { + return [ + 'txt' => $this->text, + ]; + } + + public function applyEffect(PageBuilder $builder): void { + $tags = []; + + for($i = 1; $i <= 50; $i++) { + $tags[] = new HtmlTag('div', ['class' => 'ZoomText_Child', 'style' => sprintf('font-size: %1$dpt; top: %1$dpx; left: %2$dpx; color: rgb(%3$d, %3$d, %3$d);', $i * 2, $i, $i === 50 ? 0 : (4 * $i))], [new HtmlText($this->text)]); + } + + $builder->getContainer()->appendChild(new HtmlTag('div', ['class' => 'ZoomText'], $tags)); + } +} diff --git a/src/Gradient.php b/src/Gradient.php new file mode 100644 index 0000000..32cf126 --- /dev/null +++ b/src/Gradient.php @@ -0,0 +1,87 @@ + $this->getDirection(), + 'p' => [], + ]; + + foreach($this->getPoints() as $point) + $arr['p'][] = $point->getArray(); + + return $arr; + } + + public static function fromArray($array): ?self { + $gradient = new static; + + if(!empty($array)) { + if(is_string($array)) + $array = json_decode($array); + if(is_object($array)) + $array = (array)$array; + if(is_array($array)) { + if(isset($array['d']) && is_int($array['d'])) + $gradient->setDirection($array['d']); + + if(isset($array['p']) && is_array($array['p'])) { + $gradient->resetPoints(); + + foreach($array['p'] as $point) + $gradient->addPoint(GradientPoint::fromArray($point)); + } + } + } + + if(count($gradient->points) < 2) + return null; + + return $gradient; + } + + public function getCSS(): string { + $points = []; + + foreach($this->points as $point) + $points[] = $point->getCSS(); + + return sprintf('linear-gradient(%ddeg, %s)', $this->getDirection(), implode(', ', $points)); + } + + public function getDirection(): int { + return $this->direction; + } + public function setDirection(int $dir): self { + if($dir < 0 || $dir > 359) + throw new InvalidArgumentException('dir must be between 0 and 359.'); + $this->direction = $dir; + return $this; + } + + public function getPoints(): array { + return $this->points; + } + + public function addPoint(GradientPoint $point): self { + if(!in_array($point, $this->points)) + $this->points[] = $point; + return $this; + } + public function removePoint(GradientPoint $point): self { + $index = array_search($point, $this->points); + if($index !== null) + unset($this->points[$index]); + return $this; + } + public function resetPoints(): self { + $this->points = []; + return $this; + } +} diff --git a/src/GradientPoint.php b/src/GradientPoint.php new file mode 100644 index 0000000..02ba1cb --- /dev/null +++ b/src/GradientPoint.php @@ -0,0 +1,65 @@ +setOffset($offset); + if($colour !== null) + $this->setColour($colour); + } + + public function getArray(): array { + return [ + 'o' => $this->getOffset(), + 'c' => $this->getColour()->getRaw(), + ]; + } + + public static function fromArray($array): self { + $point = new static; + + if(!empty($array)) { + if(is_string($array)) + $array = json_decode($array); + if(is_object($array)) + $array = (array)$array; + if(is_array($array)) { + if(isset($array['o']) && is_int($array['o'])) + $point->setOffset($array['o']); + if(isset($array['c'])) + $point->setColour(Colour::create($array['c'])); + } + } + + return $point; + } + + public function getOffset(): int { + return $this->offset; + } + public function setOffset(int $offset): self { + if($offset < 0 || $offset > 100) + throw new InvalidArgumentException('offset must be between 0 and 100'); + $this->offset = $offset; + return $this; + } + + public function getColour(): Colour { + if($this->colour === null) + $this->colour = new Colour; + return $this->colour; + } + public function setColour(Colour $colour): self { + $this->colour = $colour; + return $this; + } + + public function getCSS(): string { + return sprintf('#%s %d%%', $this->getColour()->getHex(), $this->getOffset()); + } +} \ No newline at end of file diff --git a/src/HtmlTag.php b/src/HtmlTag.php new file mode 100644 index 0000000..44b40e3 --- /dev/null +++ b/src/HtmlTag.php @@ -0,0 +1,126 @@ +tagName = $tagName; + + foreach($attributes as $key => $val) + $this->setAttribute($key, $val); + + if(is_bool($children)) + $this->selfClosing = $children; + elseif(is_array($children)) { + foreach($children as $child) + if($child !== null && $child instanceof HtmlTypeInterface) + $this->appendChild($child); + } + } + + public function getTagName(): string { + return $this->tagName; + } + + public function getAttribute(string $name): ?string { + return $this->attributes[$name] ?? null; + } + public function setAttribute(string $name, $value): void { + if($value === null) { + $this->removeAttribute($name); + return; + } + + $this->attributes[$name] = $value; + } + public function removeAttribute(string $name): void { + unset($this->attributes[$name]); + } + + public function appendChild(HtmlTypeInterface $child): HtmlTypeInterface { + return $this->children[] = $child; + } + public function removeChild(HtmlTypeInterface $target): void { + $remove = []; + + foreach($this->children as $child) + if($child === $target) + $remove[] = $child; + + $this->children = array_diff($this->children, $remove); + } + + public function setTextContent(string $textContent): void { + $this->children = [new HtmlText($textContent)]; + } + + public function getElementsByTagName(string $tagName): array { + $tagName = mb_strtolower($tagName); + $elements = []; + + foreach($this->children as $child) { + if($child instanceof HtmlTag) { + $elements = array_merge($elements, $child->getElementsByTagName($tagName)); + + if(mb_strtolower($child->getTagName()) === $tagName) + $elements[] = $child; + } + } + + return $elements; + } + public function getElementsByClassName(string $className): array { + $elements = []; + + foreach($this->children as $child) { + if($child instanceof HtmlTag) { + $elements = array_merge($elements, $child->getElementsByClassName($className)); + + $classList = explode(' ', $child->getAttribute('class') ?? ''); + if(in_array($className, $classList)) + $elements[] = $child; + } + } + + return $elements; + } + public function getElementById(string $idString): ?HtmlTag { + foreach($this->children as $child) { + if($child instanceof HtmlTag) { + if($child->getAttribute('id') == $idString) + return $child; + + $element = $child->getElementById($idString); + + if($element !== null) + return $element; + } + } + + return null; + } + + public function asHTML(): string { + $attrs = ''; + $children = ''; + + foreach($this->attributes as $key => $val) { + $attrs .= sprintf(' %s', $key); + + if($key !== $val) + $attrs .= sprintf('="%s"', htmlspecialchars($val)); + } + + if($this->selfClosing) + return sprintf('<%s%s/>', $this->getTagName(), $attrs); + + foreach($this->children as $child) + $children .= $child->asHTML(); + + return sprintf('<%1$s%2$s>%3$s', $this->getTagName(), $attrs, $children); + } +} diff --git a/src/HtmlText.php b/src/HtmlText.php new file mode 100644 index 0000000..fc8b79a --- /dev/null +++ b/src/HtmlText.php @@ -0,0 +1,14 @@ +text = $text; + } + + public function asHTML(): string { + return htmlspecialchars($this->text); + } +} diff --git a/src/HtmlTypeInterface.php b/src/HtmlTypeInterface.php new file mode 100644 index 0000000..7d5e696 --- /dev/null +++ b/src/HtmlTypeInterface.php @@ -0,0 +1,6 @@ +tagHtml = new HtmlTag('html'); + $this->tagHtml->appendChild($this->tagHead = new HtmlTag('head')); + $this->tagHtml->appendChild($this->tagBody = new HtmlTag('body')); + $this->tagHead->appendChild(new HtmlTag('meta', ['charset' => 'utf-8'], true)); + $this->tagHead->appendChild(new HtmlTag('meta', ['name' => 'viewport', 'content' => 'width=device-width, initial-scale=1'], true)); + $this->tagHead->appendChild($this->tagTitle = new HtmlTag('title')); + $this->tagHead->appendChild(new HtmlTag('link', ['href' => $sharedCss, 'type' => 'text/css', 'rel' => 'stylesheet'], true)); + $this->tagHead->appendChild(new HtmlTag('script', ['charset' => 'utf-8', 'src' => $sharedJs])); + $this->tagBody->appendChild($this->tagContainer = new HtmlTag('div', ['id' => 'container'])); + $this->setTitle($pageTitle); + } + + public function getHtml(): HtmlTag { + return $this->tagHtml; + } + public function getHead(): HtmlTag { + return $this->tagHead; + } + public function getBody(): HtmlTag { + return $this->tagBody; + } + public function getContainer(): HtmlTag { + return $this->tagContainer; + } + + public function setTitle(string $title): void { + $this->tagTitle->setTextContent($title); + } + + public function getElementsByTagName(string $tagName): array { + return $this->tagHtml->getElementsByTagName($tagName); + } + public function getElementsByClassName(string $className): array { + return $this->tagHtml->getElementsByClassName($className); + } + public function getElementById(string $idString): ?HtmlTag { + return $this->tagHtml->getElementById($idString); + } + + public function serialise(): string { + return '' . $this->tagHtml->asHTML(); + } + + public function __toString(): string { + return $this->serialise(); + } +} diff --git a/src/PageEffectInterface.php b/src/PageEffectInterface.php new file mode 100644 index 0000000..b3c6709 --- /dev/null +++ b/src/PageEffectInterface.php @@ -0,0 +1,14 @@ + $val) + $output[':' . $key] = $val; + + return $output; + } + + public static function renderRaw(string $path, array $vars = []): string { + $vars = self::prefixArray(array_merge(self::$vars, $vars)); + $tpl = file_get_contents(YTKNS_TPL . '/' . $path . '.html'); + $tpl = strtr($tpl, $vars); + return $tpl; + } + + public static function render(string $path, array $vars = []): void { + echo self::renderRaw($path, $vars); + } + + public static function renderSet(string $path, array $varSets = []): string { + $html = ''; + $tpl = file_get_contents(YTKNS_TPL . '/' . $path . '.html'); + + foreach($varSets as $vars) + $html .= strtr($tpl, self::prefixArray($vars)); + + return $html; + } +} diff --git a/src/Upload.php b/src/Upload.php new file mode 100644 index 0000000..f029c54 --- /dev/null +++ b/src/Upload.php @@ -0,0 +1,276 @@ +upload_id; + } + + public function getPath(): string { + return YTKNS_UPLOADS . '/' . $this->getId(); + } + public function getUrl(): string { + return 'https://' . Config::get('domain.main') . '/uploads/' . $this->getId(); + } + + public function getUserId(): int { + return $this->user_id; + } + public function setUserId(int $userId): void { + $this->user_id = $userId; + } + + public function getType(): string { + return $this->upload_type ?? 'text/plain'; + } + + public function getName(): string { + return $this->upload_name ?? ''; + } + + public function getHash(): string { + return $this->upload_hash ?? str_pad('', 64, '0'); + } + + public function getUser(): User { + return User::byId($this->getUserId()); + } + + public function getUseCount(): int { + return $this->upload_use_count ?? 0; + } + + public function getCreated(): int { + return $this->upload_created; + } + + public function getlastUsed(): ?int { + return $this->upload_last_used; + } + + public function getDeleted(): ?int { + return $this->upload_deleted; + } + + public function getDMCA(): ?int { + return $this->upload_dmca; + } + + public function delete(bool $hard): void { + if($hard) { + if(is_file($this->getPath())) + unlink($this->getPath()); + + if($this->getDMCA() < 1) { + $delete = DB::prepare(' + DELETE FROM `ytkns_uploads` + WHERE `upload_id` = :id + '); + $delete->bindValue('id', $this->getId()); + $delete->execute(); + } + } else { + $delete = DB::prepare(' + UPDATE `ytkns_uploads` + SET `upload_deleted` = NOW() + WHERE `upload_id` = :id + '); + $delete->bindValue('id', $this->getId()); + $delete->execute(); + } + } + + public function toJson(bool $asString = false) { + $uploadInfo = [ + 'id' => $this->getId(), + 'name' => $this->getName(), + 'type' => $this->getType(), + 'user' => $this->getUserId(), + 'uses' => $this->getUseCount(), + 'hash' => $this->getHash(), + 'created' => $this->getCreated(), + 'last_used' => $this->getLastUsed(), + 'deleted' => $this->getDeleted(), + 'dmca' => $this->getDMCA(), + ]; + + if($asString) + $uploadInfo = json_encode($uploadInfo); + + return $uploadInfo; + } + + public static function generateId(int $length = 16): string { + $token = random_bytes($length); + $chars = strlen(self::ID_CHARS); + + for($i = 0; $i < $length; $i++) + $token[$i] = self::ID_CHARS[ord($token[$i]) % $chars]; + + return $token; + } + + public static function create(User $user, string $fileName, string $fileType, string $fileHash): self { + $id = self::generateId(); + $create = DB::prepare(' + INSERT INTO `ytkns_uploads` ( + `upload_id`, `user_id`, `upload_ip`, `upload_name`, `upload_type`, `upload_hash` + ) VALUES ( + :id, :user, INET6_ATON(:ip), :name, :type, UNHEX(:hash) + ) + '); + $create->bindValue('id', $id); + $create->bindValue('user', $user->getId()); + $create->bindValue('ip', $_SERVER['REMOTE_ADDR']); + $create->bindValue('name', $fileName); + $create->bindValue('type', $fileType); + $create->bindValue('hash', $fileHash); + $create->execute(); + + try { + return self::byId($id); + } catch(UploadNotFoundException $ex) { + throw new UploadCreationFailedException; + } + } + + public static function byId(string $id): self { + $getUpload = DB::prepare(' + SELECT `upload_id`, `user_id`, `upload_use_count`, `upload_name`, `upload_type`, + UNIX_TIMESTAMP(`upload_created`) AS `upload_created`, + UNIX_TIMESTAMP(`upload_last_used`) AS `upload_last_used`, + UNIX_TIMESTAMP(`upload_deleted`) AS `upload_deleted`, + UNIX_TIMESTAMP(`upload_dmca`) AS `upload_dmca`, + INET6_NTOA(`upload_ip`) AS `upload_ip`, + LOWER(HEX(`upload_hash`)) AS `upload_hash` + FROM `ytkns_uploads` + WHERE `upload_id` = :id + AND `upload_deleted` IS NULL + '); + $getUpload->bindValue('id', $id); + $upload = $getUpload->execute() ? $getUpload->fetchObject(self::class) : false; + + if(!$upload) + throw new UploadNotFoundException; + + return $upload; + } + + public static function byHash(string $hash): ?self { + $getUpload = DB::prepare(' + SELECT `upload_id`, `user_id`, `upload_use_count`, `upload_name`, `upload_type`, + UNIX_TIMESTAMP(`upload_created`) AS `upload_created`, + UNIX_TIMESTAMP(`upload_last_used`) AS `upload_last_used`, + UNIX_TIMESTAMP(`upload_deleted`) AS `upload_deleted`, + UNIX_TIMESTAMP(`upload_dmca`) AS `upload_dmca`, + INET6_NTOA(`upload_ip`) AS `upload_ip`, + LOWER(HEX(`upload_hash`)) AS `upload_hash` + FROM `ytkns_uploads` + WHERE `upload_hash` = UNHEX(:hash) + AND `upload_deleted` IS NULL + '); + $getUpload->bindValue('hash', $hash); + $upload = $getUpload->execute() ? $getUpload->fetchObject(self::class) : false; + return $upload ? $upload : null; + } + + public static function deleted(): array { + $getDeleted = DB::prepare(' + SELECT `upload_id`, `user_id`, `upload_use_count`, `upload_name`, `upload_type`, + UNIX_TIMESTAMP(`upload_created`) AS `upload_created`, + UNIX_TIMESTAMP(`upload_last_used`) AS `upload_last_used`, + UNIX_TIMESTAMP(`upload_deleted`) AS `upload_deleted`, + UNIX_TIMESTAMP(`upload_dmca`) AS `upload_dmca`, + INET6_NTOA(`upload_ip`) AS `upload_ip`, + LOWER(HEX(`upload_hash`)) AS `upload_hash` + FROM `ytkns_uploads` + WHERE `upload_deleted` IS NOT NULL + OR `upload_dmca` IS NOT NULL + '); + if(!$getDeleted->execute()) + return []; + + $deleted = []; + + while($upload = $getDeleted->fetchObject(self::class)) + $deleted[] = $upload; + + return $deleted; + } + + public static function purgeOrphans(): void { + DB::exec(' + UPDATE `ytkns_uploads` + SET `upload_deleted` = NOW() + WHERE `upload_use_count` < 1 + AND (`upload_created` + INTERVAL 1 DAY) < NOW() + AND `upload_dmca` IS NULL + '); + + $orphans = self::deleted(); + + foreach($orphans as $orphan) + $orphan->delete(true); + } + + public static function resync(array $uploadFields): void { + if(empty($uploadFields)) + return; + + $effectNames = array_keys($uploadFields); + $fetchEffectsWhereIn = range(0, count($effectNames) - 1); + array_walk($fetchEffectsWhereIn, function(&$i, $k, $v) { $i = $v . $i; }, ':field_'); + + $fetchEffects = DB::prepare(sprintf(' + SELECT `effect_name`, `effect_params` + FROM `ytkns_zones_effects` + WHERE `effect_name` IN (%s) + ', implode(', ', $fetchEffectsWhereIn))); + + foreach($fetchEffectsWhereIn as $index => $name) + $fetchEffects->bindValue($name, $effectNames[$index]); + + $effectsWithUploads = $fetchEffects->execute() ? $fetchEffects->fetchAll(\PDO::FETCH_OBJ) : false; + $effectsWithUploads = $effectsWithUploads ? $effectsWithUploads : []; + + $uploadUseCounts = []; + + foreach($effectsWithUploads as $effectWithUploads) { + if(!array_key_exists($effectWithUploads->effect_name, $uploadFields)) + continue; + + $effectParams = json_decode($effectWithUploads->effect_params, true); + + foreach($uploadFields[$effectWithUploads->effect_name] as $uploadField) { + if(empty($uploadId = $effectParams[$uploadField])) + continue; + + if(!isset($uploadUseCounts[$uploadId])) + $uploadUseCounts[$uploadId] = 1; + else + $uploadUseCounts[$uploadId] += 1; + } + } + + $updateUploadUseCount = DB::prepare(' + UPDATE `ytkns_uploads` + SET `upload_use_count` = :count + WHERE `upload_id` = :id + '); + + DB::exec('UPDATE `ytkns_uploads` SET `upload_use_count` = 0'); + + foreach($uploadUseCounts as $uploadId => $uploadUseCount) { + $updateUploadUseCount->bindValue('id', $uploadId); + $updateUploadUseCount->bindValue('count', $uploadUseCount); + $updateUploadUseCount->execute(); + } + } +} diff --git a/src/User.php b/src/User.php new file mode 100644 index 0000000..5f2f9c7 --- /dev/null +++ b/src/User.php @@ -0,0 +1,123 @@ +user_id ?? 0; + } + private function setId(int $userId): void { + $this->user_id = $userId; + } + + public function getUsername(): string { + return $this->username ?? ''; + } + + public static function byId(int $userId): self { + $getUser = DB::prepare(' + SELECT `user_id`, `username`, `email`, `password`, `user_created` + FROM `ytkns_users` + WHERE `user_id` = :user + '); + $getUser->bindValue('user', $userId); + $getUser->execute(); + $user = $getUser->fetchObject(self::class); + + if($user === false) + throw new UserNotFoundException; + + return $user; + } + + public static function forLogin(string $usernameOrEmail): self { + $getUser = DB::prepare(' + SELECT `user_id`, `username`, `email`, `password`, `user_created` + FROM `ytkns_users` + WHERE LOWER(`username`) = LOWER(:username) + OR LOWER(`email`) = LOWER(:email) + '); + $getUser->bindValue('username', $usernameOrEmail); + $getUser->bindValue('email', $usernameOrEmail); + $getUser->execute(); + $user = $getUser->fetchObject(self::class); + + if($user === false) + throw new UserNotFoundException; + + return $user; + } + + public static function forProfile(string $username): self { + $getUser = DB::prepare(' + SELECT `user_id`, `username`, `email`, `password`, `user_created` + FROM `ytkns_users` + WHERE LOWER(`username`) = LOWER(:username) + '); + $getUser->bindValue('username', $username); + $getUser->execute(); + $user = $getUser->fetchObject(self::class); + + if($user === false) + throw new UserNotFoundException; + + return $user; + } + + public static function validatePassword(string $password): bool { + $chars = []; + $length = mb_strlen($password); + + for($i = 0; $i < $length; $i++) { + $current = mb_substr($password, $i, 1); + + if(!in_array($current, $chars, true)) + $chars[] = $current; + } + + return count($chars) >= 6; + } + + public static function hashPassword(string $password): string { + return password_hash($password, PASSWORD_ARGON2ID); + } + + public static function create(string $userName, string $password, string $email): self { + if(!preg_match('#^([a-zA-Z0-9-_]{1,20})$#', $userName)) + throw new UserCreationInvalidNameException; + if(!filter_var($email, FILTER_VALIDATE_EMAIL)) + throw new UserCreationInvalidMailException; + if(!self::validatePassword($password)) + throw new UserCreationInvalidPasswordException; + + $password = self::hashPassword($password); + + $createUser = DB::prepare(' + INSERT INTO `ytkns_users` ( + `username`, `email`, `password` + ) VALUES ( + :username, :email, :password + ) + '); + $createUser->bindValue('username', $userName); + $createUser->bindValue('email', $email); + $createUser->bindValue('password', $password); + $userId = $createUser->execute() ? (int)DB::lastInsertId() : 0; + + try { + return self::byId($userId); + } catch(UserNotFoundException $ex) { + throw new UserCreationFailedException; + } + } +} diff --git a/src/UserInvite.php b/src/UserInvite.php new file mode 100644 index 0000000..437f447 --- /dev/null +++ b/src/UserInvite.php @@ -0,0 +1,122 @@ +invite_token; + } + + public function getCreatorId(): int { + return $this->created_by ?? 0; + } + public function getCreator(): User { + return User::byId($this->getCreatedById()); + } + + public function getUserId(): ?int { + return $this->used_by ?? null; + } + public function getUser(): ?User { + $userId = $this->getUserId(); + + if($userId === null) + return null; + + try { + return User::byId($this->getUserId()); + } catch(UserNotFoundException $ex) { + return null; + } + } + + public function getCreated(): int { + return $this->invite_created ?? 0; + } + + public function getUsed(): ?int { + return $this->invite_used ?? null; + } + + public function isUsed(): bool { + return !empty($this->used_by) || !empty($this->invite_used); + } + + public function markUsed(User $user): void { + $markUsed = DB::prepare(' + UPDATE `ytkns_users_invites` + SET `used_by` = :user + WHERE `invite_token` = UNHEX(:token) + '); + $markUsed->bindValue('user', $user->getId()); + $markUsed->bindValue('token', $this->getToken()); + $markUsed->execute(); + } + + public static function fromCreator(User $creator): array { + $getInvites = DB::prepare(' + SELECT `created_by`, `used_by`, + LOWER(HEX(`invite_token`)) AS `invite_token`, + UNIX_TIMESTAMP(`invite_created`) AS `invite_created`, + UNIX_TIMESTAMP(`invite_used`) AS `invite_used` + FROM `ytkns_users_invites` + WHERE `created_by` = :creator + '); + $getInvites->bindValue('creator', $creator->getId()); + $getInvites->execute(); + $invites = []; + + while($invite = $getInvites->fetchObject(self::class)) + $invites[] = $invite; + + return $invites; + } + + public static function byToken(string $token, bool $isHex = true): self { + $getInvite = DB::prepare(sprintf(' + SELECT `created_by`, `used_by`, + LOWER(HEX(`invite_token`)) AS `invite_token`, + UNIX_TIMESTAMP(`invite_created`) AS `invite_created`, + UNIX_TIMESTAMP(`invite_used`) AS `invite_used` + FROM `ytkns_users_invites` + WHERE `invite_token` = %s(:token) + ', $isHex ? 'UNHEX' : '')); + $getInvite->bindValue('token', $token); + $invite = $getInvite->execute() ? $getInvite->fetchObject(self::class) : false; + + if(!$invite) + throw new UserInviteNotFoundException; + + return $invite; + } + + public static function generateToken(): string { + return bin2hex(random_bytes(self::TOKEN_LENGTH)); + } + + public static function create(User $creator): self { + $inviteToken = self::generateToken(); + $insertInvite = DB::prepare(' + INSERT INTO `ytkns_users_invites` ( + `invite_token`, `created_by` + ) VALUES ( + UNHEX(:token), :creator + ) + '); + $insertInvite->bindValue('token', $inviteToken); + $insertInvite->bindValue('creator', $creator->getId()); + $insertInvite->execute(); + + try { + return self::byToken($inviteToken); + } catch(UserInviteNotFoundException $ex) { + throw new UserInviteCreationFailedException; + } + } +} diff --git a/src/UserSession.php b/src/UserSession.php new file mode 100644 index 0000000..0fb20e4 --- /dev/null +++ b/src/UserSession.php @@ -0,0 +1,149 @@ +session_token); + } + public static function unsetInstance(): void { + self::$instance = null; + } + + public function __construct() { + } + + public function getUserId(): int { + return $this->user_id ?? 0; + } + public function getUser(): User { + return User::byId($this->getUserId()); + } + + public function getToken(): string { + return $this->session_token; + } + public function getSmallToken(int $rounds = 5, int $length = 8, int $offset = 0): string { + $token = $this->getToken(); + $tokenLength = strlen($token) - $length; + + for($i = 0; $i < $rounds; $i++) + $offset = ord($token[$offset]) % $tokenLength; + + return str_rot13(substr($token, $offset, $length)); + } + + public function getCreated(): int { + return $this->session_created ?? 0; + } + + public function getExpires(): int { + return $this->session_expires ?? 0; + } + + public function getBump(): bool { + return $this->session_bump ?? false; + } + + public function getFirstIp(): string { + return $this->session_ip_first ?? ''; + } + + public function getLastIp(): ?string { + return $this->session_ip_last ?? null; + } + + public function update(): void { + $update = DB::prepare(' + UPDATE `ytkns_users_sessions` + SET `session_expires` = IF(:bump, NOW() + INTERVAL 1 MONTH, `session_expires`), + `session_ip_last` = INET6_ATON(:ip), + `session_used` = NOW() + WHERE `session_token` = :token + '); + $update->bindValue('bump', $this->getBump() ? 1 : 0); + $update->bindValue('ip', $_SERVER['REMOTE_ADDR']); + $update->bindValue('token', $this->getToken()); + $update->execute(); + } + + public function destroy(): void { + $destroy = DB::prepare(' + DELETE FROM `ytkns_users_sessions` + WHERE `session_token` = :token + '); + $destroy->bindValue('token', $this->getToken()); + $destroy->execute(); + } + + public static function generateToken(int $length = 64): string { + $token = random_bytes($length); + $chars = strlen(self::TOKEN_CHARS); + + for($i = 0; $i < $length; $i++) + $token[$i] = self::TOKEN_CHARS[ord($token[$i]) % $chars]; + + return $token; + } + + public static function create(User $user, bool $bump = false): self { + $token = self::generateToken(); + $create = DB::prepare(' + INSERT INTO `ytkns_users_sessions` ( + `user_id`, `session_token`, `session_ip_first`, `session_bump` + ) VALUES ( + :user, :token, INET6_ATON(:ip), :bump + ) + '); + $create->bindValue('user', $user->getId()); + $create->bindValue('token', $token); + $create->bindValue('ip', $_SERVER['REMOTE_ADDR']); + $create->bindValue('bump', $bump ? 1 : 0); + $create->execute(); + + try { + return self::byToken($token); + } catch(UserSessionNotFoundException $ex) { + throw new UserSessionCreatedFailedException; + } + } + + public static function byToken(string $token): self { + $getSession = DB::prepare(' + SELECT `user_id`, `session_token`, `session_bump`, + UNIX_TIMESTAMP(`session_created`) AS `session_created`, + UNIX_TIMESTAMP(`session_expires`) AS `session_expires`, + INET6_NTOA(`session_ip_first`) AS `session_ip_first`, + INET6_NTOA(`session_ip_last`) AS `session_ip_last` + FROM `ytkns_users_sessions` + WHERE `session_token` = :token + '); + $getSession->bindValue('token', $token); + $session = $getSession->execute() ? $getSession->fetchObject(self::class) : false; + + if(!$session) + throw new UserSessionNotFoundException; + + return $session; + } + + public static function purge(): void { + DB::exec(' + DELETE FROM `ytkns_users_sessions` + WHERE `session_expires` <= NOW() + '); + } +} diff --git a/src/Zone.php b/src/Zone.php new file mode 100644 index 0000000..659b94a --- /dev/null +++ b/src/Zone.php @@ -0,0 +1,361 @@ +passiveMode = true; + + if($this->effects === null) + $this->effects = []; + } + + public function getId(): int { + return $this->zone_id ?? 0; + } + public function hasId(): bool { + return isset($this->zone_id) && $this->zone_id > 0; + } + private function setId(int $id): void { + if($id < 1) + throw new ZoneInvalidIdException; + + $this->zone_id = $id; + } + + public function getUserId(): int { + return $this->user_id ?? 0; + } + public function setUserId(int $userId): void { + $this->user_id = $userId; + } + + private $userObj = null; + public function getUser(): User { + if($this->userObj === null) + $this->userObj = User::byId($this->getUserId()); + + return $this->userObj; + } + + public function getName(): string { + return $this->zone_name; + } + public function setName(string $name): void { + if(!self::validName($name)) + throw new ZoneInvalidNameException; + + $this->zone_name = $name; + } + + public function getUrl(): string { + return 'https://' . sprintf(Config::get('domain.zone'), $this->getName()); + } + public function getUrlForPreview(): string { + return $this->getUrl() . '?preview=1'; + } + + public function getScreenshotPath(): string { + return YTKNS_SCREENSHOTS . '/' . $this->getName() . '.jpg'; + } + public function getScreenshotUrl(): string { + return 'https://' . Config::get('domain.main') . '/ss/' . $this->getName() . '.jpg'; + } + public function takeScreenshot(): void { + $path = escapeshellarg($this->getScreenshotPath()); + $url = escapeshellarg($this->getUrlForPreview()); + system(sprintf('/usr/bin/firefox --window-size=800,600 --screenshot %s %s', $path, $url)); + //system(sprintf('/usr/bin/convert %1$s 200x150 %1$s', $path)); + + } + public function removeScreenshot(): void { + $path = $this->getScreenshotPath(); + + if(is_file($path)) + unlink($path); + } + + public function getTitle(): string { + return $this->zone_title; + } + public function setTitle(string $title): void { + if(strlen($title) > 255) + throw new ZoneInvalidTitleException; + + $this->zone_title = $title; + } + + public function getViews(): int { + return $this->zone_views ?? 0; + } + + public function incrementViews(): void { + $updateViews = DB::prepare(' + UPDATE `ytkns_zones` + SET `zone_views` = `zone_views` + 1 + WHERE `zone_id` = :zone + '); + $updateViews->bindValue('zone', $this->getId()); + if($updateViews->execute()) + $this->zone_views = $this->getViews() + 1; + } + + public function getEffects(): array { + if(!is_array($this->effects)) + $this->effects = $this->hasId() ? ZoneEffect::byZone($this) : []; + + return $this->effects; + } + + public function getPageBuilder(bool $quiet = false): PageBuilder { + $pageBuilder = new PageBuilder($this->getTitle()); + + $effects = $this->getEffects(); + + foreach($effects as $effect) + $effect->applyEffect($pageBuilder, $quiet); + + return $pageBuilder; + } + + public function addEffect(PageEffectInterface $effect): void { + if(is_array($this->effects)) + $this->effects[] = $effect; + + if(!$this->passiveMode) + $this->addEffectDatabase($effect); + } + private function addEffectDatabase(PageEffectInterface $effect): void { + if(!$this->hasId()) + return; + + $insert = DB::prepare(' + REPLACE INTO `ytkns_zones_effects` ( + `zone_id`, `effect_name`, `effect_params` + ) VALUES ( + :zone, :name, :params + ) + '); + $insert->bindValue('zone', $this->getId()); + $insert->bindValue('name', substr(get_class($effect), 14, -6)); + $insert->bindValue('params', json_encode($effect->getEffectParams())); + $insert->execute(); + } + + public function removeEffects(): void { + $this->effects = []; + + if(!$this->passiveMode) + $this->removeEffectsDatabase(); + } + private function removeEffectsDatabase(): void { + if(!$this->hasId()) + return; + + $removeEffects = DB::prepare(' + DELETE FROM `ytkns_zones_effects` + WHERE `zone_id` = :zone + '); + $removeEffects->bindValue('zone', $this->getId()); + $removeEffects->execute(); + } + + public static function byId(string $id): self { + $getZone = DB::prepare(' + SELECT `zone_id`, `user_id`, `zone_name`, `zone_title`, `zone_views`, + UNIX_TIMESTAMP(`zone_created`) AS `zone_created`, + UNIX_TIMESTAMP(`zone_updated`) AS `zone_updated` + FROM `ytkns_zones` + WHERE `zone_id` = :zone + '); + $getZone->bindValue('zone', $id); + $zone = $getZone->execute() ? $getZone->fetchObject(self::class) : false; + + if(!$zone) + throw new ZoneNotFoundException; + + return $zone; + } + + public static function byName(string $name): self { + $getZone = DB::prepare(' + SELECT `zone_id`, `user_id`, `zone_name`, `zone_title`, `zone_views`, + UNIX_TIMESTAMP(`zone_created`) AS `zone_created`, + UNIX_TIMESTAMP(`zone_updated`) AS `zone_updated` + FROM `ytkns_zones` + WHERE `zone_name` = :name + '); + $getZone->bindValue('name', $name); + $zone = $getZone->execute() ? $getZone->fetchObject(self::class) : false; + + if(!$zone) + throw new ZoneNotFoundException; + + return $zone; + } + + public static function byUser(User $user, ?string $orderBy = null, bool $ascending = true, int $take = 0, int $offset = 0): array { + $getZonesQuery = ' + SELECT `zone_id`, `user_id`, `zone_name`, `zone_title`, `zone_views`, + UNIX_TIMESTAMP(`zone_created`) AS `zone_created`, + UNIX_TIMESTAMP(`zone_updated`) AS `zone_updated` + FROM `ytkns_zones` + WHERE `user_id` = :user + '; + + if($orderBy !== null) + $getZonesQuery .= sprintf('ORDER BY `%s` %s', $orderBy, $ascending ? 'ASC' : 'DESC'); + if($take > 0) + $getZonesQuery .= sprintf(' LIMIT %d OFFSET %d', $take, $offset); + + $getZones = DB::prepare($getZonesQuery); + $getZones->bindValue('user', $user->getId()); + $getZones->execute(); + $zones = []; + + while($zone = $getZones->fetchObject(self::class)) + $zones[] = $zone; + + return $zones; + } + + public static function all(?string $orderBy = null, bool $ascending = true, int $take = 0, int $offset = 0): array { + $getZonesQuery = ' + SELECT `zone_id`, `user_id`, `zone_name`, `zone_title`, `zone_views`, + UNIX_TIMESTAMP(`zone_created`) AS `zone_created`, + UNIX_TIMESTAMP(`zone_updated`) AS `zone_updated` + FROM `ytkns_zones` + '; + + if($orderBy !== null) + $getZonesQuery .= sprintf(' ORDER BY `%s` %s', $orderBy, $ascending ? 'ASC' : 'DESC'); + if($take > 0) + $getZonesQuery .= sprintf(' LIMIT %d OFFSET %d', $take, $offset); + + $getZones = DB::prepare($getZonesQuery); + $getZones->execute(); + $zones = []; + + while($zone = $getZones->fetchObject(self::class)) + $zones[] = $zone; + + return $zones; + } + + public static function count(): int { + $getZoneCount = DB::prepare(' + SELECT COUNT(`zone_id`) + FROM `ytkns_zones` + '); + return (int)($getZoneCount->execute() ? $getZoneCount->fetchColumn() : 0); + } + + public static function create(User $user, string $name, string $title): self { + $create = DB::prepare(' + INSERT INTO `ytkns_zones` ( + `user_id`, `zone_name`, `zone_title` + ) VALUES ( + :user, LOWER(:name), :title + ) + '); + $create->bindValue('user', $user->getId()); + $create->bindValue('name', $name); + $create->bindValue('title', $title); + $create->execute(); + + try { + return self::byName($name); + } catch(ZoneNotFoundException $ex) { + throw new ZoneCreationFailedException; + } + } + + public function update(array $save = ['user_id', 'zone_name', 'zone_title']): void { + if(!$this->hasId()) + return; + + $set = [ + '`zone_updated` = NOW()', + ]; + + foreach($save as $param) + $set[] = sprintf('`%1$s` = :%1$s', $param); + + $update = DB::prepare(sprintf('UPDATE `ytkns_zones` SET %s WHERE `zone_id` = :zone', implode(', ', $set))); + + if(in_array('user_id', $save)) + $update->bindValue('user_id', $this->getUserId()); + if(in_array('zone_name', $save)) + $update->bindValue('zone_name', $this->getName()); + if(in_array('zone_title', $save)) + $update->bindValue('zone_title', $this->getTitle()); + + $update->bindValue('zone', $this->getId()); + $update->execute(); + + if($this->passiveMode) { + $effects = $this->getEffects(); + $this->removeEffectsDatabase(); + + foreach($this->getEffects() as $effectInfo) + $this->addEffectDatabase($effectInfo); + } + } + + public static function exists(string $name): bool { + $check = DB::prepare(' + SELECT COUNT(`zone_name`) > 0 + FROM `ytkns_zones` + WHERE `zone_name` = :name + '); + $check->bindValue('name', $name); + return $check->execute() ? (bool)$check->fetchColumn() : true; + } + + public function delete(): void { + $delete = DB::prepare(' + DELETE FROM `ytkns_zones` + WHERE `zone_id` = :zone + '); + $delete->bindValue('zone', $this->getId()); + $delete->execute(); + } + + public static function validName(string $name): bool { + return preg_match('#^([A-Za-z0-9]{1,50})$#', $name); + } + + public function toJson(bool $asString = false) { + $zoneInfo = [ + 'id' => $this->getId(), + 'name' => $this->getName(), + 'title' => $this->getTitle(), + 'effects' => [], + ]; + + foreach($this->getEffects() as $effect) + $zoneInfo['effects'][] = $effect->toJson(); + + if($asString) + $zoneInfo = json_encode($zoneInfo); + + return $zoneInfo; + } + + public function queueTask(string $task, ...$params): void { + ZoneTask::enqueue($this, $task, $params); + } +} diff --git a/src/ZoneEffect.php b/src/ZoneEffect.php new file mode 100644 index 0000000..7f8828e --- /dev/null +++ b/src/ZoneEffect.php @@ -0,0 +1,102 @@ +zone_id ?? 0; + } + public function getZone(): Zone { + return Zone::byId($this->getZoneId()); + } + public function setZoneId(Zone $zone): void { + $this->zone_id = $zone->getId(); + } + + public function getEffectName(): string { + return $this->effect_name; + } + public function setEffectName(string $name): void { + $this->effect_name = $name; + } + + public function getEffectParams(): array { + if(!isset($this->effect_params) || $this->effect_params === null) + return []; + + if(is_string($this->effect_params)) + $this->effect_params = json_decode($this->effect_params, true); + + return $this->effect_params; + } + public function setEffectParams(array $params): void { + $this->effect_params = $params; + } + + public static function effectClassName(string $name): string { + return sprintf(self::CLASS_NAME, $name); + } + public function getEffectClassName(): string { + return self::effectClassName($this->getEffectName()); + } + public static function effectClass(string $name) { + $className = self::effectClassName($name); + + if(!class_exists($className)) + throw new ZoneEffectClassNotFoundException($name); + + return new $className; + } + public function getEffectClass(bool $quiet = false) { + try { + $effect = self::effectClass($this->getEffectName()); + $effect->setEffectParams($this->getEffectParams(), $quiet); + return $effect; + } catch(ZoneEffectClassNotFoundException $ex) { + return null; + } + } + public function applyEffect(PageBuilder $builder, bool $quiet = false): void { + $effect = $this->getEffectClass($quiet); + + if($effect !== null) + $effect->applyEffect($builder); + } + + public static function byZone(Zone $zone): array { + $getEffects = DB::prepare(' + SELECT `zone_id`, `effect_name`, `effect_params` + FROM `ytkns_zones_effects` + WHERE `zone_id` = :zone + '); + $getEffects->bindValue('zone', $zone->getId()); + if(!$getEffects->execute()) + return []; + + $effects = []; + while($effect = $getEffects->fetchObject(self::class)) + $effects[] = $effect; + + return $effects; + } + + public function toJson(bool $asString = false) { + $effectInfo = [ + 'type' => $this->getEffectName(), + 'values' => $this->getEffectParams(), + ]; + + if($asString) + $effectInfo = json_encode($effectInfo); + + return $effectInfo; + } +} diff --git a/src/ZoneRedirect.php b/src/ZoneRedirect.php new file mode 100644 index 0000000..9b106ed --- /dev/null +++ b/src/ZoneRedirect.php @@ -0,0 +1,46 @@ + 0 + FROM `ytkns_redirects` + WHERE `redirect_name` = :subdomain + '); + $check->bindValue('subdomain', $subdomain); + return $check->execute() ? (bool)$check->fetchColumn() : true; + } + + public static function find(string $subdomain): ?ZoneRedirect { + $find = DB::prepare(' + SELECT `redirect_name`, `redirect_target` + FROM `ytkns_redirects` + WHERE `redirect_name` = :subdomain + '); + $find->bindValue('subdomain', $subdomain); + $redirect = $find->execute() ? $find->fetchObject(self::class) : false; + return $redirect ? $redirect : null; + } + + public static function create(string $subdomain, string $target): void { + $create = DB::prepare(' + REPLACE INTO `ytkns_redirects` ( + `redirect_name`, `redirect_target` + ) VALUES ( + :name, :target + ) + '); + $create->bindValue('name', $subdomain); + $create->bindValue('target', $target); + $create->execute(); + } + + public function execute(): void { + http_response_code(301); + header(sprintf('Location: %s', $this->redirect_target)); + } +} diff --git a/src/ZoneTask.php b/src/ZoneTask.php new file mode 100644 index 0000000..af7878e --- /dev/null +++ b/src/ZoneTask.php @@ -0,0 +1,60 @@ +zone_id ?? 0; + } + public function getZone(): Zone { + return Zone::byId($this->getZoneId()); + } + + public function getName(): string { + return $this->task_name ?? ''; + } + + public function getParams(): array { + if(empty($this->task_params)) + return []; + return unserialize($this->task_params); + } + + public function delete(): void { + $deleteTask = DB::prepare(' + DELETE FROM `ytkns_zones_tasks` + WHERE `zone_id` = :zone + AND `task_name` = :task + '); + $deleteTask->bindValue('zone', $this->getZoneId()); + $deleteTask->bindValue('task', $this->getName()); + $deleteTask->execute(); + } + + public static function queue(): array { + $getTasks = DB::prepare(' + SELECT `zone_id`, `task_name`, `task_params` + FROM `ytkns_zones_tasks` + '); + $getTasks->execute(); + $tasks = []; + + while($task = $getTasks->fetchObject(self::class)) + $tasks[] = $task; + + return $tasks; + } + + public static function enqueue(Zone $zone, string $name, array $params = []): void { + $enqueue = DB::prepare(' + REPLACE INTO `ytkns_zones_tasks` ( + `zone_id`, `task_name`, `task_params` + ) VALUES ( + :zone, :task, :params + ) + '); + $enqueue->bindValue('zone', $zone->getId()); + $enqueue->bindValue('task', $name); + $enqueue->bindValue('params', serialize(array_values($params))); + $enqueue->execute(); + } +} diff --git a/src/ZoneView.php b/src/ZoneView.php new file mode 100644 index 0000000..b7e68ea --- /dev/null +++ b/src/ZoneView.php @@ -0,0 +1,44 @@ +view_time ?? 0; + } + + public static function byZoneAddress(Zone $zone, string $ipAddress): ?self { + $getZoneView = DB::prepare(' + SELECT `zone_id`, + UNIX_TIMESTAMP(`view_time`) AS `view_time`, + INET6_NTOA(`view_address`) AS `view_address` + FROM `ytkns_zones_views` + WHERE `zone_id` = :zone + AND `view_address` = INET6_ATON(:ip) + '); + $getZoneView->bindValue('zone', $zone->getId()); + $getZoneView->bindValue('ip', $ipAddress); + $zoneView = $getZoneView->execute() ? $getZoneView->fetchObject(self::class) : false; + return $zoneView ? $zoneView : null; + } + + public static function increment(Zone $zone, string $ipAddress): void { + $zoneView = self::byZoneAddress($zone, $ipAddress); + + if($zoneView !== null && ($zoneView->getTime() + self::THRESHOLD) > time()) + return; + + $updateZoneView = DB::prepare(' + REPLACE INTO `ytkns_zones_views` ( + `view_address`, `zone_id`, `view_time` + ) VALUES ( + INET6_ATON(:ip), :zone, NOW() + ) + '); + $updateZoneView->bindValue('ip', $ipAddress); + $updateZoneView->bindValue('zone', $zone->getId()); + if($updateZoneView->execute()) + $zone->incrementViews(); + } +} diff --git a/startup.php b/startup.php new file mode 100644 index 0000000..0166aec --- /dev/null +++ b/startup.php @@ -0,0 +1,74 @@ + ['ogg', 'mp3'], + 'BackgroundImage' => ['img'], + 'ForegroundImage' => ['img'], +]); + +error_reporting(YTKNS_DEBUG ? -1 : 0); +ini_set('display_errors', YTKNS_DEBUG ? 'On' : 'Off'); + +mb_internal_encoding('utf-8'); +date_default_timezone_set('utc'); + +// Display exception report +set_exception_handler(function(\Throwable $ex) { + http_response_code(500); + + $out = file_get_contents(YTKNS_TPL . (YTKNS_DEBUG ? '/debug/index.html' : '/errors/500.html')); + echo strtr($out, [ + ':raw_ex' => (string)$ex, + ':type' => get_class($ex), + ':msg' => $ex->getMessage(), + ]); + + exit; +}); + +// Turn uncaught errors into uncaught exceptions. +set_error_handler(function(int $errno, string $errstr, string $errfile, int $errline) { + throw new \ErrorException($errstr, 0, $errno, $errfile, $errline); + return true; +}, -1); + +// Register class autoloader +spl_autoload_register(function(string $className) { + if(substr($className, 0, 6) !== 'YTKNS\\') + return; + + $classPath = YTKNS_SRC . str_replace('\\', '/', substr($className, 5)) . '.php'; + + if(is_file($classPath)) + require_once $classPath; +}); + +DB::init(PDO_DSN, PDO_USER, PDO_PASS, DB::FLAGS); + +Config::init(); +Config::setDefault('user.invite_only', true); +Config::setDefault('domain.main', 'ytkns.com'); +Config::setDefault('domain.zone', '%s.ytkns.com'); diff --git a/templates/auth/field.html b/templates/auth/field.html new file mode 100644 index 0000000..de57e19 --- /dev/null +++ b/templates/auth/field.html @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/templates/auth/login.html b/templates/auth/login.html new file mode 100644 index 0000000..a6f2e95 --- /dev/null +++ b/templates/auth/login.html @@ -0,0 +1,11 @@ +

Log in

+
+ :auth_error + :auth_fields + +
+ +
+
diff --git a/templates/auth/login2.html b/templates/auth/login2.html new file mode 100644 index 0000000..277bb0e --- /dev/null +++ b/templates/auth/login2.html @@ -0,0 +1,17 @@ +

Log in

+
+ :auth_error +
+ + +
+
+ diff --git a/templates/auth/register.html b/templates/auth/register.html new file mode 100644 index 0000000..a662ee7 --- /dev/null +++ b/templates/auth/register.html @@ -0,0 +1,8 @@ +

Register

+
+ :auth_error + :auth_fields +
+ +
+
diff --git a/templates/debug/frame.html b/templates/debug/frame.html new file mode 100644 index 0000000..e69de29 diff --git a/templates/debug/index.html b/templates/debug/index.html new file mode 100644 index 0000000..e0e680a --- /dev/null +++ b/templates/debug/index.html @@ -0,0 +1,15 @@ + + + + + + :type - :msg + + +
+:raw_ex
+
+ + diff --git a/templates/error.html b/templates/error.html new file mode 100644 index 0000000..adf7368 --- /dev/null +++ b/templates/error.html @@ -0,0 +1,3 @@ +
+ :error_text +
\ No newline at end of file diff --git a/templates/errors/500.html b/templates/errors/500.html new file mode 100644 index 0000000..6993b23 --- /dev/null +++ b/templates/errors/500.html @@ -0,0 +1,11 @@ + + + + + Error 500 + + +

ytkns is fucking dead

+

:msg

+ + diff --git a/templates/footer.html b/templates/footer.html new file mode 100644 index 0000000..c7fbe80 --- /dev/null +++ b/templates/footer.html @@ -0,0 +1,9 @@ + + + :scripts + + diff --git a/templates/header.html b/templates/header.html new file mode 100644 index 0000000..e3c31e8 --- /dev/null +++ b/templates/header.html @@ -0,0 +1,34 @@ + + + + + + :title + + :head + + +
+
+
+ :menu_user +
+ +
+ :menu_site +
+
+
\ No newline at end of file diff --git a/templates/home.html b/templates/home.html new file mode 100644 index 0000000..354f742 --- /dev/null +++ b/templates/home.html @@ -0,0 +1,2 @@ +

this site has nothing in particular to do with splatoon lol, it's just a ytmnd clone

+

for the best experience, enable autoplay for this domain

\ No newline at end of file diff --git a/templates/information-redirect.html b/templates/information-redirect.html new file mode 100644 index 0000000..834846a --- /dev/null +++ b/templates/information-redirect.html @@ -0,0 +1 @@ +
Click here if nothing happens. \ No newline at end of file diff --git a/templates/information.html b/templates/information.html new file mode 100644 index 0000000..27366c1 --- /dev/null +++ b/templates/information.html @@ -0,0 +1,6 @@ +
+ :info_title +
+ :info_content +
+
\ No newline at end of file diff --git a/templates/menu-site-item.html b/templates/menu-site-item.html new file mode 100644 index 0000000..2e9f802 --- /dev/null +++ b/templates/menu-site-item.html @@ -0,0 +1 @@ +:text \ No newline at end of file diff --git a/templates/menu-user-item.html b/templates/menu-user-item.html new file mode 100644 index 0000000..55ba12b --- /dev/null +++ b/templates/menu-user-item.html @@ -0,0 +1 @@ +:text \ No newline at end of file diff --git a/templates/profile/index.html b/templates/profile/index.html new file mode 100644 index 0000000..69f9f19 --- /dev/null +++ b/templates/profile/index.html @@ -0,0 +1,3 @@ +

@:profile_username

+

Zones

+:profile_zones \ No newline at end of file diff --git a/templates/profile/notfound.html b/templates/profile/notfound.html new file mode 100644 index 0000000..d206bd9 --- /dev/null +++ b/templates/profile/notfound.html @@ -0,0 +1,2 @@ +

User not found!

+

No user with this username exists.

\ No newline at end of file diff --git a/templates/profile/zone-item.html b/templates/profile/zone-item.html new file mode 100644 index 0000000..cda0017 --- /dev/null +++ b/templates/profile/zone-item.html @@ -0,0 +1,16 @@ +
+ + :zone_name + +
+ + +
+ :zone_views views +
+
+
\ No newline at end of file diff --git a/templates/profile/zone-list.html b/templates/profile/zone-list.html new file mode 100644 index 0000000..8c7bb08 --- /dev/null +++ b/templates/profile/zone-list.html @@ -0,0 +1,3 @@ +
+:zone_items +
\ No newline at end of file diff --git a/templates/profile/zone-none.html b/templates/profile/zone-none.html new file mode 100644 index 0000000..86d0d84 --- /dev/null +++ b/templates/profile/zone-none.html @@ -0,0 +1 @@ +

This user has no zones yet.

\ No newline at end of file diff --git a/templates/settings/index.html b/templates/settings/index.html new file mode 100644 index 0000000..831a751 --- /dev/null +++ b/templates/settings/index.html @@ -0,0 +1 @@ +

Settings

\ No newline at end of file diff --git a/templates/settings/invites-create.html b/templates/settings/invites-create.html new file mode 100644 index 0000000..87c6fcf --- /dev/null +++ b/templates/settings/invites-create.html @@ -0,0 +1,4 @@ +
+ + +
\ No newline at end of file diff --git a/templates/settings/invites-item.html b/templates/settings/invites-item.html new file mode 100644 index 0000000..0fc377f --- /dev/null +++ b/templates/settings/invites-item.html @@ -0,0 +1,14 @@ +
+
+ :invite_created +
+
+ :invite_used +
+
+ :invite_user +
+
+ :invite_token +
+
\ No newline at end of file diff --git a/templates/settings/invites.html b/templates/settings/invites.html new file mode 100644 index 0000000..9cbc070 --- /dev/null +++ b/templates/settings/invites.html @@ -0,0 +1,22 @@ +

Invites

+
+ :invite_error + :invite_create +
+
+
+ Created on +
+
+ Used on +
+
+ Used by +
+
+ Invitation +
+
+ :invite_list +
+
diff --git a/templates/zones/create.html b/templates/zones/create.html new file mode 100644 index 0000000..eb8552b --- /dev/null +++ b/templates/zones/create.html @@ -0,0 +1,20 @@ +

Create a Zone

+
+ :create_error + + + +
+ +
+
\ No newline at end of file diff --git a/templates/zones/delete.html b/templates/zones/delete.html new file mode 100644 index 0000000..2b3bf41 --- /dev/null +++ b/templates/zones/delete.html @@ -0,0 +1,8 @@ +

Deleting Zone - :delete_zone_name

+
+ + +
\ No newline at end of file diff --git a/templates/zones/edit.html b/templates/zones/edit.html new file mode 100644 index 0000000..7b27fe7 --- /dev/null +++ b/templates/zones/edit.html @@ -0,0 +1,25 @@ + +
+
+
+
+ Loading editor... +
+
+
+ \ No newline at end of file diff --git a/templates/zones/item.html b/templates/zones/item.html new file mode 100644 index 0000000..3410b24 --- /dev/null +++ b/templates/zones/item.html @@ -0,0 +1,16 @@ +
+ + :zone_name + +
+ + +
+ :zone_views views - @:zone_user_name +
+
+
\ No newline at end of file diff --git a/templates/zones/list.html b/templates/zones/list.html new file mode 100644 index 0000000..28b8995 --- /dev/null +++ b/templates/zones/list.html @@ -0,0 +1,7 @@ +

:zone_list_title

+
+:zone_sortings +
+
+:zone_list +
\ No newline at end of file diff --git a/templates/zones/my-item.html b/templates/zones/my-item.html new file mode 100644 index 0000000..b0709ad --- /dev/null +++ b/templates/zones/my-item.html @@ -0,0 +1,33 @@ + +
+ + :zone_name + + +
\ No newline at end of file diff --git a/templates/zones/my-list.html b/templates/zones/my-list.html new file mode 100644 index 0000000..4f6c7e8 --- /dev/null +++ b/templates/zones/my-list.html @@ -0,0 +1,4 @@ +

My Zones

+
+:zone_list +
\ No newline at end of file diff --git a/templates/zones/my-none.html b/templates/zones/my-none.html new file mode 100644 index 0000000..6246461 --- /dev/null +++ b/templates/zones/my-none.html @@ -0,0 +1,9 @@ +

My Zones

+
+
+ You have no zones yet. +
+ +
\ No newline at end of file diff --git a/templates/zones/none.html b/templates/zones/none.html new file mode 100644 index 0000000..3568c25 --- /dev/null +++ b/templates/zones/none.html @@ -0,0 +1,4 @@ +
+

This zone doesn't exist!

+

Would you like to create it?

+
\ No newline at end of file