Compare commits
967 Commits
Author | SHA1 | Date | |
---|---|---|---|
b80f5a5316 | |||
f003905c0b | |||
9330bb628e | |||
f0e3aa6fd7 | |||
c437696955 | |||
fd6624862f | |||
d2a3b8ad37 | |||
155f44a424 | |||
7fc58f1605 | |||
23d9fd0b40 | |||
b28e1f13ee | |||
0868fb43bc | |||
5340c39466 | |||
1e3bfa7a0a | |||
22de5776a7 | |||
dddef5add6 | |||
30db156df7 | |||
58669f023c | |||
238a423806 | |||
24d6eaf0da | |||
ecce90abd5 | |||
913f9082ef | |||
7584d0a935 | |||
fd2f3121bf | |||
249333b9fa | |||
aca0f66db2 | |||
66fca53dfc | |||
3b754ce634 | |||
44e1039067 | |||
78755f4019 | |||
ed9a3227ac | |||
e2f29c6731 | |||
1b74df5269 | |||
9c01f3fbd0 | |||
3714a93dcf | |||
608cd4004e | |||
aace368b24 | |||
e20601053c | |||
08176be4d0 | |||
69991a126a | |||
980e2726c5 | |||
3f8c0432d2 | |||
ba276cc86e | |||
26a1c464a1 | |||
40a340c67c | |||
7d6af5ad00 | |||
a4cde0b28d | |||
a692617101 | |||
41ca3a2d86 | |||
386938c75f | |||
2f2260e0b4 | |||
1da68d8ec4 | |||
e06cc16890 | |||
09734515ca | |||
1725a106fa | |||
4d4b7126e2 | |||
e2e845b9c0 | |||
24def28cda | |||
f3b7e58ee1 | |||
e7e70a1541 | |||
65a8da755a | |||
ca8a0edad0 | |||
9a6d0052d7 | |||
6a82944473 | |||
f328420869 | |||
3965f137e1 | |||
ff6fcffca8 | |||
4c1c78577e | |||
d66b589a75 | |||
20f318114b | |||
56032728eb | |||
722518579f | |||
eab8a1cd4b | |||
2fa83b5f1d | |||
48b031e190 | |||
a8110d4a90 | |||
455b551683 | |||
de6faf2224 | |||
10cc9dece2 | |||
8fe2114816 | |||
83de995951 | |||
766e3cd71c | |||
375b8f9bcb | |||
dcd138cba6 | |||
50f3c394c5 | |||
0ed7a0de2e | |||
bb878d905f | |||
3bb3d2a5cf | |||
9297ff4b94 | |||
9e8b3f4663 | |||
|
5e157e4a4e | ||
035e142c98 | |||
ebe8626ba7 | |||
493f849aeb | |||
332ff72360 | |||
cacf19781d | |||
545c495869 | |||
4131a019ca | |||
c4fb095eba | |||
e4018c269f | |||
05c50387f6 | |||
dc20d8de7c | |||
b806519b22 | |||
48e51c816f | |||
9045eb6b8f | |||
77ee6ae50e | |||
fa89df567d | |||
2d44435c59 | |||
9344d98056 | |||
ee18d407a2 | |||
30ee7b5601 | |||
7373cf93b7 | |||
a80284a4ee | |||
bbd375e464 | |||
95a7e74d69 | |||
41ea7f082f | |||
625a57c191 | |||
0ad4d56663 | |||
a3d56afef3 | |||
93d87336b1 | |||
9cc3645950 | |||
b2c86adcf5 | |||
badf941265 | |||
ae276a536f | |||
a97a31ea41 | |||
52b562f455 | |||
4c2abf5416 | |||
e88230ad52 | |||
96a389af66 | |||
212b34ac4c | |||
f804cc66fb | |||
52e5b10512 | |||
4d6c15b030 | |||
f2991cd416 | |||
12be7b8a1e | |||
fe284d755b | |||
3f2c23ab96 | |||
da15c45fd9 | |||
da570d5167 | |||
17fb2b4db4 | |||
9ba4354d29 | |||
|
dccdd62707 | ||
2d7d511ab1 | |||
|
3fb614a3a8 | ||
461d074d41 | |||
|
b0a85c0b50 | ||
fc71007227 | |||
2584047289 | |||
4f6a92cd45 | |||
813a3fc807 | |||
a1b9ee65fa | |||
fce9764f08 | |||
b6a7226424 | |||
e572e2f147 | |||
b200085a3f | |||
919d8ee6b1 | |||
9ecd5df6c7 | |||
392f24d11f | |||
61ca4300d4 | |||
e550a096bc | |||
2222d48f23 | |||
40bc7a34e1 | |||
82d77d0f35 | |||
063edc0f6f | |||
4b37492b61 | |||
783dd322c0 | |||
9259c85586 | |||
39f672b35f | |||
c60b821bca | |||
39f23c2c35 | |||
bcbb24c0f5 | |||
4fd03d309c | |||
daaa560fbd | |||
396d88f343 | |||
cc0e03b573 | |||
5b58059860 | |||
357e08d41e | |||
dac6d4b775 | |||
f242b0dbfa | |||
d688dbdd2f | |||
0d0a3309fb | |||
177eeaaef5 | |||
3827ef326d | |||
dccc578395 | |||
2958307115 | |||
acd507d7f2 | |||
db68f983bf | |||
591d7bb4bc | |||
eab72cd821 | |||
c6e209a36f | |||
4308418414 | |||
52fc6d8391 | |||
8ea1cbc52f | |||
725cbc4cf2 | |||
eeea80ff0a | |||
1abe7d9aee | |||
9924793a49 | |||
5807c9dac3 | |||
33d46a27eb | |||
1a4a65ceaf | |||
19320cf299 | |||
7cc3c5bd88 | |||
e5073f6a6c | |||
a7931052b0 | |||
dec5b25569 | |||
14613e6395 | |||
14992c4c80 | |||
ea2065ab4d | |||
4c43a0cf79 | |||
87ecc0dce4 | |||
580dbb5993 | |||
7a4cda5bf0 | |||
e4b8e6ce51 | |||
698025146f | |||
acd3b84b55 | |||
75aa7e3aae | |||
efede08401 | |||
951f4362db | |||
26fca2a1c2 | |||
4b73b1b249 | |||
08fc1c81bf | |||
3bb9734e1d | |||
6fd2b22d72 | |||
47b9d7ba7a | |||
4ecac1c15f | |||
b0682ae1b2 | |||
77ffd46cd8 | |||
59ba0f49c7 | |||
44ec41b0f7 | |||
fec671e3cd | |||
848f667626 | |||
361ebcbbe4 | |||
6f2a59ae95 | |||
d10e930a6b | |||
c6b74e2775 | |||
f3b42ae056 | |||
9140ebaa19 | |||
27160bda9a | |||
3fae7fe9d6 | |||
b5ec89de34 | |||
8eca9c9d04 | |||
dfce7f649e | |||
b4a5e8ce77 | |||
1f2accf4ec | |||
6e950613e6 | |||
9214b0c87b | |||
73488d8244 | |||
c93629dea2 | |||
5bf8277376 | |||
51bf392d1b | |||
dd6e99877a | |||
4de92a3591 | |||
2943f716b9 | |||
62781355b1 | |||
9e6ce8171a | |||
aa1e6675c2 | |||
9f585bf1b4 | |||
ca03e96edd | |||
76b23c7646 | |||
82c8fa8661 | |||
74ab8bd8b9 | |||
62e7cc7bed | |||
1d9537126b | |||
2c915188a8 | |||
765fc9de42 | |||
b944e1f250 | |||
92243189ee | |||
4c896349b9 | |||
f3f2879c54 | |||
b70ba1da6f | |||
28146ad909 | |||
0348d0db00 | |||
84ca0a9481 | |||
aec9a2f2b8 | |||
42ec5faa4a | |||
1c3b478d49 | |||
5d752f6ee3 | |||
0503cad15f | |||
49dc661de1 | |||
59403b9cb5 | |||
4d26acea5b | |||
a38c9712e6 | |||
b871a4fac2 | |||
aacf7ece65 | |||
826cb0c1cb | |||
94e61e35a8 | |||
f09716b040 | |||
921febaeb4 | |||
e3d6ac20ea | |||
f3c85da8cc | |||
92c5b2baf7 | |||
9ad74ed887 | |||
27977a0c8a | |||
11475187fc | |||
f88f1578a8 | |||
b9e8ddfc9a | |||
99c94963e6 | |||
556e184ce5 | |||
c4f759e5d8 | |||
61b7a799b9 | |||
38cd9c74d9 | |||
67e068f053 | |||
24edf55f44 | |||
6770c133fb | |||
7a4816d34d | |||
d09908cb1d | |||
cd2dcf2873 | |||
c2d51b2b7e | |||
067c9b4035 | |||
3244db3438 | |||
fa27abb954 | |||
96820a6418 | |||
cf1c55782f | |||
bd4cfaafe1 | |||
679c369427 | |||
0fa54d9f7f | |||
015929b48d | |||
2a6929c6ff | |||
50b65d66e1 | |||
14be365a16 | |||
db686dbd8d | |||
16f62ceb8d | |||
28164f72da | |||
f243dd4583 | |||
fccc794528 | |||
12dfb491bc | |||
82520afa0e | |||
779d7263d0 | |||
762bdba724 | |||
592e9d2c86 | |||
ef33664f6f | |||
0d84cdb1c1 | |||
562e88dc43 | |||
8100f6fd71 | |||
07cae83e15 | |||
|
f7cfcbbced | ||
|
ca6954936f | ||
4eb258bb2f | |||
54b4e11335 | |||
aaafebec99 | |||
5258b215d4 | |||
ee3f3e1743 | |||
5b4e1df740 | |||
fec30b7e36 | |||
7a2bb1ba05 | |||
bb9e7b8d49 | |||
e4110f089e | |||
88b68b847c | |||
b625f22d6a | |||
f1a54d8782 | |||
616ae1ea82 | |||
0102a0b3ee | |||
789af166ae | |||
3098fef20c | |||
4d29b43123 | |||
5632c0c815 | |||
6f5db162a0 | |||
dde6c328f4 | |||
e3b4b9dd32 | |||
9821c17de3 | |||
2d3295757d | |||
ede69b6099 | |||
ca5bfafe88 | |||
d8db517fa5 | |||
77f2ffa93f | |||
664a9ec14a | |||
4284c38e9c | |||
b19f8965e0 | |||
0fefece3e3 | |||
5fe48dfd57 | |||
45b545d32d | |||
0cb09dc4b8 | |||
282ba45960 | |||
348fe6e724 | |||
153230580e | |||
ca3fa85df3 | |||
1fa5695a9f | |||
807c701edd | |||
282022dbd2 | |||
ba8663b22a | |||
ce44761420 | |||
ff97cc1cb2 | |||
b3474ddff2 | |||
64a9f41a64 | |||
1ab47ca03a | |||
6df059fe75 | |||
79b8c09a9b | |||
24ea198dad | |||
7c7a2d0824 | |||
0ff848c614 | |||
787687abf8 | |||
b4a843e699 | |||
1dc1370115 | |||
6f7d94641e | |||
3d51a81347 | |||
6e58844286 | |||
|
28e4f22d7c | ||
7a1967b404 | |||
3b3156e78a | |||
d29945eb96 | |||
f04cc7d1d5 | |||
e50ce3fa5b | |||
e0e72eeef8 | |||
204313abbf | |||
c4332d0ccd | |||
b078690fdf | |||
bc98b977f4 | |||
116b4de204 | |||
1df419cde5 | |||
3c5bac98a1 | |||
98cabd32af | |||
fa2d79c77e | |||
70832f8b63 | |||
ad92e51258 | |||
79f6ae8db7 | |||
e6b534078f | |||
a6dc8caaa0 | |||
23a6241e2c | |||
fe383249e4 | |||
322833ff13 | |||
dc9d7ab4e1 | |||
c67fbff9ed | |||
32a5ab6492 | |||
9f61123479 | |||
55cabc5840 | |||
0f705750e7 | |||
4a0792e4fe | |||
f5d8d4f18a | |||
7a3ad6ebc6 | |||
bd61bd734b | |||
f205ba8cff | |||
fb8db698a6 | |||
d1987fc710 | |||
98283e27d8 | |||
19a032b874 | |||
d3cbe06cb0 | |||
1634e98799 | |||
2bdefd1aa1 | |||
1ea3a59b6a | |||
815bf835d3 | |||
b5c7d430f4 | |||
72e68e18ac | |||
d08ffd1d17 | |||
a6e63b07f0 | |||
7703187cd9 | |||
30062ab65d | |||
a2f6805417 | |||
d13ad92dd1 | |||
f02a94b862 | |||
cce0e714dc | |||
42117a1386 | |||
b1cae947a7 | |||
c76f23bffd | |||
d51ee20abf | |||
40d3ebcc6f | |||
79c6f21a2f | |||
534079b6e7 | |||
fab01c3e89 | |||
c0d912a1c4 | |||
23552d62b1 | |||
4b248eb8cf | |||
f9a667b20b | |||
0f94c0b479 | |||
d798a057c7 | |||
2d19809ad3 | |||
d632cc11e4 | |||
318c582fe9 | |||
26aab3eef6 | |||
da2fa371e5 | |||
e808f751e1 | |||
8bfc9fcc6e | |||
5ef0ccf9a7 | |||
90f81fdf7f | |||
dd07a441b2 | |||
6caa031c86 | |||
e632843bab | |||
d8b985c993 | |||
510ae24dca | |||
06213df29f | |||
c81e12bb89 | |||
54c544113b | |||
e714599fad | |||
1df71121eb | |||
d62710bdf8 | |||
029073a4ea | |||
1c06748232 | |||
2d9c5b3093 | |||
33099df6ea | |||
59446649f6 | |||
4bd62ce881 | |||
e6cfe2b6e9 | |||
555de3d17b | |||
ed4f9152d4 | |||
3f81068182 | |||
e5e4323486 | |||
5357bfb122 | |||
b444648a3d | |||
e0516e4cc0 | |||
f412aaad27 | |||
05b8136a1e | |||
853a349a4b | |||
ea2b4fe148 | |||
d435438b20 | |||
255895097a | |||
06a92a5a8e | |||
388c0a274d | |||
d91665e3ae | |||
650ddc781f | |||
9f41603152 | |||
06bc655a59 | |||
ca402bd826 | |||
82c86b7b47 | |||
8223786bb5 | |||
aa901550a1 | |||
f8f499520e | |||
ffd034220c | |||
67819156ed | |||
e7ef11c423 | |||
a767c75014 | |||
9dfc99f49b | |||
88c65dd0df | |||
528deae789 | |||
be252836be | |||
3bb4f32bdb | |||
08a882bbb6 | |||
a892d875fd | |||
4f528ca2c8 | |||
754a5e7b98 | |||
161c2b3bc1 | |||
e740b9e314 | |||
493ac3ca03 | |||
f59827f95f | |||
bc8822e725 | |||
da336876b6 | |||
f7119a5b0f | |||
f3df8f1588 | |||
e7aba2a256 | |||
|
d6b7075229 | ||
7caa6d254f | |||
71b692c791 | |||
54cd406187 | |||
0d49a3fc93 | |||
6f643454ef | |||
1fd2dd1146 | |||
857d484b61 | |||
535de1cf50 | |||
e84b837dce | |||
a58d654d1d | |||
40a899066e | |||
185416f09d | |||
66236129a6 | |||
0187140e8e | |||
39e083d17c | |||
b38d5811a4 | |||
c895dfcfbe | |||
4653ce2f42 | |||
5605384b6a | |||
3e68fec704 | |||
d7c47f3c1f | |||
34604d1b7b | |||
b6dc104947 | |||
26d339e546 | |||
663258df0d | |||
451c7904d8 | |||
15b6d492f1 | |||
e4f6db8229 | |||
c7a75dd2b4 | |||
5c382db49c | |||
df249fd423 | |||
88f6a3fc4f | |||
9c7ed16538 | |||
1f86d4460c | |||
7faaa06079 | |||
2223243a45 | |||
dc879d0511 | |||
f1e5a0a237 | |||
9ec3e0cd94 | |||
7d03a69196 | |||
c82898b16c | |||
92a1b147fc | |||
d15710abfa | |||
d620e44114 | |||
e6dde8c42a | |||
7889849fe2 | |||
adecbe1e5c | |||
774f9de80f | |||
59cac2cbb3 | |||
b5e2b9a8a3 | |||
8f9a77c1eb | |||
96fce9a6d5 | |||
fdd7f91835 | |||
9951053b4f | |||
1f58d7a419 | |||
58549b7018 | |||
856aaec7ca | |||
1f6100c367 | |||
23d78c3b28 | |||
672aa287dc | |||
4def692123 | |||
086b5a9f2d | |||
dfdc06d048 | |||
365da603fa | |||
b902464446 | |||
71610b241c | |||
8c31a3b6d6 | |||
7648d31e53 | |||
24bba26c38 | |||
e6fb7d4cc4 | |||
7c33a6ef70 | |||
6652ed7354 | |||
a04643ea10 | |||
30b398c946 | |||
e5db6bebe8 | |||
506cce02cf | |||
325d391c2e | |||
3e4e76cf0f | |||
8a789e0786 | |||
ed13df885b | |||
e3cc53e23d | |||
1a8042191f | |||
77c314dd0f | |||
5e1600d35d | |||
c77afcb580 | |||
3778a8e6de | |||
b4c8cafde7 | |||
874a1cdfee | |||
fa889ad7fc | |||
c733a892fd | |||
d88220d9f7 | |||
a29feae442 | |||
28584be828 | |||
0d553b7dd4 | |||
9c1dc50e65 | |||
cfa23b8066 | |||
8ddd2f5fd5 | |||
fb1af97bae | |||
0b04c22b58 | |||
e2aa61f580 | |||
c9f74dd863 | |||
c5b51054df | |||
0e9c257ab7 | |||
5aafbc9cb2 | |||
5f0f830aea | |||
20540963ff | |||
76c9adbc43 | |||
5c5b1cd318 | |||
f41cbf70e3 | |||
6d735ebab5 | |||
b4de2cfe01 | |||
0c70bcb948 | |||
3dbbcd218a | |||
093abdb14b | |||
a48c3e5f8e | |||
47bfe810fc | |||
82e0078dbf | |||
c38b4b7f45 | |||
da4163bc6a | |||
f697e3e18e | |||
e2e51a0025 | |||
ad4b71ac03 | |||
627c33356c | |||
17372bafd2 | |||
ef6d0f82fd | |||
b800dcb407 | |||
28fcdd1e2d | |||
df09e27dee | |||
57b103c136 | |||
780b1ae837 | |||
3f90e93af1 | |||
192618b890 | |||
04e8a9e514 | |||
005014a9c7 | |||
b8f75e14ec | |||
833cd0f7e3 | |||
1818bf105d | |||
5f6c1f9f48 | |||
2505df6501 | |||
94bfdebf46 | |||
2c106f607c | |||
95f768aed3 | |||
5c34c92453 | |||
07c4682711 | |||
3dac007390 | |||
747dc66d67 | |||
27895a27be | |||
a266c68f9b | |||
d79370deb9 | |||
68bee55f6a | |||
29d8207c63 | |||
abfa97d99c | |||
76c5e50b0e | |||
270f9ab167 | |||
f10b0f5284 | |||
adbed10173 | |||
2e3cc1837b | |||
951187783b | |||
e6e9c9424b | |||
3f34ecee28 | |||
0644c40b1c | |||
036396a382 | |||
7be02bb292 | |||
8d559dc664 | |||
a5dd9f0650 | |||
5fc70bb4fe | |||
08b4227b34 | |||
187812576c | |||
8412588940 | |||
4c75701c0d | |||
9eda005399 | |||
e6c96bed21 | |||
9c8df03c36 | |||
609ba57078 | |||
772aeae20f | |||
9bd5d62ca7 | |||
a92acd203d | |||
0c910bff1a | |||
a41acb28f6 | |||
14181c9c51 | |||
276c492355 | |||
fe91235436 | |||
08c793b912 | |||
bc0452aa2f | |||
27f66cfea3 | |||
5e2a68dc84 | |||
08b61d08e9 | |||
c3ec94c063 | |||
9ec66803c8 | |||
afbde14116 | |||
52b1959338 | |||
4607d146a4 | |||
bf01e41e32 | |||
756db06540 | |||
d6f9ceb5c7 | |||
f1a6f99fc4 | |||
3335093e87 | |||
1d6e347b78 | |||
f163ea41d6 | |||
d30e90937b | |||
30834be3a8 | |||
282fb44603 | |||
aac478a455 | |||
b14413af3f | |||
cc65cc9cf1 | |||
1ccce00e46 | |||
7f1bcc841a | |||
ba6ada32f9 | |||
3d66fa9c11 | |||
2a3a64f0eb | |||
1b93adefa3 | |||
756b5b5136 | |||
ac76994a70 | |||
1d2936df92 | |||
fec9de0e3d | |||
5a69da4466 | |||
4772c6df95 | |||
0632ebba66 | |||
b61f0b5e6c | |||
d54d180090 | |||
23fa88bb33 | |||
c0c90eb565 | |||
1f80491968 | |||
691387b7c9 | |||
229387a972 | |||
8ba6e83032 | |||
3f7711dd20 | |||
9fc3d60835 | |||
c9e0b333bf | |||
6da1924f53 | |||
1afb45522d | |||
e0b58a29ea | |||
675ccac14d | |||
47b8a83d86 | |||
1bc1d2fa31 | |||
1844f89e63 | |||
741d9d0805 | |||
05391eceab | |||
cf7b645d54 | |||
3710f42505 | |||
01d25f2817 | |||
d17549c0fb | |||
79f71eee09 | |||
de2f4fca1e | |||
aa2e6a5418 | |||
29041a4667 | |||
a7063e9a49 | |||
35b42d5234 | |||
d8e67e914f | |||
6817082816 | |||
5caa85d8f1 | |||
d895c44c0c | |||
86065d16df | |||
ea1b63265b | |||
52c1cff5e8 | |||
5fee288016 | |||
c08880c19d | |||
1c08959ed3 | |||
4e58950ae2 | |||
c7b4ddf71e | |||
3d19f93001 | |||
5db1d8b494 | |||
181af86899 | |||
352ebb4105 | |||
3127e06a47 | |||
44e4aaa732 | |||
125dc96de5 | |||
5aa8a70b0b | |||
510211cfd0 | |||
26c6df74e4 | |||
d8c89c9deb | |||
dbaadc4c2a | |||
2da5935d1f | |||
9d00fc140c | |||
28adcaf95a | |||
a5f59092bb | |||
598d987b37 | |||
eb8090b350 | |||
38abb27eb3 | |||
b6307eb88b | |||
b2522acd08 | |||
ba4597973e | |||
63b26a104d | |||
b346bdf337 | |||
c12bff6027 | |||
825e0c1a90 | |||
db69b13d87 | |||
918b8fd18c | |||
170acbaffd | |||
2effae1bd5 | |||
41e6f6e57a | |||
5c188f3513 | |||
3019e9f62e | |||
dcf5bebb9b | |||
fc02a68691 | |||
b1549440e0 | |||
af29c68ec9 | |||
f3a44e6f33 | |||
6555aac2fb | |||
93b885dc0d | |||
d746de34ea | |||
49bd468aa4 | |||
f007ad987d | |||
daf4b71bbb | |||
e59ead5a84 | |||
2e3306708e | |||
|
c59288b5f9 | ||
6ff4ee2746 | |||
9b03f102f3 | |||
816a309f18 | |||
b85ddb9464 | |||
3918ce4eb7 | |||
3bd5c7d218 | |||
9c73ab928c | |||
016e0988e9 | |||
97dfa89b6d | |||
f5549934fe | |||
27ac7e8063 | |||
275b0eea40 | |||
fa4940f22d | |||
bfe46fbbd1 | |||
38faaebb5f | |||
3c124456d0 | |||
b4489311c9 | |||
|
7a9fe42d83 | ||
|
dd0e15137a | ||
205c7ac76d | |||
|
6455541210 | ||
|
7cdd79a116 | ||
6e4e8edd9d | |||
1251486aa3 | |||
1b8ed53afb | |||
|
5e048977a3 | ||
|
355aff2951 | ||
3c41682d73 | |||
77709c068a | |||
d48cafa54d | |||
f386766841 | |||
52397bbd61 | |||
99b429433c | |||
c23c63cb15 | |||
fff46421bf | |||
61f1963db8 | |||
253f191113 | |||
6622014bd1 | |||
bd6b5e2b54 | |||
4ed6200d9f | |||
|
c009a96a15 | ||
ae88282b13 | |||
|
38b2f34527 | ||
afced2339a | |||
83cd815750 | |||
ca2e72d3f0 | |||
3c4ba096c6 | |||
1891aafef5 | |||
aee2fa7120 | |||
c55f91a79f | |||
|
8f95bfe7e0 | ||
|
db33f46547 | ||
68cb36b193 | |||
ff28b40c9e | |||
e6b4fe59a3 | |||
0cd30e811d | |||
d3541da789 | |||
cdb4406e14 | |||
6ef8caca00 | |||
beb127c06c | |||
|
9b38242f9d | ||
|
d63d33d245 | ||
30f18106cb | |||
fdd0da8d93 | |||
4ad6178bf0 | |||
32d20a9234 | |||
def424da72 | |||
c99d4ee53d | |||
935076cc63 | |||
766fad6bb2 | |||
672b781425 | |||
95ecc9e9b8 | |||
8625f20b74 | |||
944a0c9c2a | |||
f22015635e | |||
23122964d2 | |||
67080a098f | |||
5bf46e0840 | |||
454679626c | |||
e2e27c2311 | |||
3cf753a707 | |||
9ed18ce131 | |||
6b1f39525d | |||
|
725915060b | ||
|
2b6b8bff43 | ||
f49e4fe3d8 | |||
|
9cf958b1ed | ||
|
25981afeef | ||
3de32f52af | |||
15707167f1 | |||
|
c86d72f3e2 | ||
|
edd393793a | ||
f9b3d64eca | |||
83f1a9afd9 | |||
d53524ed86 | |||
aedabd6eda | |||
|
af95ac941f | ||
|
7c5a73e73b | ||
5e5434d057 | |||
9f123822b1 | |||
651b9c4483 | |||
e71e76dbc6 | |||
7917c39065 | |||
e8a9982f9a | |||
e634a22134 | |||
a7ae1ac3a6 | |||
5624d9b44e | |||
92d9124bb7 | |||
8fb6dae119 | |||
5f6119c86b | |||
602759b471 | |||
c788cf5d87 | |||
9193938dee | |||
b1c6039630 | |||
67799fcdfa | |||
dfe0b3a6cf | |||
cee211621c | |||
8904054212 | |||
6450a76351 | |||
54371bf76a | |||
c33575e3d8 | |||
|
ef4e9b3e85 |
20
.editorconfig
Normal file
20
.editorconfig
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# EditorConfig is awesome: http://EditorConfig.org
|
||||||
|
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Unix-style newlines with a newline ending every file
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = false
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = tab
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.{cpp,c,h,hpp,cxx}]
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
# Yaml files
|
||||||
|
[*.{yml,yaml}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
151
.gitignore
vendored
151
.gitignore
vendored
@ -1,10 +1,151 @@
|
|||||||
|
|
||||||
|
# Created by https://www.gitignore.io/api/macos,jetbrains+all
|
||||||
|
|
||||||
|
### JetBrains+all ###
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
### JetBrains+all Patch ###
|
||||||
|
# Ignores the whole .idea folder and all .iml files
|
||||||
|
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
|
||||||
|
|
||||||
|
*.iml
|
||||||
|
modules.xml
|
||||||
|
.idea/misc.xml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### macOS ###
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
|
||||||
|
# End of https://www.gitignore.io/api/macos,jetbrains+all
|
||||||
|
|
||||||
|
.codelite
|
||||||
|
.phing_targets
|
||||||
|
.sonar/
|
||||||
|
*.phprj
|
||||||
|
*.workspace
|
||||||
vendor
|
vendor
|
||||||
app/cache/*
|
**/cache/**
|
||||||
public/images/*
|
**/logs/**
|
||||||
public/js/cache/*
|
**/coverage/**
|
||||||
|
**/docs/**
|
||||||
|
**/node_modules/**
|
||||||
composer.lock
|
composer.lock
|
||||||
*.sqlite
|
*.sqlite
|
||||||
*.db
|
*.db
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
docs/*
|
apidocs/**
|
||||||
coverage/*
|
tests/test_data/sessions/*
|
||||||
|
cache.properties
|
||||||
|
build/**
|
||||||
|
!build/*.txt
|
||||||
|
!build/*.xml
|
||||||
|
!build/*.php
|
||||||
|
app/config/*.toml
|
||||||
|
!app/config/*.toml.example
|
||||||
|
phinx.yml
|
||||||
|
Caddyfile
|
||||||
|
build/humbuglog.txt
|
||||||
|
public/images/anime/**
|
||||||
|
public/images/avatars/**
|
||||||
|
public/images/manga/**
|
||||||
|
public/images/characters/**
|
||||||
|
public/images/people/**
|
||||||
|
public/mal_mappings.json
|
||||||
|
.phpunit.result.cache
|
||||||
|
|
||||||
|
.is-dev
|
6
.htaccess
Normal file
6
.htaccess
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#Rewrite index.php out of the app urls
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteBase /
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule ^(.*)$ index.php/$1 [L]
|
16
.travis.yml
16
.travis.yml
@ -1,20 +1,16 @@
|
|||||||
language: php
|
language: php
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- composer install
|
- composer install --ignore-platform-reqs
|
||||||
|
|
||||||
php:
|
php:
|
||||||
- 5.4
|
- 7.4
|
||||||
- 5.5
|
|
||||||
- 5.6
|
|
||||||
- 7
|
|
||||||
- hhvm
|
|
||||||
- nightly
|
- nightly
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- mkdir -p build/logs
|
- mkdir -p build/logs
|
||||||
- phpunit --coverage-clover=coverage.clover
|
- php vendor/bin/phpunit -c build
|
||||||
|
|
||||||
after_script:
|
matrix:
|
||||||
- wget https://scrutinizer-ci.com/ocular.phar
|
allow_failures:
|
||||||
- php ocular.phar code-coverage:upload --format=php-clover coverage.clover
|
- php: nightly
|
||||||
|
43
CHANGELOG.md
Normal file
43
CHANGELOG.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## Version 5.1
|
||||||
|
* Added session check, so when coming back to a page, if the session is expired, the page will refresh.
|
||||||
|
* Updated logging config so that much fewer, much smaller files are generated.
|
||||||
|
* Updated Kitsu integration to use GraphQL API, reducing a lot of internal complexity.
|
||||||
|
|
||||||
|
## Version 5
|
||||||
|
* Updated PHP requirement to 7.4
|
||||||
|
* Added anime watching history view
|
||||||
|
* Added manga reading history view
|
||||||
|
* Updated anime collection to have more media types
|
||||||
|
|
||||||
|
## Version 4.2
|
||||||
|
* Updated dependencies
|
||||||
|
* Updated PHP requirement to 7.3
|
||||||
|
* Added option to automatically set dark mode based on the OS setting
|
||||||
|
|
||||||
|
## Version 4.1
|
||||||
|
* Added optional dark theme
|
||||||
|
* Removed MAL integration, added Anilist Integration
|
||||||
|
* Now uses WebP cache images when the browser supports it
|
||||||
|
* Replaces JS minifier with pre-minified scripts (Removes the need for one caching folder, too)
|
||||||
|
* Updated console command to sync Kitsu and Anilist data (Kitsu can sync MAL, and MAL's API broke, so MAL sync was removed)
|
||||||
|
* Added page to update settings without having to edit config files
|
||||||
|
* Defaulted to secure (HTTPS) urls
|
||||||
|
* Updated Character pages to show voice actors
|
||||||
|
* Added People pages, showing which works they contributed to, and in what role
|
||||||
|
|
||||||
|
## Version 4
|
||||||
|
* Updated to use Kitsu API after discontinuation of Hummingbird
|
||||||
|
* Added streaming links to list entries from the Kitsu API
|
||||||
|
* Added simple integration with MyAnimeList, so an update can cross-post to both Kitsu and MyAnimeList (anime and manga)
|
||||||
|
* Added console command to sync Kitsu and MyAnimeList data
|
||||||
|
* Added character pages
|
||||||
|
|
||||||
|
## Version 3
|
||||||
|
* Converted user configuration to toml files
|
||||||
|
* Added a caching layer for api calls, which resets upon updates from the
|
||||||
|
app.
|
||||||
|
* Added a bulk thumbnail generator script
|
||||||
|
* Removed json file "cache" from the app folder
|
||||||
|
|
49
Jenkinsfile
vendored
Normal file
49
Jenkinsfile
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
pipeline {
|
||||||
|
agent none
|
||||||
|
stages {
|
||||||
|
stage('setup') {
|
||||||
|
agent any
|
||||||
|
steps {
|
||||||
|
sh 'curl -sS https://getcomposer.org/installer | php'
|
||||||
|
sh 'rm -rf ./vendor'
|
||||||
|
sh 'rm -f composer.lock'
|
||||||
|
sh 'php composer.phar install --ignore-platform-reqs'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('PHP 7.4') {
|
||||||
|
agent {
|
||||||
|
docker {
|
||||||
|
image 'php:7.4-alpine'
|
||||||
|
args '-u root --privileged'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'apk add --no-cache git'
|
||||||
|
sh 'php ./vendor/bin/phpunit --colors=never'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Latest PHP') {
|
||||||
|
agent {
|
||||||
|
docker {
|
||||||
|
image 'php:alpine'
|
||||||
|
args '-u root --privileged'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
sh 'apk add --no-cache git'
|
||||||
|
sh 'php ./vendor/bin/phpunit --colors=never'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Coverage') {
|
||||||
|
agent any
|
||||||
|
steps {
|
||||||
|
sh 'php composer.phar run-script coverage'
|
||||||
|
step([
|
||||||
|
$class: 'CloverPublisher',
|
||||||
|
cloverReportDir: '',
|
||||||
|
cloverReportFileName: 'build/logs/clover.xml',
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2017 Timothy J Warren
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
45
README.md
45
README.md
@ -1,10 +1,11 @@
|
|||||||
# Hummingbird Anime Client
|
# Hummingbird Anime Client
|
||||||
|
|
||||||
A self-hosted client that allows custom formatting of data from the hummingbird api
|
Update your anime/manga list on Kitsu.io and Anilist
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/timw4mail/HummingBirdAnimeClient.svg)](https://travis-ci.org/timw4mail/HummingBirdAnimeClient)
|
[![Build Status](https://travis-ci.com/timw4mail/HummingBirdAnimeClient.svg?branch=master)](https://travis-ci.com/github/timw4mail/HummingBirdAnimeClient)
|
||||||
|
[![Build Status](https://jenkins.timshomepage.net/buildStatus/icon?job=timw4mail/HummingBirdAnimeClient/develop)](https://jenkins.timshomepage.net/job/timw4mail/HummingBirdAnimeClient/develop)
|
||||||
|
|
||||||
[[Hosted Example](https://anime.timshomepage.net)]
|
[[Hosted Example](https://list.timshomepage.net)]
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@ -14,7 +15,7 @@ A self-hosted client that allows custom formatting of data from the hummingbird
|
|||||||
* On Hold
|
* On Hold
|
||||||
* Dropped
|
* Dropped
|
||||||
* Completed
|
* Completed
|
||||||
* All of the above
|
* Combined View
|
||||||
|
|
||||||
* Manga List views (Each with list and cover views):
|
* Manga List views (Each with list and cover views):
|
||||||
* Reading
|
* Reading
|
||||||
@ -22,7 +23,7 @@ A self-hosted client that allows custom formatting of data from the hummingbird
|
|||||||
* On Hold
|
* On Hold
|
||||||
* Dropped
|
* Dropped
|
||||||
* Completed
|
* Completed
|
||||||
* All of the above
|
* Combined View
|
||||||
|
|
||||||
* Anime collection view (segmented by media type):
|
* Anime collection view (segmented by media type):
|
||||||
* Cover Images
|
* Cover Images
|
||||||
@ -30,26 +31,30 @@ A self-hosted client that allows custom formatting of data from the hummingbird
|
|||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
* PHP 5.4+
|
* PHP 7.4+
|
||||||
* PDO SQLite (For collection tab)
|
* PDO SQLite or PDO PostgreSQL (For collection tab)
|
||||||
* GD
|
* GD extension for caching images
|
||||||
|
|
||||||
|
### Highly Recommended
|
||||||
|
* Redis or Memcached for caching
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
1. Install dependencies via composer: `composer install`
|
1. Install via git, then install dependencies via composer: `composer install`
|
||||||
2. Change the `WHOSE` constant declaration in `index.php` to your name
|
2. Duplicate `app/config/config.toml.example` file as `app/config/config.toml`
|
||||||
3. Configure settings in `app/config/config.php` to your liking
|
3. Configure settings in `app/config/config.toml` to your liking
|
||||||
4. Create the following directories if they don't exist, and make sure they are world writable
|
4. Create the following directories if they don't exist, and make sure they are world writable
|
||||||
* app/cache
|
* app/config
|
||||||
* public/images/manga
|
* app/logs
|
||||||
|
* public/images/avatars
|
||||||
* public/images/anime
|
* public/images/anime
|
||||||
* public/js/cache
|
* public/images/characters
|
||||||
|
* public/images/manga
|
||||||
|
5. Make sure the `console` script is executable
|
||||||
|
6. Additional settings are on the settings page once you log in.
|
||||||
|
|
||||||
#### Anime Collection Additional Installation
|
### Server Setup
|
||||||
* Run `php /vendor/bin/phinx migrate -e development` to create the database tables
|
|
||||||
* For importing anime:
|
|
||||||
1. Find the anime you are looking for on the hummingbird search api page: `https://hummingbird.me/api/v1/search/anime?query=`
|
|
||||||
2. Create an `import.json` file in the root of the app, with an array of objects from the search page that you want to import
|
|
||||||
3. Go to the anime collection tab, and the import will be run
|
|
||||||
|
|
||||||
|
See the [wiki](https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient/wiki)
|
||||||
|
for more in-depth information
|
||||||
|
|
316
RoboFile.php
Normal file
316
RoboFile.php
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
use Robo\Tasks;
|
||||||
|
|
||||||
|
if ( ! function_exists('glob_recursive'))
|
||||||
|
{
|
||||||
|
// Does not support flag GLOB_BRACE
|
||||||
|
function glob_recursive($pattern, $flags = 0)
|
||||||
|
{
|
||||||
|
$files = glob($pattern, $flags);
|
||||||
|
|
||||||
|
foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir)
|
||||||
|
{
|
||||||
|
$files = array_merge($files, glob_recursive($dir.'/'.basename($pattern), $flags));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is project's console commands configuration for Robo task runner.
|
||||||
|
*
|
||||||
|
* @see http://robo.li/
|
||||||
|
*/
|
||||||
|
class RoboFile extends Tasks {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directories used by analysis tools
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $taskDirs = [
|
||||||
|
'build/logs',
|
||||||
|
'build/pdepend',
|
||||||
|
'build/phpdox',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directories to remove with the clean task
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $cleanDirs = [
|
||||||
|
'coverage',
|
||||||
|
'docs',
|
||||||
|
'phpdoc',
|
||||||
|
'build/logs',
|
||||||
|
'build/phpdox',
|
||||||
|
'build/pdepend'
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do static analysis tasks
|
||||||
|
*/
|
||||||
|
public function analyze(): void
|
||||||
|
{
|
||||||
|
$this->prepare();
|
||||||
|
$this->lint();
|
||||||
|
$this->phploc(TRUE);
|
||||||
|
$this->phpcs(TRUE);
|
||||||
|
$this->phpmd(TRUE);
|
||||||
|
$this->dependencyReport();
|
||||||
|
$this->phpcpdReport();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run all tests, generate coverage, generate docs, generate code statistics
|
||||||
|
*/
|
||||||
|
public function build(): void
|
||||||
|
{
|
||||||
|
$this->analyze();
|
||||||
|
$this->coverage();
|
||||||
|
$this->docs();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup temporary files
|
||||||
|
*/
|
||||||
|
public function clean(): void
|
||||||
|
{
|
||||||
|
$cleanFiles = [
|
||||||
|
'build/humbug.json',
|
||||||
|
'build/humbug-log.txt',
|
||||||
|
];
|
||||||
|
array_map(static function ($file) {
|
||||||
|
@unlink($file);
|
||||||
|
}, $cleanFiles);
|
||||||
|
|
||||||
|
// So the task doesn't complain,
|
||||||
|
// make any 'missing' dirs to cleanup
|
||||||
|
array_map(static function ($dir) {
|
||||||
|
if ( ! is_dir($dir))
|
||||||
|
{
|
||||||
|
`mkdir -p {$dir}`;
|
||||||
|
}
|
||||||
|
}, $this->cleanDirs);
|
||||||
|
|
||||||
|
$this->_cleanDir($this->cleanDirs);
|
||||||
|
$this->_deleteDir($this->cleanDirs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run unit tests and generate coverage reports
|
||||||
|
*/
|
||||||
|
public function coverage(): void
|
||||||
|
{
|
||||||
|
$this->_run(['phpdbg -qrr -- vendor/bin/phpunit -c build']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate documentation with phpdox
|
||||||
|
*/
|
||||||
|
public function docs(): void
|
||||||
|
{
|
||||||
|
$cmd_parts = [
|
||||||
|
'vendor/bin/phpdox',
|
||||||
|
];
|
||||||
|
$this->_run($cmd_parts, ' && ');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that source files are valid
|
||||||
|
*/
|
||||||
|
public function lint(): void
|
||||||
|
{
|
||||||
|
$files = $this->getAllSourceFiles();
|
||||||
|
|
||||||
|
$chunks = array_chunk($files, (int)shell_exec('getconf _NPROCESSORS_ONLN'));
|
||||||
|
|
||||||
|
foreach($chunks as $chunk)
|
||||||
|
{
|
||||||
|
$this->parallelLint($chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the phpcs tool
|
||||||
|
*
|
||||||
|
* @param bool $report - if true, generates reports instead of direct output
|
||||||
|
*/
|
||||||
|
public function phpcs($report = FALSE): void
|
||||||
|
{
|
||||||
|
$report_cmd_parts = [
|
||||||
|
'vendor/bin/phpcs',
|
||||||
|
'--standard=./build/phpcs.xml',
|
||||||
|
'--report-checkstyle=./build/logs/phpcs.xml',
|
||||||
|
];
|
||||||
|
|
||||||
|
$normal_cmd_parts = [
|
||||||
|
'vendor/bin/phpcs',
|
||||||
|
'--standard=./build/phpcs.xml',
|
||||||
|
];
|
||||||
|
|
||||||
|
$cmd_parts = ($report) ? $report_cmd_parts : $normal_cmd_parts;
|
||||||
|
|
||||||
|
$this->_run($cmd_parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function phpmd($report = FALSE): void
|
||||||
|
{
|
||||||
|
$report_cmd_parts = [
|
||||||
|
'vendor/bin/phpmd',
|
||||||
|
'./src',
|
||||||
|
'xml',
|
||||||
|
'cleancode,codesize,controversial,design,naming,unusedcode',
|
||||||
|
'--exclude ParallelAPIRequest',
|
||||||
|
'--reportfile ./build/logs/phpmd.xml'
|
||||||
|
];
|
||||||
|
|
||||||
|
$normal_cmd_parts = [
|
||||||
|
'vendor/bin/phpmd',
|
||||||
|
'./src',
|
||||||
|
'ansi',
|
||||||
|
'cleancode,codesize,controversial,design,naming,unusedcode',
|
||||||
|
'--exclude ParallelAPIRequest'
|
||||||
|
];
|
||||||
|
|
||||||
|
$cmd_parts = ($report) ? $report_cmd_parts : $normal_cmd_parts;
|
||||||
|
|
||||||
|
$this->_run($cmd_parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the phploc tool
|
||||||
|
*
|
||||||
|
* @param bool $report - if true, generates reports instead of direct output
|
||||||
|
*/
|
||||||
|
public function phploc($report = FALSE): void
|
||||||
|
{
|
||||||
|
// Command for generating reports
|
||||||
|
$report_cmd_parts = [
|
||||||
|
'vendor/bin/phploc',
|
||||||
|
'--count-tests',
|
||||||
|
'--log-csv=build/logs/phploc.csv',
|
||||||
|
'--log-xml=build/logs/phploc.xml',
|
||||||
|
'src',
|
||||||
|
'tests'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Command for generating direct output
|
||||||
|
$normal_cmd_parts = [
|
||||||
|
'vendor/bin/phploc',
|
||||||
|
'--count-tests',
|
||||||
|
'src',
|
||||||
|
'tests'
|
||||||
|
];
|
||||||
|
|
||||||
|
$cmd_parts = ($report) ? $report_cmd_parts : $normal_cmd_parts;
|
||||||
|
|
||||||
|
$this->_run($cmd_parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create temporary directories
|
||||||
|
*/
|
||||||
|
public function prepare(): void
|
||||||
|
{
|
||||||
|
array_map([$this, '_mkdir'], $this->taskDirs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lint php files and run unit tests
|
||||||
|
*/
|
||||||
|
public function test(): void
|
||||||
|
{
|
||||||
|
$this->lint();
|
||||||
|
|
||||||
|
$this->_run(['vendor/bin/phpunit']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create pdepend reports
|
||||||
|
*/
|
||||||
|
protected function dependencyReport(): void
|
||||||
|
{
|
||||||
|
$cmd_parts = [
|
||||||
|
'vendor/bin/pdepend',
|
||||||
|
'--jdepend-xml=build/logs/jdepend.xml',
|
||||||
|
'--jdepend-chart=build/pdepend/dependencies.svg',
|
||||||
|
'--overview-pyramid=build/pdepend/overview-pyramid.svg',
|
||||||
|
'src'
|
||||||
|
];
|
||||||
|
$this->_run($cmd_parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the total list of source files, including tests
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getAllSourceFiles(): array
|
||||||
|
{
|
||||||
|
$files = array_merge(
|
||||||
|
glob_recursive('build/*.php'),
|
||||||
|
glob_recursive('src/*.php'),
|
||||||
|
glob_recursive('src/**/*.php'),
|
||||||
|
glob_recursive('tests/*.php'),
|
||||||
|
glob_recursive('tests/**/*.php'),
|
||||||
|
glob('*.php')
|
||||||
|
);
|
||||||
|
|
||||||
|
$files = array_filter($files, static function(string $value) {
|
||||||
|
return strpos($value, '__snapshots__') === FALSE;
|
||||||
|
});
|
||||||
|
|
||||||
|
sort($files);
|
||||||
|
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run php's linter in one parallel task for the passed chunk
|
||||||
|
*
|
||||||
|
* @param array $chunk
|
||||||
|
*/
|
||||||
|
protected function parallelLint(array $chunk): void
|
||||||
|
{
|
||||||
|
$task = $this->taskParallelExec()
|
||||||
|
->timeout(5)
|
||||||
|
->printed(FALSE);
|
||||||
|
|
||||||
|
foreach($chunk as $file)
|
||||||
|
{
|
||||||
|
$task = $task->process("php -l {$file}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$task->run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate copy paste detector report
|
||||||
|
*/
|
||||||
|
protected function phpcpdReport(): void
|
||||||
|
{
|
||||||
|
$cmd_parts = [
|
||||||
|
'vendor/bin/phpcpd',
|
||||||
|
'--log-pmd build/logs/pmd-cpd.xml',
|
||||||
|
'src'
|
||||||
|
];
|
||||||
|
$this->_run($cmd_parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut for joining an array of command arguments
|
||||||
|
* and then running it
|
||||||
|
*
|
||||||
|
* @param array $cmd_parts - command arguments
|
||||||
|
* @param string $join_on - what to join the command arguments with
|
||||||
|
*/
|
||||||
|
protected function _run(array $cmd_parts, $join_on = ' '): void
|
||||||
|
{
|
||||||
|
$this->taskExec(implode($join_on, $cmd_parts))->run();
|
||||||
|
}
|
||||||
|
}
|
59
app/appConf/base_config.php
Normal file
59
app/appConf/base_config.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Hummingbird Anime List Client
|
||||||
|
*
|
||||||
|
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
||||||
|
*
|
||||||
|
* PHP version 7
|
||||||
|
*
|
||||||
|
* @package HummingbirdAnimeClient
|
||||||
|
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||||
|
* @copyright 2015 - 2017 Timothy J. Warren
|
||||||
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||||
|
* @version 4.0
|
||||||
|
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||||
|
*/
|
||||||
|
|
||||||
|
use function Aviat\AnimeClient\loadToml;
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Lower level configuration
|
||||||
|
//
|
||||||
|
// You shouldn't generally need to change anything below this line
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
$APP_DIR = realpath(__DIR__ . '/../');
|
||||||
|
$ROOT_DIR = realpath("{$APP_DIR}/../");
|
||||||
|
|
||||||
|
$tomlConfig = loadToml(__DIR__);
|
||||||
|
|
||||||
|
return array_merge($tomlConfig, [
|
||||||
|
'asset_dir' => "{$ROOT_DIR}/public",
|
||||||
|
'base_config_dir' => __DIR__,
|
||||||
|
'config_dir' => "{$APP_DIR}/config",
|
||||||
|
|
||||||
|
// No config defaults
|
||||||
|
'kitsu_username' => 'timw4mail',
|
||||||
|
'whose_list' => 'Someone',
|
||||||
|
'cache' => [
|
||||||
|
'connection' => [],
|
||||||
|
'driver' => 'null',
|
||||||
|
],
|
||||||
|
'secure_urls' => TRUE,
|
||||||
|
|
||||||
|
// Routing defaults
|
||||||
|
'asset_path' => '/public',
|
||||||
|
'default_list' => 'anime', //anime|manga
|
||||||
|
'default_anime_list_path' => 'watching', // watching|plan_to_watch|on_hold|dropped|completed|all
|
||||||
|
'default_manga_list_path' => 'reading', // reading|plan_to_read|on_hold|dropped|completed|all
|
||||||
|
'default_view_type' => 'cover_view', // cover_view|list_view
|
||||||
|
|
||||||
|
// Template file path
|
||||||
|
'view_path' => "{$APP_DIR}/views",
|
||||||
|
|
||||||
|
// Cache paths
|
||||||
|
'data_cache_path' => "{$APP_DIR}/cache",
|
||||||
|
'img_cache_path' => "{$ROOT_DIR}/public/images",
|
||||||
|
|
||||||
|
// Included config files
|
||||||
|
'routes' => require 'routes.php',
|
||||||
|
]);
|
21
app/appConf/menus.toml
Normal file
21
app/appConf/menus.toml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[anime_list]
|
||||||
|
route_prefix = ""
|
||||||
|
[anime_list.items]
|
||||||
|
watch_history = '/history/anime'
|
||||||
|
watching = '/anime/watching'
|
||||||
|
plan_to_watch = '/anime/plan_to_watch'
|
||||||
|
on_hold = '/anime/on_hold'
|
||||||
|
dropped = '/anime/dropped'
|
||||||
|
completed = '/anime/completed'
|
||||||
|
all = '/anime/all'
|
||||||
|
|
||||||
|
[manga_list]
|
||||||
|
route_prefix = ""
|
||||||
|
[manga_list.items]
|
||||||
|
reading_history = '/history/manga'
|
||||||
|
reading = '/manga/reading'
|
||||||
|
plan_to_read = '/manga/plan_to_read'
|
||||||
|
on_hold = '/manga/on_hold'
|
||||||
|
dropped = '/manga/dropped'
|
||||||
|
completed = '/manga/completed'
|
||||||
|
all = '/manga/all'
|
320
app/appConf/routes.php
Normal file
320
app/appConf/routes.php
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Hummingbird Anime List Client
|
||||||
|
*
|
||||||
|
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
||||||
|
*
|
||||||
|
* PHP version 7
|
||||||
|
*
|
||||||
|
* @package HummingbirdAnimeClient
|
||||||
|
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||||
|
* @copyright 2015 - 2018 Timothy J. Warren
|
||||||
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||||
|
* @version 4.0
|
||||||
|
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||||
|
*/
|
||||||
|
|
||||||
|
use const Aviat\AnimeClient\{
|
||||||
|
ALPHA_SLUG_PATTERN,
|
||||||
|
NUM_PATTERN,
|
||||||
|
SLUG_PATTERN,
|
||||||
|
DEFAULT_CONTROLLER_METHOD,
|
||||||
|
DEFAULT_CONTROLLER
|
||||||
|
};
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Routing Config
|
||||||
|
//
|
||||||
|
// Maps paths to controllers and methods
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
$routes = [
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// AJAX Routes
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
'cache_purge' => [
|
||||||
|
'path' => '/cache_purge',
|
||||||
|
'action' => 'clearCache',
|
||||||
|
],
|
||||||
|
'heartbeat' => [
|
||||||
|
'path' => '/heartbeat',
|
||||||
|
'action' => 'heartbeat',
|
||||||
|
],
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Anime List Routes
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
'anime.add.get' => [
|
||||||
|
'path' => '/anime/add',
|
||||||
|
'action' => 'addForm',
|
||||||
|
],
|
||||||
|
'anime.add.post' => [
|
||||||
|
'path' => '/anime/add',
|
||||||
|
'action' => 'add',
|
||||||
|
'verb' => 'post',
|
||||||
|
],
|
||||||
|
'anime.details' => [
|
||||||
|
'path' => '/anime/details/{id}',
|
||||||
|
'action' => 'details',
|
||||||
|
'tokens' => [
|
||||||
|
'id' => SLUG_PATTERN,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'anime.delete' => [
|
||||||
|
'path' => '/anime/delete',
|
||||||
|
'action' => 'delete',
|
||||||
|
'verb' => 'post',
|
||||||
|
],
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Manga Routes
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
'manga.search' => [
|
||||||
|
'path' => '/manga/search',
|
||||||
|
'action' => 'search',
|
||||||
|
],
|
||||||
|
'manga.add.get' => [
|
||||||
|
'path' => '/manga/add',
|
||||||
|
'action' => 'addForm',
|
||||||
|
],
|
||||||
|
'manga.add.post' => [
|
||||||
|
'path' => '/manga/add',
|
||||||
|
'action' => 'add',
|
||||||
|
'verb' => 'post',
|
||||||
|
],
|
||||||
|
'manga.delete' => [
|
||||||
|
'path' => '/manga/delete',
|
||||||
|
'action' => 'delete',
|
||||||
|
'verb' => 'post',
|
||||||
|
],
|
||||||
|
'manga.details' => [
|
||||||
|
'path' => '/manga/details/{id}',
|
||||||
|
'action' => 'details',
|
||||||
|
'tokens' => [
|
||||||
|
'id' => SLUG_PATTERN,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Anime Collection Routes
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
'anime.collection.search' => [
|
||||||
|
'path' => '/anime-collection/search',
|
||||||
|
'action' => 'search',
|
||||||
|
],
|
||||||
|
'anime.collection.add.get' => [
|
||||||
|
'path' => '/anime-collection/add',
|
||||||
|
'action' => 'form',
|
||||||
|
],
|
||||||
|
'anime.collection.edit.get' => [
|
||||||
|
'path' => '/anime-collection/edit/{id}',
|
||||||
|
'action' => 'form',
|
||||||
|
'tokens' => [
|
||||||
|
'id' => NUM_PATTERN,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'anime.collection.add.post' => [
|
||||||
|
'path' => '/anime-collection/add',
|
||||||
|
'action' => 'add',
|
||||||
|
'verb' => 'post',
|
||||||
|
],
|
||||||
|
'anime.collection.edit.post' => [
|
||||||
|
'path' => '/anime-collection/edit',
|
||||||
|
'action' => 'edit',
|
||||||
|
'verb' => 'post',
|
||||||
|
],
|
||||||
|
'anime.collection.view' => [
|
||||||
|
'path' => '/anime-collection/view{/view}',
|
||||||
|
'action' => 'view',
|
||||||
|
'tokens' => [
|
||||||
|
'view' => ALPHA_SLUG_PATTERN,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'anime.collection.delete' => [
|
||||||
|
'path' => '/anime-collection/delete',
|
||||||
|
'action' => 'delete',
|
||||||
|
'verb' => 'post',
|
||||||
|
],
|
||||||
|
'anime.collection.redirect' => [
|
||||||
|
'path' => '/anime-collection',
|
||||||
|
],
|
||||||
|
'anime.collection.redirect2' => [
|
||||||
|
'path' => '/anime-collection/',
|
||||||
|
],
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Manga Collection Routes
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
'manga.collection.search' => [
|
||||||
|
'path' => '/manga-collection/search',
|
||||||
|
'action' => 'search',
|
||||||
|
],
|
||||||
|
'manga.collection.add.get' => [
|
||||||
|
'path' => '/manga-collection/add',
|
||||||
|
'action' => 'form',
|
||||||
|
],
|
||||||
|
'manga.collection.edit.get' => [
|
||||||
|
'path' => '/manga-collection/edit/{id}',
|
||||||
|
'action' => 'form',
|
||||||
|
'tokens' => [
|
||||||
|
'id' => NUM_PATTERN,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'manga.collection.add.post' => [
|
||||||
|
'path' => '/manga-collection/add',
|
||||||
|
'action' => 'add',
|
||||||
|
'verb' => 'post',
|
||||||
|
],
|
||||||
|
'manga.collection.edit.post' => [
|
||||||
|
'path' => '/manga-collection/edit',
|
||||||
|
'action' => 'edit',
|
||||||
|
'verb' => 'post',
|
||||||
|
],
|
||||||
|
'manga.collection.view' => [
|
||||||
|
'path' => '/manga-collection/view{/view}',
|
||||||
|
'tokens' => [
|
||||||
|
'view' => ALPHA_SLUG_PATTERN,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'manga.collection.delete' => [
|
||||||
|
'path' => '/manga-collection/delete',
|
||||||
|
'action' => 'delete',
|
||||||
|
'verb' => 'post',
|
||||||
|
],
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Other Routes
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
'character' => [
|
||||||
|
'path' => '/character/{slug}',
|
||||||
|
'tokens' => [
|
||||||
|
'slug' => SLUG_PATTERN
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'person' => [
|
||||||
|
'path' => '/people/{slug}',
|
||||||
|
'tokens' => [
|
||||||
|
'slug' => SLUG_PATTERN,
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'default_user_info' => [
|
||||||
|
'path' => '/me',
|
||||||
|
'action' => 'me',
|
||||||
|
'controller' => 'user',
|
||||||
|
],
|
||||||
|
'user_info' => [
|
||||||
|
'path' => '/user/{username}',
|
||||||
|
'controller' => 'user',
|
||||||
|
'action' => 'about',
|
||||||
|
'tokens' => [
|
||||||
|
'username' => '.*?'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Default / Shared routes
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
'anilist-redirect' => [
|
||||||
|
'path' => '/anilist-redirect',
|
||||||
|
'action' => 'anilistRedirect',
|
||||||
|
'controller' => 'settings',
|
||||||
|
],
|
||||||
|
'anilist-callback' => [
|
||||||
|
'path' => '/anilist-oauth',
|
||||||
|
'action' => 'anilistCallback',
|
||||||
|
'controller' => 'settings',
|
||||||
|
],
|
||||||
|
'image_proxy' => [
|
||||||
|
'path' => '/public/images/{type}/{file}',
|
||||||
|
'action' => 'cache',
|
||||||
|
'controller' => 'images',
|
||||||
|
'tokens' => [
|
||||||
|
'type' => SLUG_PATTERN,
|
||||||
|
'file' => '[a-z0-9\-]+\.[a-z]{3,4}'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'settings' => [
|
||||||
|
'path' => '/settings',
|
||||||
|
],
|
||||||
|
'settings-post' => [
|
||||||
|
'path' => '/settings/update',
|
||||||
|
'action' => 'update',
|
||||||
|
'verb' => 'post',
|
||||||
|
],
|
||||||
|
'login' => [
|
||||||
|
'path' => '/login',
|
||||||
|
'action' => 'login',
|
||||||
|
],
|
||||||
|
'login.post' => [
|
||||||
|
'path' => '/login',
|
||||||
|
'action' => 'loginAction',
|
||||||
|
'verb' => 'post',
|
||||||
|
],
|
||||||
|
'logout' => [
|
||||||
|
'path' => '/logout',
|
||||||
|
'action' => 'logout',
|
||||||
|
],
|
||||||
|
'increment' => [
|
||||||
|
'path' => '/{controller}/increment',
|
||||||
|
'action' => 'increment',
|
||||||
|
'verb' => 'post',
|
||||||
|
'tokens' => [
|
||||||
|
'controller' => ALPHA_SLUG_PATTERN,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'update' => [
|
||||||
|
'path' => '/{controller}/update',
|
||||||
|
'action' => 'update',
|
||||||
|
'verb' => 'post',
|
||||||
|
'tokens' => [
|
||||||
|
'controller' => ALPHA_SLUG_PATTERN,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'update.post' => [
|
||||||
|
'path' => '/{controller}/update_form',
|
||||||
|
'action' => 'formUpdate',
|
||||||
|
'verb' => 'post',
|
||||||
|
'tokens' => [
|
||||||
|
'controller' => ALPHA_SLUG_PATTERN,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'edit' => [
|
||||||
|
'path' => '/{controller}/edit/{id}/{status}',
|
||||||
|
'action' => 'edit',
|
||||||
|
'tokens' => [
|
||||||
|
'id' => SLUG_PATTERN,
|
||||||
|
'status' => '([a-zA-Z\-_]|%20)+',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'list' => [
|
||||||
|
'path' => '/{controller}/{type}{/view}',
|
||||||
|
'tokens' => [
|
||||||
|
'type' => ALPHA_SLUG_PATTERN,
|
||||||
|
'view' => ALPHA_SLUG_PATTERN,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'history' => [
|
||||||
|
'controller' => 'history',
|
||||||
|
'path' => '/history/{type}',
|
||||||
|
'tokens' => [
|
||||||
|
'type' => SLUG_PATTERN
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'index_redirect' => [
|
||||||
|
'path' => '/',
|
||||||
|
'action' => 'redirectToDefaultRoute',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$defaultMap = [
|
||||||
|
'action' => DEFAULT_CONTROLLER_METHOD,
|
||||||
|
'controller' => DEFAULT_CONTROLLER,
|
||||||
|
'params' => [],
|
||||||
|
'verb' => 'get',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($routes as &$route)
|
||||||
|
{
|
||||||
|
foreach($defaultMap as $key => $val)
|
||||||
|
{
|
||||||
|
if ( ! array_key_exists($key, $route))
|
||||||
|
{
|
||||||
|
$route[$key] = $val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $routes;
|
@ -1,81 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Base API Model
|
|
||||||
*/
|
|
||||||
namespace AnimeClient;
|
|
||||||
|
|
||||||
use \GuzzleHttp\Client;
|
|
||||||
use \GuzzleHttp\Cookie\CookieJar;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base model for api interaction
|
|
||||||
*/
|
|
||||||
class BaseApiModel extends BaseModel {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base url for making api requests
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $base_url = '';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Guzzle http client object
|
|
||||||
* @var object
|
|
||||||
*/
|
|
||||||
protected $client;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cookie jar object for api requests
|
|
||||||
* @var object
|
|
||||||
*/
|
|
||||||
protected $cookieJar;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
|
|
||||||
$this->cookieJar = new CookieJar();
|
|
||||||
$this->client = new Client([
|
|
||||||
'base_url' => $this->base_url,
|
|
||||||
'defaults' => [
|
|
||||||
'cookies' => $this->cookieJar,
|
|
||||||
'headers' => [
|
|
||||||
'User-Agent' => $_SERVER['HTTP_USER_AGENT'],
|
|
||||||
'Accept-Encoding' => 'application/json'
|
|
||||||
],
|
|
||||||
'timeout' => 5,
|
|
||||||
'connect_timeout' => 5
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt login via the api
|
|
||||||
*
|
|
||||||
* @codeCoverageIgnore
|
|
||||||
* @param string $username
|
|
||||||
* @param string $password
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function authenticate($username, $password)
|
|
||||||
{
|
|
||||||
$result = $this->client->post('https://hummingbird.me/api/v1/users/authenticate', [
|
|
||||||
'body' => [
|
|
||||||
'username' => $username,
|
|
||||||
'password' => $password
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($result->getStatusCode() === 201)
|
|
||||||
{
|
|
||||||
$_SESSION['hummingbird_anime_token'] = $result->json();
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// End of BaseApiModel.php
|
|
@ -1,254 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Base Controller
|
|
||||||
*/
|
|
||||||
namespace AnimeClient;
|
|
||||||
|
|
||||||
use Aura\Web\WebFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class for controllers, defines output methods
|
|
||||||
*/
|
|
||||||
class BaseController {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The global configuration object
|
|
||||||
* @var object $config
|
|
||||||
*/
|
|
||||||
protected $config;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request object
|
|
||||||
* @var object $request
|
|
||||||
*/
|
|
||||||
protected $request;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response object
|
|
||||||
* @var object $response
|
|
||||||
*/
|
|
||||||
protected $response;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The api model for the current controller
|
|
||||||
* @var object
|
|
||||||
*/
|
|
||||||
protected $model;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common data to be sent to views
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $base_data = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
public function __construct(Config $config, Array $web)
|
|
||||||
{
|
|
||||||
$this->config = $config;
|
|
||||||
|
|
||||||
list($request, $response) = $web;
|
|
||||||
$this->request = $request;
|
|
||||||
$this->response = $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __destruct()
|
|
||||||
{
|
|
||||||
$this->output();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the string output of a partial template
|
|
||||||
*
|
|
||||||
* @param string $template
|
|
||||||
* @param array|object $data
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function load_partial($template, $data=[])
|
|
||||||
{
|
|
||||||
if (isset($this->base_data))
|
|
||||||
{
|
|
||||||
$data = array_merge($this->base_data, $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
global $router, $defaultHandler;
|
|
||||||
$route = $router->get_route();
|
|
||||||
$data['route_path'] = ($route) ? $router->get_route()->path : "";
|
|
||||||
|
|
||||||
$defaultHandler->addDataTable('Template Data', $data);
|
|
||||||
|
|
||||||
$template_path = _dir(APP_DIR, 'views', "{$template}.php");
|
|
||||||
|
|
||||||
if ( ! is_file($template_path))
|
|
||||||
{
|
|
||||||
throw new Exception("Invalid template : {$path}");
|
|
||||||
}
|
|
||||||
|
|
||||||
ob_start();
|
|
||||||
extract($data);
|
|
||||||
include _dir(APP_DIR, 'views', 'header.php');
|
|
||||||
include $template_path;
|
|
||||||
include _dir(APP_DIR, 'views', 'footer.php');
|
|
||||||
$buffer = ob_get_contents();
|
|
||||||
ob_end_clean();
|
|
||||||
|
|
||||||
return $buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Output a template to HTML, using the provided data
|
|
||||||
*
|
|
||||||
* @param string $template
|
|
||||||
* @param array|object $data
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function outputHTML($template, $data=[])
|
|
||||||
{
|
|
||||||
$buffer = $this->load_partial($template, $data);
|
|
||||||
|
|
||||||
$this->response->content->setType('text/html');
|
|
||||||
$this->response->content->set($buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Output json with the proper content type
|
|
||||||
*
|
|
||||||
* @param mixed $data
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function outputJSON($data)
|
|
||||||
{
|
|
||||||
if ( ! is_string($data))
|
|
||||||
{
|
|
||||||
$data = json_encode($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->response->content->setType('application/json');
|
|
||||||
$this->response->content->set($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirect to the selected page
|
|
||||||
*
|
|
||||||
* @param string $url
|
|
||||||
* @param int $code
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function redirect($url, $code, $type="anime")
|
|
||||||
{
|
|
||||||
$url = full_url($url, $type);
|
|
||||||
|
|
||||||
$codes = [
|
|
||||||
301 => 'Moved Permanently',
|
|
||||||
302 => 'Found',
|
|
||||||
303 => 'See Other'
|
|
||||||
];
|
|
||||||
|
|
||||||
header("HTTP/1.1 {$code} {$codes[$code]}");
|
|
||||||
header("Location: {$url}");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a message box to the page
|
|
||||||
*
|
|
||||||
* @param string $type
|
|
||||||
* @param string $message
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function show_message($type, $message)
|
|
||||||
{
|
|
||||||
return $this->load_partial('message', [
|
|
||||||
'stat_class' => $type,
|
|
||||||
'message' => $message
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the api session
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function logout()
|
|
||||||
{
|
|
||||||
session_destroy();
|
|
||||||
$this->response->redirect->seeOther(full_url(''));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the login form
|
|
||||||
*
|
|
||||||
* @param string $status
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function login($status="")
|
|
||||||
{
|
|
||||||
$message = "";
|
|
||||||
|
|
||||||
if ($status != "")
|
|
||||||
{
|
|
||||||
$message = $this->show_message('error', $status);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->outputHTML('login', [
|
|
||||||
'title' => 'Api login',
|
|
||||||
'message' => $message
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to log in with the api
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function login_action()
|
|
||||||
{
|
|
||||||
if (
|
|
||||||
$this->model->authenticate(
|
|
||||||
$this->config->hummingbird_username,
|
|
||||||
$this->request->post->get('password')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
{
|
|
||||||
$this->response->redirect->afterPost(full_url('', $this->base_data['url_type']));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->login("Invalid username or password.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send the appropriate response
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
private function output()
|
|
||||||
{
|
|
||||||
// send status
|
|
||||||
@header($this->response->status->get(), true, $this->response->status->getCode());
|
|
||||||
|
|
||||||
// headers
|
|
||||||
foreach($this->response->headers->get() as $label => $value)
|
|
||||||
{
|
|
||||||
@header("{$label}: {$value}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// cookies
|
|
||||||
foreach($this->response->cookies->get() as $name => $cookie)
|
|
||||||
{
|
|
||||||
@setcookie(
|
|
||||||
$name,
|
|
||||||
$cookie['value'],
|
|
||||||
$cookie['expire'],
|
|
||||||
$cookie['path'],
|
|
||||||
$cookie['domain'],
|
|
||||||
$cookie['secure'],
|
|
||||||
$cookie['httponly']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// send the actual response
|
|
||||||
echo $this->response->content->get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// End of BaseController.php
|
|
@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Base DB model
|
|
||||||
*/
|
|
||||||
namespace AnimeClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base model for database interaction
|
|
||||||
*/
|
|
||||||
class BaseDBModel extends BaseModel {
|
|
||||||
/**
|
|
||||||
* The query builder object
|
|
||||||
* @var object $db
|
|
||||||
*/
|
|
||||||
protected $db;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The database connection information array
|
|
||||||
* @var array $db_config
|
|
||||||
*/
|
|
||||||
protected $db_config;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
$this->db_config = $this->config->database;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// End of BaseDBModel.php
|
|
@ -1,106 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Base for base models
|
|
||||||
*/
|
|
||||||
namespace AnimeClient;
|
|
||||||
|
|
||||||
use abeautifulsite\SimpleImage;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common base for all Models
|
|
||||||
*/
|
|
||||||
class BaseModel {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The global configuration object
|
|
||||||
* @var object $config
|
|
||||||
*/
|
|
||||||
protected $config;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
global $config;
|
|
||||||
$this->config = $config;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the path of the cached version of the image. Create the cached image
|
|
||||||
* if the file does not already exist
|
|
||||||
*
|
|
||||||
* @codeCoverageIgnore
|
|
||||||
* @param string $api_path - The original image url
|
|
||||||
* @param string $series_slug - The part of the url with the series name, becomes the image name
|
|
||||||
* @param string $type - Anime or Manga, controls cache path
|
|
||||||
* @return string - the frontend path for the cached image
|
|
||||||
*/
|
|
||||||
public function get_cached_image($api_path, $series_slug, $type="anime")
|
|
||||||
{
|
|
||||||
$api_path = str_replace("jjpg", "jpg", $api_path);
|
|
||||||
$path_parts = explode('?', basename($api_path));
|
|
||||||
$path = current($path_parts);
|
|
||||||
$ext_parts = explode('.', $path);
|
|
||||||
$ext = end($ext_parts);
|
|
||||||
|
|
||||||
// Workaround for some broken extensions
|
|
||||||
if ($ext == "jjpg") $ext = "jpg";
|
|
||||||
|
|
||||||
// Failsafe for weird urls
|
|
||||||
if (strlen($ext) > 3) return $api_path;
|
|
||||||
|
|
||||||
$cached_image = "{$series_slug}.{$ext}";
|
|
||||||
$cached_path = "{$this->config->img_cache_path}/{$type}/{$cached_image}";
|
|
||||||
|
|
||||||
// Cache the file if it doesn't already exist
|
|
||||||
if ( ! file_exists($cached_path))
|
|
||||||
{
|
|
||||||
if (ini_get('allow_url_fopen'))
|
|
||||||
{
|
|
||||||
copy($api_path, $cached_path);
|
|
||||||
}
|
|
||||||
elseif (function_exists('curl_init'))
|
|
||||||
{
|
|
||||||
$ch = curl_init($api_path);
|
|
||||||
$fp = fopen($cached_path, 'wb');
|
|
||||||
curl_setopt_array($ch, [
|
|
||||||
CURLOPT_FILE => $fp,
|
|
||||||
CURLOPT_HEADER => 0
|
|
||||||
]);
|
|
||||||
curl_exec($ch);
|
|
||||||
curl_close($ch);
|
|
||||||
fclose($ch);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new Exception("Couldn't cache images because they couldn't be downloaded.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resize the image
|
|
||||||
if ($type == 'anime')
|
|
||||||
{
|
|
||||||
$resize_width = 220;
|
|
||||||
$resize_height = 319;
|
|
||||||
$this->_resize($cached_path, $resize_width, $resize_height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "/public/images/{$type}/{$cached_image}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resize an image
|
|
||||||
*
|
|
||||||
* @codeCoverageIgnore
|
|
||||||
* @param string $path
|
|
||||||
* @param string $width
|
|
||||||
* @param string $height
|
|
||||||
*/
|
|
||||||
private function _resize($path, $width, $height)
|
|
||||||
{
|
|
||||||
$img = new SimpleImage($path);
|
|
||||||
$img->resize($width,$height)->save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// End of BaseModel.php
|
|
@ -1,56 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace AnimeClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrapper for configuration values
|
|
||||||
*/
|
|
||||||
class Config {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Config object
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $config = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*
|
|
||||||
* @param array $config_files
|
|
||||||
*/
|
|
||||||
public function __construct(Array $config_files=[])
|
|
||||||
{
|
|
||||||
// @codeCoverageIgnoreStart
|
|
||||||
if (empty($config_files))
|
|
||||||
{
|
|
||||||
require_once _dir(CONF_DIR, 'config.php'); // $config
|
|
||||||
require_once _dir(CONF_DIR, 'base_config.php'); // $base_config
|
|
||||||
}
|
|
||||||
else // @codeCoverageIgnoreEnd
|
|
||||||
{
|
|
||||||
$config = $config_files['config'];
|
|
||||||
$base_config = $config_files['base_config'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->config = array_merge($config, $base_config);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter for config values
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function __get($key)
|
|
||||||
{
|
|
||||||
if (isset($this->config[$key]))
|
|
||||||
{
|
|
||||||
return $this->config[$key];
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// End of config.php
|
|
@ -1,175 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Routing logic
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace AnimeClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Basic routing/ dispatch
|
|
||||||
*/
|
|
||||||
class Router {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The route-matching object
|
|
||||||
* @var object $router
|
|
||||||
*/
|
|
||||||
protected $router;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The global configuration object
|
|
||||||
* @var object $config
|
|
||||||
*/
|
|
||||||
protected $config;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Array containing request and response objects
|
|
||||||
* @var array $web
|
|
||||||
*/
|
|
||||||
protected $web;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*
|
|
||||||
* @param
|
|
||||||
*/
|
|
||||||
public function __construct(Config $config, \Aura\Router\Router $router, \Aura\Web\Request $request, \Aura\Web\Response $response)
|
|
||||||
{
|
|
||||||
$this->config = $config;
|
|
||||||
$this->router = $router;
|
|
||||||
$this->web = [$request, $response];
|
|
||||||
|
|
||||||
$this->_setup_routes();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the current route object, if one matches
|
|
||||||
*
|
|
||||||
* @return object
|
|
||||||
*/
|
|
||||||
public function get_route()
|
|
||||||
{
|
|
||||||
global $defaultHandler;
|
|
||||||
|
|
||||||
$raw_route = $_SERVER['REQUEST_URI'];
|
|
||||||
$route_path = str_replace([$this->config->anime_path, $this->config->manga_path], '', $raw_route);
|
|
||||||
$route_path = "/" . trim($route_path, '/');
|
|
||||||
|
|
||||||
$defaultHandler->addDataTable('Route Info', [
|
|
||||||
'route_path' => $route_path
|
|
||||||
]);
|
|
||||||
|
|
||||||
$route = $this->router->match($route_path, $_SERVER);
|
|
||||||
|
|
||||||
return $route;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle the current route
|
|
||||||
*
|
|
||||||
* @param [object] $route
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function dispatch($route = NULL)
|
|
||||||
{
|
|
||||||
global $defaultHandler;
|
|
||||||
|
|
||||||
if (is_null($route))
|
|
||||||
{
|
|
||||||
$route = $this->get_route();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! $route)
|
|
||||||
{
|
|
||||||
$failure = $this->router->getFailedRoute();
|
|
||||||
$defaultHandler->addDataTable('failed_route', (array)$failure);
|
|
||||||
|
|
||||||
$controller_name = 'BaseController';
|
|
||||||
$action_method = 'outputHTML';
|
|
||||||
$params = [
|
|
||||||
'template' => '404',
|
|
||||||
'data' => [
|
|
||||||
'title' => 'Page Not Found'
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
list($controller_name, $action_method) = $route->params['action'];
|
|
||||||
$params = (isset($route->params['params'])) ? $route->params['params'] : [];
|
|
||||||
|
|
||||||
if ( ! empty($route->tokens))
|
|
||||||
{
|
|
||||||
foreach($route->tokens as $key => $v)
|
|
||||||
{
|
|
||||||
if (array_key_exists($key, $route->params))
|
|
||||||
{
|
|
||||||
$params[$key] = $route->params[$key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$controller = new $controller_name($this->config, $this->web);
|
|
||||||
|
|
||||||
// Run the appropriate controller method
|
|
||||||
$defaultHandler->addDataTable('controller_args', $params);
|
|
||||||
call_user_func_array([$controller, $action_method], $params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Select controller based on the current url, and apply its relevent routes
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
private function _setup_routes()
|
|
||||||
{
|
|
||||||
$route_map = [
|
|
||||||
'anime' => '\\AnimeClient\\AnimeController',
|
|
||||||
'manga' => '\\AnimeClient\\MangaController',
|
|
||||||
];
|
|
||||||
$route_type = "anime";
|
|
||||||
|
|
||||||
if ($this->config->manga_host !== "" && strpos($_SERVER['HTTP_HOST'], $this->config->manga_host) !== FALSE)
|
|
||||||
{
|
|
||||||
$route_type = "manga";
|
|
||||||
}
|
|
||||||
else if ($this->config->manga_path !== "" && strpos($_SERVER['REQUEST_URI'], $this->config->manga_path) !== FALSE)
|
|
||||||
{
|
|
||||||
$route_type = "manga";
|
|
||||||
}
|
|
||||||
|
|
||||||
$routes = $this->config->routes;
|
|
||||||
|
|
||||||
// Add routes
|
|
||||||
foreach(['common', $route_type] as $key)
|
|
||||||
{
|
|
||||||
foreach($routes[$key] as $name => &$route)
|
|
||||||
{
|
|
||||||
$path = $route['path'];
|
|
||||||
unset($route['path']);
|
|
||||||
|
|
||||||
// Prepend the controller to the route parameters
|
|
||||||
array_unshift($route['action'], $route_map[$route_type]);
|
|
||||||
|
|
||||||
// Select the appropriate router method based on the http verb
|
|
||||||
$add = (array_key_exists('verb', $route)) ? "add" . ucfirst(strtolower($route['verb'])) : "addGet";
|
|
||||||
|
|
||||||
if ( ! array_key_exists('tokens', $route))
|
|
||||||
{
|
|
||||||
$this->router->$add($name, $path)->addValues($route);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$tokens = $route['tokens'];
|
|
||||||
unset($route['tokens']);
|
|
||||||
|
|
||||||
$this->router->$add($name, $path)
|
|
||||||
->addValues($route)
|
|
||||||
->addTokens($tokens);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// End of Router.php
|
|
@ -1,134 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Global functions
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the user is currently logged in
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
function is_logged_in()
|
|
||||||
{
|
|
||||||
return array_key_exists('hummingbird_anime_token', $_SESSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HTML selection helper function
|
|
||||||
*
|
|
||||||
* @param string $a - First item to compare
|
|
||||||
* @param string $b - Second item to compare
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
function is_selected($a, $b)
|
|
||||||
{
|
|
||||||
return ($a === $b) ? 'selected' : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inverse of selected helper function
|
|
||||||
*
|
|
||||||
* @param string $a - First item to compare
|
|
||||||
* @param string $b - Second item to compare
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
function is_not_selected($a, $b)
|
|
||||||
{
|
|
||||||
return ($a !== $b) ? 'selected' : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the base url for css/js/images
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
function asset_url(/*...*/)
|
|
||||||
{
|
|
||||||
global $config;
|
|
||||||
|
|
||||||
$args = func_get_args();
|
|
||||||
$base_url = rtrim($config->asset_path, '/');
|
|
||||||
|
|
||||||
array_unshift($args, $base_url);
|
|
||||||
|
|
||||||
return implode("/", $args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the base url from the config
|
|
||||||
*
|
|
||||||
* @param string $type - (optional) The controller
|
|
||||||
# @param object $config - (optional) Config
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
function base_url($type="anime", $config=NULL)
|
|
||||||
{
|
|
||||||
if (is_null($config)) global $config;
|
|
||||||
|
|
||||||
|
|
||||||
$config_path = trim($config->{"{$type}_path"}, "/");
|
|
||||||
$config_host = $config->{"{$type}_host"};
|
|
||||||
|
|
||||||
// Set the appropriate HTTP host
|
|
||||||
$host = ($config_host !== '') ? $config_host : $_SERVER['HTTP_HOST'];
|
|
||||||
$path = ($config_path !== '') ? $config_path : "";
|
|
||||||
|
|
||||||
return implode("/", ['/', $host, $path]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate full url path from the route path based on config
|
|
||||||
*
|
|
||||||
* @param string $path - (optional) The route path
|
|
||||||
* @param string $type - (optional) The controller (anime or manga), defaults to anime
|
|
||||||
# @param object $config - (optional) Config
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
function full_url($path="", $type="anime", $config=NULL)
|
|
||||||
{
|
|
||||||
if (is_null($config)) global $config;
|
|
||||||
|
|
||||||
$config_path = trim($config->{"{$type}_path"}, "/");
|
|
||||||
$config_host = $config->{"{$type}_host"};
|
|
||||||
$config_default_route = $config->{"default_{$type}_path"};
|
|
||||||
|
|
||||||
// Remove beginning/trailing slashes
|
|
||||||
$config_path = trim($config_path, '/');
|
|
||||||
$path = trim($path, '/');
|
|
||||||
|
|
||||||
// Remove any optional parameters from the route
|
|
||||||
$path = preg_replace('`{/.*?}`i', '', $path);
|
|
||||||
|
|
||||||
// Set the appropriate HTTP host
|
|
||||||
$host = ($config_host !== '') ? $config_host : $_SERVER['HTTP_HOST'];
|
|
||||||
|
|
||||||
// Set the default view
|
|
||||||
if ($path === '')
|
|
||||||
{
|
|
||||||
$path .= trim($config_default_route, '/');
|
|
||||||
if ($config->default_to_list_view) $path .= '/list';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set an leading folder
|
|
||||||
if ($config_path !== '')
|
|
||||||
{
|
|
||||||
$path = "{$config_path}/{$path}";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "//{$host}/{$path}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the last segment of the current url
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
function last_segment()
|
|
||||||
{
|
|
||||||
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
|
||||||
$segments = explode('/', $path);
|
|
||||||
return end($segments);
|
|
||||||
}
|
|
||||||
|
|
||||||
// End of functions.php
|
|
@ -1,43 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Functions that need to be included before config
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Joins paths together. Variadic to take an
|
|
||||||
* arbitrary number of arguments
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
function _dir()
|
|
||||||
{
|
|
||||||
return implode(DIRECTORY_SEPARATOR, func_get_args());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up autoloaders
|
|
||||||
*
|
|
||||||
* @codeCoverageIgnore
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
function _setup_autoloaders()
|
|
||||||
{
|
|
||||||
require _dir(ROOT_DIR, '/vendor/autoload.php');
|
|
||||||
spl_autoload_register(function ($class) {
|
|
||||||
$class_parts = explode('\\', $class);
|
|
||||||
$class = end($class_parts);
|
|
||||||
|
|
||||||
$dirs = ["base", "controllers", "models"];
|
|
||||||
|
|
||||||
foreach($dirs as $dir)
|
|
||||||
{
|
|
||||||
$file = _dir(APP_DIR, $dir, "{$class}.php");
|
|
||||||
if (file_exists($file))
|
|
||||||
{
|
|
||||||
require_once $file;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,57 +1,195 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Hummingbird Anime List Client
|
||||||
|
*
|
||||||
|
* An API client for Kitsu to manage anime and manga watch lists
|
||||||
|
*
|
||||||
|
* PHP version 7.4
|
||||||
|
*
|
||||||
|
* @package HummingbirdAnimeClient
|
||||||
|
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||||
|
* @copyright 2015 - 2020 Timothy J. Warren
|
||||||
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||||
|
* @version 5.1
|
||||||
|
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||||
|
*/
|
||||||
|
|
||||||
namespace AnimeClient;
|
namespace Aviat\AnimeClient;
|
||||||
|
|
||||||
use \Whoops\Handler\PrettyPageHandler;
|
use Aura\Html\HelperLocatorFactory;
|
||||||
use \Whoops\Handler\JsonResponseHandler;
|
use Aura\Router\RouterContainer;
|
||||||
use \Aura\Web\WebFactory;
|
use Aura\Session\SessionFactory;
|
||||||
use \Aura\Router\RouterFactory;
|
use Aviat\AnimeClient\API\{Anilist, Kitsu};
|
||||||
use \GuzzleHttp\Client;
|
use Aviat\AnimeClient\Component;
|
||||||
use \GuzzleHttp\Cookie\CookieJar;
|
use Aviat\AnimeClient\Model;
|
||||||
|
use Aviat\Banker\Teller;
|
||||||
|
use Aviat\Ion\Config;
|
||||||
|
use Aviat\Ion\Di\Container;
|
||||||
|
use Aviat\Ion\Di\ContainerInterface;
|
||||||
|
use Laminas\Diactoros\ServerRequestFactory;
|
||||||
|
use Monolog\Formatter\JsonFormatter;
|
||||||
|
use Monolog\Handler\RotatingFileHandler;
|
||||||
|
use Monolog\Logger;
|
||||||
|
use Psr\SimpleCache\CacheInterface;
|
||||||
|
|
||||||
|
if ( ! defined('APP_DIR'))
|
||||||
|
{
|
||||||
|
define('APP_DIR', __DIR__);
|
||||||
|
define('TEMPLATE_DIR', APP_DIR . '/templates');
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Setup error handling
|
// Setup DI container
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
$whoops = new \Whoops\Run();
|
return static function (array $configArray = []): Container {
|
||||||
|
$container = new Container();
|
||||||
|
|
||||||
// Set up default handler for general errors
|
// -------------------------------------------------------------------------
|
||||||
$defaultHandler = new PrettyPageHandler();
|
// Logging
|
||||||
$whoops->pushHandler($defaultHandler);
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
// Set up json handler for ajax errors
|
$appLogger = new Logger('animeclient');
|
||||||
$jsonHandler = new JsonResponseHandler();
|
$appLogger->pushHandler(new RotatingFileHandler(__DIR__ . '/logs/app.log', 2, Logger::WARNING));
|
||||||
$jsonHandler->onlyForAjaxRequests(true);
|
$container->setLogger($appLogger);
|
||||||
$whoops->pushHandler($jsonHandler);
|
|
||||||
|
|
||||||
$whoops->register();
|
foreach (['anilist-request', 'kitsu-request', 'kitsu-graphql'] as $channel)
|
||||||
|
{
|
||||||
|
$logger = new Logger($channel);
|
||||||
|
$handler = new RotatingFileHandler(__DIR__ . "/logs/{$channel}.log", 2, Logger::WARNING);
|
||||||
|
$handler->setFormatter(new JsonFormatter());
|
||||||
|
$logger->pushHandler($handler);
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
$container->setLogger($logger, $channel);
|
||||||
// Injected Objects
|
}
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Create Config Object
|
// -------------------------------------------------------------------------
|
||||||
$config = new Config();
|
// Injected Objects
|
||||||
require _dir(BASE_DIR, '/functions.php');
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
// Create Aura Router Object
|
// Create Config Object
|
||||||
$router_factory = new RouterFactory();
|
$container->set('config', fn () => new Config($configArray));
|
||||||
$aura_router = $router_factory->newInstance();
|
|
||||||
|
|
||||||
// Create Request/Response Objects
|
// Create Cache Object
|
||||||
$web_factory = new WebFactory([
|
$container->set('cache', static function(ContainerInterface $container): CacheInterface {
|
||||||
'_GET' => $_GET,
|
$logger = $container->getLogger();
|
||||||
'_POST' => $_POST,
|
$config = $container->get('config')->get('cache');
|
||||||
'_COOKIE' => $_COOKIE,
|
return new Teller($config, $logger);
|
||||||
'_SERVER' => $_SERVER,
|
});
|
||||||
'_FILES' => $_FILES
|
|
||||||
]);
|
|
||||||
$request = $web_factory->newRequest();
|
|
||||||
$response = $web_factory->newResponse();
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// Create Aura Router Object
|
||||||
// Router
|
$container->set('aura-router', fn() => new RouterContainer);
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
$router = new Router($config, $aura_router, $request, $response);
|
// Create Html helpers
|
||||||
$router->dispatch();
|
$container->set('html-helper', static function(ContainerInterface $container) {
|
||||||
|
$htmlHelper = (new HelperLocatorFactory)->newInstance();
|
||||||
|
$helpers = [
|
||||||
|
'menu' => Helper\Menu::class,
|
||||||
|
'field' => Helper\Form::class,
|
||||||
|
'picture' => Helper\Picture::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($helpers as $name => $class)
|
||||||
|
{
|
||||||
|
$htmlHelper->set($name, static function() use ($class, $container) {
|
||||||
|
$helper = new $class;
|
||||||
|
$helper->setContainer($container);
|
||||||
|
return $helper;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return $htmlHelper;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create Component helpers
|
||||||
|
$container->set('component-helper', static function (ContainerInterface $container) {
|
||||||
|
$helper = (new HelperLocatorFactory)->newInstance();
|
||||||
|
$components = [
|
||||||
|
'animeCover' => Component\AnimeCover::class,
|
||||||
|
'mangaCover' => Component\MangaCover::class,
|
||||||
|
'character' => Component\Character::class,
|
||||||
|
'media' => Component\Media::class,
|
||||||
|
'tabs' => Component\Tabs::class,
|
||||||
|
'verticalTabs' => Component\VerticalTabs::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($components as $name => $componentClass)
|
||||||
|
{
|
||||||
|
$helper->set($name, static function () use ($container, $componentClass) {
|
||||||
|
$helper = new $componentClass;
|
||||||
|
$helper->setContainer($container);
|
||||||
|
return $helper;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return $helper;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create Request Object
|
||||||
|
$container->set('request', fn () => ServerRequestFactory::fromGlobals(
|
||||||
|
$_SERVER,
|
||||||
|
$_GET,
|
||||||
|
$_POST,
|
||||||
|
$_COOKIE,
|
||||||
|
$_FILES
|
||||||
|
));
|
||||||
|
|
||||||
|
// Create session Object
|
||||||
|
$container->set('session', fn () => (new SessionFactory())->newInstance($_COOKIE));
|
||||||
|
|
||||||
|
// Miscellaneous helper methods
|
||||||
|
$container->set('util', fn ($container) => new Util($container));
|
||||||
|
|
||||||
|
// Models
|
||||||
|
$container->set('kitsu-model', static function(ContainerInterface $container): Kitsu\Model {
|
||||||
|
$requestBuilder = new Kitsu\RequestBuilder($container);
|
||||||
|
$requestBuilder->setLogger($container->getLogger('kitsu-request'));
|
||||||
|
|
||||||
|
$listItem = new Kitsu\ListItem();
|
||||||
|
$listItem->setContainer($container);
|
||||||
|
$listItem->setRequestBuilder($requestBuilder);
|
||||||
|
|
||||||
|
$model = new Kitsu\Model($listItem);
|
||||||
|
$model->setContainer($container);
|
||||||
|
$model->setRequestBuilder($requestBuilder);
|
||||||
|
|
||||||
|
$cache = $container->get('cache');
|
||||||
|
$model->setCache($cache);
|
||||||
|
return $model;
|
||||||
|
});
|
||||||
|
$container->set('anilist-model', static function(ContainerInterface $container): Anilist\Model {
|
||||||
|
$requestBuilder = new Anilist\RequestBuilder($container);
|
||||||
|
$requestBuilder->setLogger($container->getLogger('anilist-request'));
|
||||||
|
|
||||||
|
$listItem = new Anilist\ListItem();
|
||||||
|
$listItem->setContainer($container);
|
||||||
|
$listItem->setRequestBuilder($requestBuilder);
|
||||||
|
|
||||||
|
$model = new Anilist\Model($listItem);
|
||||||
|
$model->setContainer($container);
|
||||||
|
$model->setRequestBuilder($requestBuilder);
|
||||||
|
|
||||||
|
return $model;
|
||||||
|
});
|
||||||
|
$container->set('anime-model', fn ($container) => new Model\Anime($container));
|
||||||
|
$container->set('manga-model', fn ($container) => new Model\Manga($container));
|
||||||
|
$container->set('anime-collection-model', fn ($container) => new Model\AnimeCollection($container));
|
||||||
|
$container->set('manga-collection-model', fn ($container) => new Model\MangaCollection($container));
|
||||||
|
$container->set('settings-model', static function($container) {
|
||||||
|
$model = new Model\Settings($container->get('config'));
|
||||||
|
$model->setContainer($container);
|
||||||
|
return $model;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Miscellaneous Classes
|
||||||
|
$container->set('auth', fn ($container) => new Kitsu\Auth($container));
|
||||||
|
$container->set('url-generator', fn ($container) => new UrlGenerator($container));
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Dispatcher
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
$container->set('dispatcher', fn ($container) => new Dispatcher($container));
|
||||||
|
|
||||||
|
return $container;
|
||||||
|
};
|
||||||
|
|
||||||
// End of bootstrap.php
|
// End of bootstrap.php
|
@ -1,15 +0,0 @@
|
|||||||
<?php
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Lower level configuration
|
|
||||||
//
|
|
||||||
// You shouldn't generally need to change anything below this line
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
$base_config = [
|
|
||||||
// Cache paths
|
|
||||||
'data_cache_path' => _dir(APP_DIR, 'cache'),
|
|
||||||
'img_cache_path' => _dir(ROOT_DIR, 'public/images'),
|
|
||||||
|
|
||||||
// Included config files
|
|
||||||
'routes' => require _dir(CONF_DIR, 'routes.php'),
|
|
||||||
'database' => require _dir(CONF_DIR, 'database.php'),
|
|
||||||
];
|
|
22
app/config/cache.toml.example
Normal file
22
app/config/cache.toml.example
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
################################################################################
|
||||||
|
# Cache Setup #
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# See https://git.timshomepage.net/aviat/banker for more information
|
||||||
|
|
||||||
|
# Available drivers are memcached, redis or null
|
||||||
|
# Null cache driver means no caching
|
||||||
|
driver = "redis"
|
||||||
|
|
||||||
|
[connection]
|
||||||
|
# Host or socket to connect to
|
||||||
|
host = "127.0.0.1"
|
||||||
|
|
||||||
|
# Connection port
|
||||||
|
#port = 6379
|
||||||
|
|
||||||
|
# Connection password
|
||||||
|
#password = ""
|
||||||
|
|
||||||
|
# Database number
|
||||||
|
database = 2
|
@ -1,40 +0,0 @@
|
|||||||
<?php
|
|
||||||
$config = [
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Username for anime and manga lists
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
'hummingbird_username' => 'timw4mail',
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// General config
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// do you wish to show the anime collection tab?
|
|
||||||
'show_anime_collection' => TRUE,
|
|
||||||
|
|
||||||
// path to public directory
|
|
||||||
'asset_path' => '//' . $_SERVER['HTTP_HOST'] . '/public',
|
|
||||||
|
|
||||||
// path to public directory on the server
|
|
||||||
'asset_dir' => __DIR__ . '/../../public',
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Routing
|
|
||||||
//
|
|
||||||
// Route by path, or route by domain. To route by path, set the _host suffixed
|
|
||||||
// options to an empty string. To route by host, set the _path suffixed options
|
|
||||||
// to an empty string
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
'anime_host' => 'anime.timshomepage.net',
|
|
||||||
'manga_host' => 'manga.timshomepage.net',
|
|
||||||
'anime_path' => '',
|
|
||||||
'manga_path' => '',
|
|
||||||
|
|
||||||
// Default pages for anime/manga
|
|
||||||
'default_anime_path' => '/watching',
|
|
||||||
'default_manga_path' => '/all',
|
|
||||||
|
|
||||||
// Default to list view?
|
|
||||||
'default_to_list_view' => FALSE,
|
|
||||||
];
|
|
36
app/config/config.toml.example
Normal file
36
app/config/config.toml.example
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
################################################################################
|
||||||
|
# Main User Configuration #
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# Username for anime and manga lists
|
||||||
|
kitsu_username = "johnsmith"
|
||||||
|
|
||||||
|
# Whose list is it?
|
||||||
|
whose_list = "Someone"
|
||||||
|
|
||||||
|
# do you wish to show the anime collection?
|
||||||
|
show_anime_collection = true
|
||||||
|
|
||||||
|
# do you wish to show the manga collection?
|
||||||
|
show_manga_collection = false
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Default views and paths
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# Which list should be the default?
|
||||||
|
default_list = "anime" # anime or manga
|
||||||
|
|
||||||
|
# Default pages for anime/manga
|
||||||
|
default_anime_list_path = "watching" # watching|plan_to_watch|on_hold|dropped|completed|all
|
||||||
|
default_manga_list_path = "reading" # reading|plan_to_read|on_hold|dropped|completed|all
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Not on Settings Page
|
||||||
|
#
|
||||||
|
# These settings are not available to change on the settings page
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# Use HTTPs for URLs
|
||||||
|
# It is not recommended to change this setting
|
||||||
|
secure_urls = true
|
@ -1,13 +0,0 @@
|
|||||||
<?php
|
|
||||||
return [
|
|
||||||
'collection' => [
|
|
||||||
'type' => 'sqlite',
|
|
||||||
'host' => '',
|
|
||||||
'user' => '',
|
|
||||||
'pass' => '',
|
|
||||||
'port' => '',
|
|
||||||
'name' => 'default',
|
|
||||||
'database' => '',
|
|
||||||
'file' => __DIR__ . '/../../anime_collection.sqlite',
|
|
||||||
]
|
|
||||||
];
|
|
11
app/config/database.toml.example
Normal file
11
app/config/database.toml.example
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
################################################################################
|
||||||
|
# Database Configuration #
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
type = "sqlite"
|
||||||
|
host = ""
|
||||||
|
user = ""
|
||||||
|
pass = ""
|
||||||
|
port = ""
|
||||||
|
database = ""
|
||||||
|
file = "anime_collection.sqlite3"
|
@ -1,60 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Easy Min
|
|
||||||
*
|
|
||||||
* Simple minification for better website performance
|
|
||||||
*
|
|
||||||
* @author Timothy J. Warren
|
|
||||||
* @copyright Copyright (c) 2012
|
|
||||||
* @link https://github.com/aviat4ion/Easy-Min
|
|
||||||
* @license http://philsturgeon.co.uk/code/dbad-license
|
|
||||||
*/
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/* $config = */require 'config.php';
|
|
||||||
|
|
||||||
$config = (object)$config;
|
|
||||||
|
|
||||||
// Should we use myth to preprocess?
|
|
||||||
$use_myth = FALSE;
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| CSS Folder
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| The folder where css files exist, in relation to the document root
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
$css_root = $config->asset_dir. '/css/';
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Path from
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Path fragment to rewrite in css files
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
$path_from = '';
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Path to
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| The path fragment replacement for the css files
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
$path_to = '';
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| JS Folder
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| The folder where javascript files exist, in relation to the document root
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
$js_root = $config->asset_dir. '/js/';
|
|
@ -1,36 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Easy Min
|
|
||||||
*
|
|
||||||
* Simple minification for better website performance
|
|
||||||
*
|
|
||||||
* @author Timothy J. Warren
|
|
||||||
* @copyright Copyright (c) 2012
|
|
||||||
* @link https://github.com/aviat4ion/Easy-Min
|
|
||||||
* @license http://philsturgeon.co.uk/code/dbad-license
|
|
||||||
*/
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the config array for css files to concatenate and minify
|
|
||||||
*/
|
|
||||||
return [
|
|
||||||
/*-----
|
|
||||||
Css
|
|
||||||
-----*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
For each group create an array like so
|
|
||||||
|
|
||||||
'my_group' => array(
|
|
||||||
'path/to/css/file1.css',
|
|
||||||
'path/to/css/file2.css'
|
|
||||||
),
|
|
||||||
*/
|
|
||||||
'base' => [
|
|
||||||
'marx.css',
|
|
||||||
'base.css'
|
|
||||||
]
|
|
||||||
];
|
|
||||||
// End of css_groups.php
|
|
@ -1,40 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Easy Min
|
|
||||||
*
|
|
||||||
* Simple minification for better website performance
|
|
||||||
*
|
|
||||||
* @author Timothy J. Warren
|
|
||||||
* @copyright Copyright (c) 2012
|
|
||||||
* @link https://github.com/aviat4ion/Easy-Min
|
|
||||||
* @license http://philsturgeon.co.uk/code/dbad-license
|
|
||||||
*/
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the config array for javascript files to concatenate and minify
|
|
||||||
*/
|
|
||||||
return [
|
|
||||||
/*
|
|
||||||
For each group create an array like so
|
|
||||||
|
|
||||||
'my_group' => array(
|
|
||||||
'path/to/js/file1.js',
|
|
||||||
'path/to/js/file2.js'
|
|
||||||
),
|
|
||||||
*/
|
|
||||||
'table' => [
|
|
||||||
'lib/jquery.min.js',
|
|
||||||
'lib/table_sorter/jquery.tablesorter.min.js',
|
|
||||||
'sort_tables.js'
|
|
||||||
],
|
|
||||||
'edit' => [
|
|
||||||
'lib/jquery.min.js',
|
|
||||||
'show_message.js',
|
|
||||||
'anime_edit.js',
|
|
||||||
'manga_edit.js'
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
// End of js_groups.php
|
|
@ -1,188 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
return [
|
|
||||||
// Routes on all controllers
|
|
||||||
'common' => [
|
|
||||||
'update' => [
|
|
||||||
'path' => '/update',
|
|
||||||
'action' => ['update'],
|
|
||||||
'verb' => 'post'
|
|
||||||
],
|
|
||||||
'login_form' => [
|
|
||||||
'path' => '/login',
|
|
||||||
'action' => ['login'],
|
|
||||||
'verb' => 'get'
|
|
||||||
],
|
|
||||||
'login_action' => [
|
|
||||||
'path' => '/login',
|
|
||||||
'action' => ['login_action'],
|
|
||||||
'verb' => 'post'
|
|
||||||
],
|
|
||||||
'logout' => [
|
|
||||||
'path' => '/logout',
|
|
||||||
'action' => ['logout']
|
|
||||||
],
|
|
||||||
],
|
|
||||||
// Routes on anime controller
|
|
||||||
'anime' => [
|
|
||||||
'index' => [
|
|
||||||
'path' => '/',
|
|
||||||
'action' => ['redirect'],
|
|
||||||
'params' => [
|
|
||||||
'url' => '', // Determined by config
|
|
||||||
'code' => '301'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'all' => [
|
|
||||||
'path' => '/all{/view}',
|
|
||||||
'action' => ['anime_list'],
|
|
||||||
'params' => [
|
|
||||||
'type' => 'all',
|
|
||||||
'title' => WHOSE . " Anime List · All"
|
|
||||||
],
|
|
||||||
'tokens' => [
|
|
||||||
'view' => '[a-z_]+'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'watching' => [
|
|
||||||
'path' => '/watching{/view}',
|
|
||||||
'action' => ['anime_list'],
|
|
||||||
'params' => [
|
|
||||||
'type' => 'currently-watching',
|
|
||||||
'title' => WHOSE . " Anime List · Watching"
|
|
||||||
],
|
|
||||||
'tokens' => [
|
|
||||||
'view' => '[a-z_]+'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'plan_to_watch' => [
|
|
||||||
'path' => '/plan_to_watch{/view}',
|
|
||||||
'action' => ['anime_list'],
|
|
||||||
'params' => [
|
|
||||||
'type' => 'plan-to-watch',
|
|
||||||
'title' => WHOSE . " Anime List · Plan to Watch"
|
|
||||||
],
|
|
||||||
'tokens' => [
|
|
||||||
'view' => '[a-z_]+'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'on_hold' => [
|
|
||||||
'path' => '/on_hold{/view}',
|
|
||||||
'action' => ['anime_list'],
|
|
||||||
'params' => [
|
|
||||||
'type' => 'on-hold',
|
|
||||||
'title' => WHOSE . " Anime List · On Hold"
|
|
||||||
],
|
|
||||||
'tokens' => [
|
|
||||||
'view' => '[a-z_]+'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'dropped' => [
|
|
||||||
'path' => '/dropped{/view}',
|
|
||||||
'action' => ['anime_list'],
|
|
||||||
'params' => [
|
|
||||||
'type' => 'dropped',
|
|
||||||
'title' => WHOSE . " Anime List · Dropped"
|
|
||||||
],
|
|
||||||
'tokens' => [
|
|
||||||
'view' => '[a-z_]+'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'completed' => [
|
|
||||||
'path' => '/completed{/view}',
|
|
||||||
'action' => ['anime_list'],
|
|
||||||
'params' => [
|
|
||||||
'type' => 'completed',
|
|
||||||
'title' => WHOSE . " Anime List · Completed"
|
|
||||||
],
|
|
||||||
'tokens' => [
|
|
||||||
'view' => '[a-z_]+'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'collection' => [
|
|
||||||
'path' => '/collection{/view}',
|
|
||||||
'action' => ['collection'],
|
|
||||||
'params' => [],
|
|
||||||
'tokens' => [
|
|
||||||
'view' => '[a-z_]+'
|
|
||||||
]
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'manga' => [
|
|
||||||
'index' => [
|
|
||||||
'path' => '/',
|
|
||||||
'action' => ['redirect'],
|
|
||||||
'params' => [
|
|
||||||
'url' => '', // Determined by config
|
|
||||||
'code' => '301',
|
|
||||||
'type' => 'manga'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'all' => [
|
|
||||||
'path' => '/all{/view}',
|
|
||||||
'action' => ['manga_list'],
|
|
||||||
'params' => [
|
|
||||||
'type' => 'all',
|
|
||||||
'title' => WHOSE . " Manga List · All"
|
|
||||||
],
|
|
||||||
'tokens' => [
|
|
||||||
'view' => '[a-z_]+'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'reading' => [
|
|
||||||
'path' => '/reading{/view}',
|
|
||||||
'action' => ['manga_list'],
|
|
||||||
'params' => [
|
|
||||||
'type' => 'Reading',
|
|
||||||
'title' => WHOSE . " Manga List · Reading"
|
|
||||||
],
|
|
||||||
'tokens' => [
|
|
||||||
'view' => '[a-z_]+'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'plan_to_read' => [
|
|
||||||
'path' => '/plan_to_read{/view}',
|
|
||||||
'action' => ['manga_list'],
|
|
||||||
'params' => [
|
|
||||||
'type' => 'Plan to Read',
|
|
||||||
'title' => WHOSE . " Manga List · Plan to Read"
|
|
||||||
],
|
|
||||||
'tokens' => [
|
|
||||||
'view' => '[a-z_]+'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'on_hold' => [
|
|
||||||
'path' => '/on_hold{/view}',
|
|
||||||
'action' => ['manga_list'],
|
|
||||||
'params' => [
|
|
||||||
'type' => 'On Hold',
|
|
||||||
'title' => WHOSE . " Manga List · On Hold"
|
|
||||||
],
|
|
||||||
'tokens' => [
|
|
||||||
'view' => '[a-z_]+'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'dropped' => [
|
|
||||||
'path' => '/dropped{/view}',
|
|
||||||
'action' => ['manga_list'],
|
|
||||||
'params' => [
|
|
||||||
'type' => 'Dropped',
|
|
||||||
'title' => WHOSE . " Manga List · Dropped"
|
|
||||||
],
|
|
||||||
'tokens' => [
|
|
||||||
'view' => '[a-z_]+'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'completed' => [
|
|
||||||
'path' => '/completed{/view}',
|
|
||||||
'action' => ['manga_list'],
|
|
||||||
'params' => [
|
|
||||||
'type' => 'Completed',
|
|
||||||
'title' => WHOSE . " Manga List · Completed"
|
|
||||||
],
|
|
||||||
'tokens' => [
|
|
||||||
'view' => '[a-z_]+'
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
];
|
|
@ -1,121 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Anime Controller
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace AnimeClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller for Anime-related pages
|
|
||||||
*/
|
|
||||||
class AnimeController extends BaseController {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The anime list model
|
|
||||||
* @var object $model
|
|
||||||
*/
|
|
||||||
protected $model;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The anime collection model
|
|
||||||
* @var object $collection_model
|
|
||||||
*/
|
|
||||||
private $collection_model;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data to ve sent to all routes in this controller
|
|
||||||
* @var array $base_data
|
|
||||||
*/
|
|
||||||
protected $base_data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Route mapping for main navigation
|
|
||||||
* @var array $nav_routes
|
|
||||||
*/
|
|
||||||
private $nav_routes = [
|
|
||||||
'Watching' => '/watching{/view}',
|
|
||||||
'Plan to Watch' => '/plan_to_watch{/view}',
|
|
||||||
'On Hold' => '/on_hold{/view}',
|
|
||||||
'Dropped' => '/dropped{/view}',
|
|
||||||
'Completed' => '/completed{/view}',
|
|
||||||
'Collection' => '/collection{/view}',
|
|
||||||
'All' => '/all{/view}'
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
public function __construct(Config $config, Array $web)
|
|
||||||
{
|
|
||||||
parent::__construct($config, $web);
|
|
||||||
|
|
||||||
if ($this->config->show_anime_collection === FALSE)
|
|
||||||
{
|
|
||||||
unset($this->nav_routes['Collection']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->model = new AnimeModel();
|
|
||||||
$this->collection_model = new AnimeCollectionModel();
|
|
||||||
$this->base_data = [
|
|
||||||
'message' => '',
|
|
||||||
'url_type' => 'anime',
|
|
||||||
'other_type' => 'manga',
|
|
||||||
'nav_routes' => $this->nav_routes,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show a portion, or all of the anime list
|
|
||||||
*
|
|
||||||
* @param string $type - The section of the list
|
|
||||||
* @param string $title - The title of the page
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function anime_list($type, $title, $view)
|
|
||||||
{
|
|
||||||
$view_map = [
|
|
||||||
'' => 'cover',
|
|
||||||
'list' => 'list'
|
|
||||||
];
|
|
||||||
|
|
||||||
$data = ($type != 'all')
|
|
||||||
? $this->model->get_list($type)
|
|
||||||
: $this->model->get_all_lists();
|
|
||||||
|
|
||||||
$this->outputHTML('anime/' . $view_map[$view], [
|
|
||||||
'title' => $title,
|
|
||||||
'sections' => $data
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the anime collection page
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function collection($view)
|
|
||||||
{
|
|
||||||
$view_map = [
|
|
||||||
'' => 'collection',
|
|
||||||
'list' => 'collection_list'
|
|
||||||
];
|
|
||||||
|
|
||||||
$data = $this->collection_model->get_collection();
|
|
||||||
|
|
||||||
$this->outputHTML('anime/' . $view_map[$view], [
|
|
||||||
'title' => WHOSE . " Anime Collection",
|
|
||||||
'sections' => $data
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update an anime item
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function update()
|
|
||||||
{
|
|
||||||
print_r($this->model->update($this->request->post->get()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// End of AnimeController.php
|
|
@ -1,87 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Manga Controller
|
|
||||||
*/
|
|
||||||
namespace AnimeClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller for manga list
|
|
||||||
*/
|
|
||||||
class MangaController extends BaseController {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The manga model
|
|
||||||
* @var object $model
|
|
||||||
*/
|
|
||||||
protected $model;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data to ve sent to all routes in this controller
|
|
||||||
* @var array $base_data
|
|
||||||
*/
|
|
||||||
protected $base_data;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Route mapping for main navigation
|
|
||||||
* @var array $nav_routes
|
|
||||||
*/
|
|
||||||
private $nav_routes = [
|
|
||||||
'Reading' => '/reading{/view}',
|
|
||||||
'Plan to Read' => '/plan_to_read{/view}',
|
|
||||||
'On Hold' => '/on_hold{/view}',
|
|
||||||
'Dropped' => '/dropped{/view}',
|
|
||||||
'Completed' => '/completed{/view}',
|
|
||||||
'All' => '/all{/view}'
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
public function __construct(Config $config, Array $web)
|
|
||||||
{
|
|
||||||
parent::__construct($config, $web);
|
|
||||||
$this->model = new MangaModel();
|
|
||||||
$this->base_data = [
|
|
||||||
'url_type' => 'manga',
|
|
||||||
'other_type' => 'anime',
|
|
||||||
'nav_routes' => $this->nav_routes
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update an anime item
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function update()
|
|
||||||
{
|
|
||||||
$this->outputJSON($this->model->update($this->request->post->get()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a section of the manga list
|
|
||||||
*
|
|
||||||
* @param string $status
|
|
||||||
* @param string $title
|
|
||||||
* @param string $view
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function manga_list($status, $title, $view)
|
|
||||||
{
|
|
||||||
$view_map = [
|
|
||||||
'' => 'cover',
|
|
||||||
'list' => 'list'
|
|
||||||
];
|
|
||||||
|
|
||||||
$data = ($status !== 'all')
|
|
||||||
? [$status => $this->model->get_list($status)]
|
|
||||||
: $this->model->get_all_lists();
|
|
||||||
|
|
||||||
$this->outputHTML('manga/' . $view_map[$view], [
|
|
||||||
'title' => $title,
|
|
||||||
'sections' => $data
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// End of MangaController.php
|
|
@ -1,206 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Anime Collection DB Model
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace AnimeClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Model for getting anime collection data
|
|
||||||
*/
|
|
||||||
class AnimeCollectionModel extends BaseDBModel {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Anime API Model
|
|
||||||
* @var object $anime_model
|
|
||||||
*/
|
|
||||||
private $anime_model;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the database is valid for querying
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
private $valid_database = FALSE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
|
|
||||||
$this->db = \Query($this->db_config['collection']);
|
|
||||||
$this->anime_model = new AnimeModel();
|
|
||||||
|
|
||||||
// Is database valid? If not, set a flag so the
|
|
||||||
// app can be run without a valid database
|
|
||||||
$db_file = file_get_contents($this->db_config['collection']['file']);
|
|
||||||
$this->valid_database = (strpos($db_file, 'SQLite format 3') === 0);
|
|
||||||
|
|
||||||
|
|
||||||
// Do an import if an import file exists
|
|
||||||
$this->json_import();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get collection from the database, and organize by media type
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function get_collection()
|
|
||||||
{
|
|
||||||
$raw_collection = $this->_get_collection();
|
|
||||||
|
|
||||||
$collection = [];
|
|
||||||
|
|
||||||
foreach($raw_collection as $row)
|
|
||||||
{
|
|
||||||
if (array_key_exists($row['media'], $collection))
|
|
||||||
{
|
|
||||||
$collection[$row['media']][] = $row;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$collection[$row['media']] = [$row];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $collection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get full collection from the database
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function _get_collection()
|
|
||||||
{
|
|
||||||
if ( ! $this->valid_database) return [];
|
|
||||||
|
|
||||||
$query = $this->db->select('hummingbird_id, slug, title, alternate_title, show_type, age_rating, episode_count, episode_length, cover_image, notes, media.type as media')
|
|
||||||
->from('anime_set a')
|
|
||||||
->join('media', 'media.id=a.media_id', 'inner')
|
|
||||||
->order_by('media')
|
|
||||||
->order_by('title')
|
|
||||||
->get();
|
|
||||||
|
|
||||||
return $query->fetchAll(\PDO::FETCH_ASSOC);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Import anime into collection from a json file
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
private function json_import()
|
|
||||||
{
|
|
||||||
if ( ! file_exists('import.json')) return;
|
|
||||||
if ( ! $this->valid_database) return;
|
|
||||||
|
|
||||||
$anime = json_decode(file_get_contents("import.json"));
|
|
||||||
|
|
||||||
foreach($anime as $item)
|
|
||||||
{
|
|
||||||
$this->db->set([
|
|
||||||
'hummingbird_id' => $item->id,
|
|
||||||
'slug' => $item->slug,
|
|
||||||
'title' => $item->title,
|
|
||||||
'alternate_title' => $item->alternate_title,
|
|
||||||
'show_type' => $item->show_type,
|
|
||||||
'age_rating' => $item->age_rating,
|
|
||||||
'cover_image' => $this->get_cached_image($item->cover_image, $item->slug, 'anime'),
|
|
||||||
'episode_count' => $item->episode_count,
|
|
||||||
'episode_length' => $item->episode_length
|
|
||||||
])->insert('anime_set');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the import file
|
|
||||||
unlink('import.json');
|
|
||||||
|
|
||||||
// Update genre info
|
|
||||||
$this->update_genres();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update genre information
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
private function update_genres()
|
|
||||||
{
|
|
||||||
$genres = [];
|
|
||||||
$flipped_genres = [];
|
|
||||||
|
|
||||||
$links = [];
|
|
||||||
|
|
||||||
// Get existing genres
|
|
||||||
$query = $this->db->select('id, genre')
|
|
||||||
->from('genres')
|
|
||||||
->get();
|
|
||||||
foreach($query->fetchAll(PDO::FETCH_ASSOC) as $genre)
|
|
||||||
{
|
|
||||||
$genres[$genre['id']] = $genre['genre'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get existing link table entries
|
|
||||||
$query = $this->db->select('hummingbird_id, genre_id')
|
|
||||||
->from('genre_anime_set_link')
|
|
||||||
->get();
|
|
||||||
foreach($query->fetchAll(PDO::FETCH_ASSOC) as $link)
|
|
||||||
{
|
|
||||||
if (array_key_exists($link['hummingbird_id'], $links))
|
|
||||||
{
|
|
||||||
$links[$link['hummingbird_id']][] = $link['genre_id'];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$links[$link['hummingbird_id']] = [$link['genre_id']];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the anime collection
|
|
||||||
$collection = $this->_get_collection();
|
|
||||||
foreach($collection as $anime)
|
|
||||||
{
|
|
||||||
// Get api information
|
|
||||||
$api = $this->anime_model->get_anime($anime['hummingbird_id']);
|
|
||||||
|
|
||||||
|
|
||||||
foreach($api['genres'] as $genre)
|
|
||||||
{
|
|
||||||
// Add genres that don't currently exist
|
|
||||||
if ( ! in_array($genre['name'], $genres))
|
|
||||||
{
|
|
||||||
$this->db->set('genre', $genre['name'])
|
|
||||||
->insert('genres');
|
|
||||||
|
|
||||||
$genres[] = $genre['name'];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Update link table
|
|
||||||
|
|
||||||
// Get id of genre to put in link table
|
|
||||||
$flipped_genres = array_flip($genres);
|
|
||||||
|
|
||||||
$insert_array = [
|
|
||||||
'hummingbird_id' => $anime['hummingbird_id'],
|
|
||||||
'genre_id' => $flipped_genres[$genre['name']]
|
|
||||||
];
|
|
||||||
|
|
||||||
if (array_key_exists($anime['hummingbird_id'], $links))
|
|
||||||
{
|
|
||||||
if ( ! in_array($flipped_genres[$genre['name']], $links[$anime['hummingbird_id']]))
|
|
||||||
{
|
|
||||||
$this->db->set($insert_array)->insert('genre_anime_set_link');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$this->db->set($insert_array)->insert('genre_anime_set_link');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// End of AnimeCollectionModel.php
|
|
@ -1,245 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Anime API Model
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace AnimeClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Model for handling requests dealing with the anime list
|
|
||||||
*/
|
|
||||||
class AnimeModel extends BaseApiModel {
|
|
||||||
/**
|
|
||||||
* The base url for api requests
|
|
||||||
* @var string $base_url
|
|
||||||
*/
|
|
||||||
protected $base_url = "https://hummingbird.me/api/v1/";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the selected anime
|
|
||||||
*
|
|
||||||
* @param array $data
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function update($data)
|
|
||||||
{
|
|
||||||
$data['auth_token'] = $_SESSION['hummingbird_anime_token'];
|
|
||||||
|
|
||||||
$result = $this->client->post("libraries/{$data['id']}", [
|
|
||||||
'body' => $data
|
|
||||||
]);
|
|
||||||
|
|
||||||
return $result->json();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the full set of anime lists
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function get_all_lists()
|
|
||||||
{
|
|
||||||
$output = [
|
|
||||||
'Watching' => [],
|
|
||||||
'Plan to Watch' => [],
|
|
||||||
'On Hold' => [],
|
|
||||||
'Dropped' => [],
|
|
||||||
'Completed' => [],
|
|
||||||
];
|
|
||||||
|
|
||||||
$data = $this->_get_list();
|
|
||||||
|
|
||||||
foreach($data as $datum)
|
|
||||||
{
|
|
||||||
switch($datum['status'])
|
|
||||||
{
|
|
||||||
case "completed":
|
|
||||||
$output['Completed'][] = $datum;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "plan-to-watch":
|
|
||||||
$output['Plan to Watch'][] = $datum;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "dropped":
|
|
||||||
$output['Dropped'][] = $datum;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "on-hold":
|
|
||||||
$output['On Hold'][] = $datum;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "currently-watching":
|
|
||||||
$output['Watching'][] = $datum;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort anime by name
|
|
||||||
foreach($output as &$status_list)
|
|
||||||
{
|
|
||||||
$this->sort_by_name($status_list);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a category out of the full list
|
|
||||||
*
|
|
||||||
* @param string $status
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function get_list($status)
|
|
||||||
{
|
|
||||||
$map = [
|
|
||||||
'currently-watching' => 'Watching',
|
|
||||||
'plan-to-watch' => 'Plan to Watch',
|
|
||||||
'on-hold' => 'On Hold',
|
|
||||||
'dropped' => 'Dropped',
|
|
||||||
'completed' => 'Completed',
|
|
||||||
];
|
|
||||||
|
|
||||||
$data = $this->_get_list($status);
|
|
||||||
$this->sort_by_name($data);
|
|
||||||
|
|
||||||
$output = [];
|
|
||||||
$output[$map[$status]] = $data;
|
|
||||||
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get information about an anime from its id
|
|
||||||
*
|
|
||||||
* @param string $anime_id
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function get_anime($anime_id)
|
|
||||||
{
|
|
||||||
$config = [
|
|
||||||
'query' => [
|
|
||||||
'id' => $anime_id
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
$response = $this->client->get("anime/{$anime_id}", $config);
|
|
||||||
|
|
||||||
return $response->json();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search for anime by name
|
|
||||||
*
|
|
||||||
* @param string $name
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function search($name)
|
|
||||||
{
|
|
||||||
global $defaultHandler;
|
|
||||||
|
|
||||||
$config = [
|
|
||||||
'query' => [
|
|
||||||
'query' => $name
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
$response = $this->client->get('search/anime', $config);
|
|
||||||
$defaultHandler->addDataTable('anime_search_response', (array)$response);
|
|
||||||
|
|
||||||
if ($response->getStatusCode() != 200)
|
|
||||||
{
|
|
||||||
throw new Exception($response->getEffectiveUrl());
|
|
||||||
}
|
|
||||||
|
|
||||||
return $response->json();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Actually retreive the data from the api
|
|
||||||
*
|
|
||||||
* @param string $status - Status to filter by
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function _get_list($status="all")
|
|
||||||
{
|
|
||||||
global $defaultHandler;
|
|
||||||
|
|
||||||
$cache_file = "{$this->config->data_cache_path}/anime-{$status}.json";
|
|
||||||
|
|
||||||
$config = [
|
|
||||||
'allow_redirects' => FALSE
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($status != "all")
|
|
||||||
{
|
|
||||||
$config['query']['status'] = $status;
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = $this->client->get("users/{$this->config->hummingbird_username}/library", $config);
|
|
||||||
|
|
||||||
$defaultHandler->addDataTable('anime_list_response', (array)$response);
|
|
||||||
|
|
||||||
if ($response->getStatusCode() != 200)
|
|
||||||
{
|
|
||||||
if ( ! file_exists($cache_file))
|
|
||||||
{
|
|
||||||
throw new Exception($response->getEffectiveUrl());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$output = json_decode(file_get_contents($cache_file), TRUE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$output = $response->json();
|
|
||||||
$output_json = json_encode($output);
|
|
||||||
|
|
||||||
if (( ! file_exists($cache_file)) || file_get_contents($cache_file) !== $output_json)
|
|
||||||
{
|
|
||||||
// Attempt to create the cache folder if it doesn't exist
|
|
||||||
if ( ! is_dir($this->config->data_cache_path))
|
|
||||||
{
|
|
||||||
mkdir($this->config->data_cache_path);
|
|
||||||
}
|
|
||||||
// Cache the call in case of downtime
|
|
||||||
file_put_contents($cache_file, json_encode($output));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach($output as &$row)
|
|
||||||
{
|
|
||||||
$row['anime']['cover_image'] = $this->get_cached_image($row['anime']['cover_image'], $row['anime']['slug'], 'anime');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort the list by title
|
|
||||||
*
|
|
||||||
* @param array $array
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
private function sort_by_name(&$array)
|
|
||||||
{
|
|
||||||
$sort = array();
|
|
||||||
|
|
||||||
foreach($array as $key => $item)
|
|
||||||
{
|
|
||||||
$sort[$key] = $item['anime']['title'];
|
|
||||||
}
|
|
||||||
|
|
||||||
array_multisort($sort, SORT_ASC, $array);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// End of AnimeModel.php
|
|
@ -1,193 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* Manga API Model
|
|
||||||
*/
|
|
||||||
namespace AnimeClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Model for handling requests dealing with the manga list
|
|
||||||
*/
|
|
||||||
class MangaModel extends BaseApiModel {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The base url for api requests
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $base_url = "https://hummingbird.me/";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the selected manga
|
|
||||||
*
|
|
||||||
* @param array $data
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function update($data)
|
|
||||||
{
|
|
||||||
$id = $data['id'];
|
|
||||||
unset($data['id']);
|
|
||||||
|
|
||||||
$result = $this->client->put("manga_library_entries/{$id}", [
|
|
||||||
'cookies' => ['token' => $_SESSION['hummingbird_anime_token']],
|
|
||||||
'json' => ['manga_library_entry' => $data]
|
|
||||||
]);
|
|
||||||
|
|
||||||
return $result->json();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the full set of anime lists
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function get_all_lists()
|
|
||||||
{
|
|
||||||
$data = $this->_get_list();
|
|
||||||
|
|
||||||
foreach ($data as $key => &$val)
|
|
||||||
{
|
|
||||||
$this->sort_by_name($val);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a category out of the full list
|
|
||||||
*
|
|
||||||
* @param string $status
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function get_list($status)
|
|
||||||
{
|
|
||||||
$data = $this->_get_list($status);
|
|
||||||
|
|
||||||
$this->sort_by_name($data);
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Massage the list of manga entries into something more usable
|
|
||||||
*
|
|
||||||
* @param string $status
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function _get_list($status="all")
|
|
||||||
{
|
|
||||||
global $defaultHandler;
|
|
||||||
|
|
||||||
$cache_file = _dir($this->config->data_cache_path, 'manga.json');
|
|
||||||
|
|
||||||
$config = [
|
|
||||||
'query' => [
|
|
||||||
'user_id' => $this->config->hummingbird_username
|
|
||||||
],
|
|
||||||
'allow_redirects' => FALSE
|
|
||||||
];
|
|
||||||
|
|
||||||
$response = $this->client->get('manga_library_entries', $config);
|
|
||||||
|
|
||||||
$defaultHandler->addDataTable('response', (array)$response);
|
|
||||||
|
|
||||||
if ($response->getStatusCode() != 200)
|
|
||||||
{
|
|
||||||
if ( ! file_exists($cache_file))
|
|
||||||
{
|
|
||||||
throw new Exception($response->getEffectiveUrl());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$raw_data = json_decode(file_get_contents($cache_file), TRUE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Reorganize data to be more usable
|
|
||||||
$raw_data = $response->json();
|
|
||||||
|
|
||||||
// Attempt to create the cache dir if it doesn't exist
|
|
||||||
if ( ! is_dir($this->config->data_cache_path))
|
|
||||||
{
|
|
||||||
mkdir($this->config->data_cache_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache data in case of downtime
|
|
||||||
file_put_contents($cache_file, json_encode($raw_data));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bail out early if there isn't any manga data
|
|
||||||
if (empty($raw_data)) return [];
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'Reading' => [],
|
|
||||||
'Plan to Read' => [],
|
|
||||||
'On Hold' => [],
|
|
||||||
'Dropped' => [],
|
|
||||||
'Completed' => [],
|
|
||||||
];
|
|
||||||
$manga_data = [];
|
|
||||||
|
|
||||||
// Massage the two lists into one
|
|
||||||
foreach($raw_data['manga'] as $manga)
|
|
||||||
{
|
|
||||||
$manga_data[$manga['id']] = $manga;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter data by status
|
|
||||||
foreach($raw_data['manga_library_entries'] as &$entry)
|
|
||||||
{
|
|
||||||
$entry['manga'] = $manga_data[$entry['manga_id']];
|
|
||||||
|
|
||||||
// Cache poster images
|
|
||||||
$entry['manga']['poster_image'] = $this->get_cached_image($entry['manga']['poster_image'], $entry['manga_id'], 'manga');
|
|
||||||
|
|
||||||
switch($entry['status'])
|
|
||||||
{
|
|
||||||
case "Plan to Read":
|
|
||||||
$data['Plan to Read'][] = $entry;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "Dropped":
|
|
||||||
$data['Dropped'][] = $entry;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "On Hold":
|
|
||||||
$data['On Hold'][] = $entry;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "Currently Reading":
|
|
||||||
$data['Reading'][] = $entry;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "Completed":
|
|
||||||
default:
|
|
||||||
$data['Completed'][] = $entry;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//file_put_contents(_dir($this->config->data_cache_path, "manga-processed.json"), json_encode($data, JSON_PRETTY_PRINT));
|
|
||||||
|
|
||||||
return (array_key_exists($status, $data)) ? $data[$status] : $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort the manga entries by their title
|
|
||||||
*
|
|
||||||
* @param array $array
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
private function sort_by_name(&$array)
|
|
||||||
{
|
|
||||||
$sort = array();
|
|
||||||
|
|
||||||
foreach($array as $key => $item)
|
|
||||||
{
|
|
||||||
$sort[$key] = $item['manga']['romaji_title'];
|
|
||||||
}
|
|
||||||
|
|
||||||
array_multisort($sort, SORT_ASC, $array);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// End of MangaModel.php
|
|
99
app/templates/anime-cover.php
Normal file
99
app/templates/anime-cover.php
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<article
|
||||||
|
class="media"
|
||||||
|
data-kitsu-id="<?= $item['id'] ?>"
|
||||||
|
data-mal-id="<?= $item['mal_id'] ?>"
|
||||||
|
>
|
||||||
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
|
<button title="Increment episode count" class="plus-one" hidden>+1 Episode</button>
|
||||||
|
<?php endif ?>
|
||||||
|
<?= $helper->picture("images/anime/{$item['anime']['id']}.webp") ?>
|
||||||
|
|
||||||
|
<div class="name">
|
||||||
|
<a href="<?= $url->generate('anime.details', ['id' => $item['anime']['slug']]) ?>">
|
||||||
|
<span class="canonical"><?= $item['anime']['title'] ?></span>
|
||||||
|
<?php foreach ($item['anime']['titles'] as $title): ?>
|
||||||
|
<br/>
|
||||||
|
<small><?= $title ?></small>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="table">
|
||||||
|
<?php if ($item['private'] || $item['rewatching']): ?>
|
||||||
|
<div class="row">
|
||||||
|
<?php foreach (['private', 'rewatching'] as $attr): ?>
|
||||||
|
<?php if ($item[$attr]): ?>
|
||||||
|
<span class="item-<?= $attr ?>"><?= ucfirst($attr) ?></span>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<?php if ($item['rewatched'] > 0): ?>
|
||||||
|
<div class="row">
|
||||||
|
<?php if ($item['rewatched'] == 1): ?>
|
||||||
|
<div>Rewatched once</div>
|
||||||
|
<?php elseif ($item['rewatched'] == 2): ?>
|
||||||
|
<div>Rewatched twice</div>
|
||||||
|
<?php elseif ($item['rewatched'] == 3): ?>
|
||||||
|
<div>Rewatched thrice</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div>Rewatched <?= $item['rewatched'] ?> times</div>
|
||||||
|
<?php endif ?>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<?php if (count($item['anime']['streaming_links']) > 0): ?>
|
||||||
|
<div class="row">
|
||||||
|
<?php foreach ($item['anime']['streaming_links'] as $link): ?>
|
||||||
|
<div class="cover-streaming-link">
|
||||||
|
<?php if ($link['meta']['link']): ?>
|
||||||
|
<a href="<?= $link['link'] ?>"
|
||||||
|
title="Stream '<?= $item['anime']['title'] ?>' on <?= $link['meta']['name'] ?>">
|
||||||
|
<?= $helper->img("/public/images/{$link['meta']['image']}", [
|
||||||
|
'class' => 'streaming-logo',
|
||||||
|
'width' => 20,
|
||||||
|
'height' => 20,
|
||||||
|
'alt' => "{$link['meta']['name']} logo",
|
||||||
|
]); ?>
|
||||||
|
</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<?= $helper->img("/public/images/{$link['meta']['image']}", [
|
||||||
|
'class' => 'streaming-logo',
|
||||||
|
'width' => 20,
|
||||||
|
'height' => 20,
|
||||||
|
'alt' => "{$link['meta']['name']} logo",
|
||||||
|
]); ?>
|
||||||
|
<?php endif ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
|
<div class="row">
|
||||||
|
<span class="edit">
|
||||||
|
<a class="bracketed" title="Edit information about this anime" href="<?=
|
||||||
|
$url->generate('edit', [
|
||||||
|
'controller' => 'anime',
|
||||||
|
'id' => $item['id'],
|
||||||
|
'status' => $item['watching_status']
|
||||||
|
]);
|
||||||
|
?>">Edit</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="user-rating">Rating: <?= $item['user_rating'] ?> / 10</div>
|
||||||
|
<div class="completion">Episodes:
|
||||||
|
<span class="completed_number"><?= $item['episodes']['watched'] ?></span> /
|
||||||
|
<span class="total_number"><?= $item['episodes']['total'] ?></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="media_type"><?= $escape->html($item['anime']['show_type']) ?></div>
|
||||||
|
<div class="airing-status"><?= $escape->html($item['airing']['status']) ?></div>
|
||||||
|
<div class="age-rating"><?= $escape->html($item['anime']['age_rating']) ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
6
app/templates/character.php
Normal file
6
app/templates/character.php
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<article class="<?= $className ?>">
|
||||||
|
<div class="name">
|
||||||
|
<a href="<?= $link ?>"><?= $name ?></a>
|
||||||
|
</div>
|
||||||
|
<a href="<?= $link ?>"><?= $picture ?></a>
|
||||||
|
</article>
|
74
app/templates/manga-cover.php
Normal file
74
app/templates/manga-cover.php
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<article class="media" data-kitsu-id="<?= $item['id'] ?>" data-mal-id="<?= $item['mal_id'] ?>">
|
||||||
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
|
<div class="edit-buttons" hidden>
|
||||||
|
<button class="plus-one-chapter">+1 Chapter</button>
|
||||||
|
<?php /* <button class="plus-one-volume">+1 Volume</button> */ ?>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
|
<?= $helper->picture("images/manga/{$item['manga']['id']}.webp") ?>
|
||||||
|
<div class="name">
|
||||||
|
<a href="<?= $url->generate('manga.details', ['id' => $item['manga']['slug']]) ?>">
|
||||||
|
<?= $escape->html($item['manga']['title']) ?>
|
||||||
|
<?php foreach($item['manga']['titles'] as $title): ?>
|
||||||
|
<br /><small><?= $title ?></small>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="table">
|
||||||
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
|
<div class="row">
|
||||||
|
<span class="edit">
|
||||||
|
<a class="bracketed"
|
||||||
|
title="Edit information about this manga"
|
||||||
|
href="<?= $url->generate('edit', [
|
||||||
|
'controller' => 'manga',
|
||||||
|
'id' => $item['id'],
|
||||||
|
'status' => $name
|
||||||
|
]) ?>">
|
||||||
|
Edit
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
|
<div class="row">
|
||||||
|
<div><?= $item['manga']['type'] ?></div>
|
||||||
|
<div class="user-rating">Rating: <?= $item['user_rating'] ?> / 10</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($item['rereading']): ?>
|
||||||
|
<div class="row">
|
||||||
|
<?php foreach(['rereading'] as $attr): ?>
|
||||||
|
<?php if($item[$attr]): ?>
|
||||||
|
<span class="item-<?= $attr ?>"><?= ucfirst($attr) ?></span>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<?php if ($item['reread'] > 0): ?>
|
||||||
|
<div class="row">
|
||||||
|
<?php if ($item['reread'] == 1): ?>
|
||||||
|
<div>Reread once</div>
|
||||||
|
<?php elseif ($item['reread'] == 2): ?>
|
||||||
|
<div>Reread twice</div>
|
||||||
|
<?php elseif ($item['reread'] == 3): ?>
|
||||||
|
<div>Reread thrice</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div>Reread <?= $item['reread'] ?> times</div>
|
||||||
|
<?php endif ?>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="chapter_completion">
|
||||||
|
Chapters: <span class="chapters_read"><?= $item['chapters']['read'] ?></span> /
|
||||||
|
<span class="chapter_count"><?= $item['chapters']['total'] ?></span>
|
||||||
|
</div>
|
||||||
|
<?php /* </div>
|
||||||
|
<div class="row"> */ ?>
|
||||||
|
<div class="volume_completion">
|
||||||
|
Volumes: <span class="volume_count"><?= $item['volumes']['total'] ?></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
12
app/templates/media.php
Normal file
12
app/templates/media.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<article class="<?= $className ?>">
|
||||||
|
<a href="<?= $link ?>"><?= $picture ?></a>
|
||||||
|
<div class="name">
|
||||||
|
<a href="<?= $link ?>">
|
||||||
|
<?= array_shift($titles) ?>
|
||||||
|
<?php foreach ($titles as $title): ?>
|
||||||
|
<br />
|
||||||
|
<small><?= $title ?></small>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
5
app/templates/single-tab.php
Normal file
5
app/templates/single-tab.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<section class="<?= $className ?>">
|
||||||
|
<?php foreach ($data as $tabName => $tabData): ?>
|
||||||
|
<?= $callback($tabData, $tabName) ?>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</section>
|
32
app/templates/tabs.php
Normal file
32
app/templates/tabs.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<div class="tabs">
|
||||||
|
<?php $i = 0; foreach ($data as $tabName => $tabData): ?>
|
||||||
|
<?php if ( ! empty($tabData)): ?>
|
||||||
|
<?php $id = "{$name}-{$i}"; ?>
|
||||||
|
<input
|
||||||
|
role='tab'
|
||||||
|
aria-controls="_<?= $id ?>"
|
||||||
|
type="radio"
|
||||||
|
name="<?= $name ?>"
|
||||||
|
id="<?= $id ?>"
|
||||||
|
<?= ($i === 0) ? 'checked="checked"' : '' ?>
|
||||||
|
/>
|
||||||
|
<label for="<?= $id ?>"><?= ucfirst($tabName) ?></label>
|
||||||
|
|
||||||
|
<?php if ($hasSectionWrapper): ?>
|
||||||
|
<div class="content full-height">
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<section
|
||||||
|
id="_<?= $id ?>"
|
||||||
|
role="tabpanel"
|
||||||
|
class="<?= $className ?>"
|
||||||
|
>
|
||||||
|
<?= $callback($tabData, $tabName) ?>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<?php if ($hasSectionWrapper): ?>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php $i++; endforeach ?>
|
||||||
|
</div>
|
25
app/templates/vertical-tabs.php
Normal file
25
app/templates/vertical-tabs.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<div class="vertical-tabs">
|
||||||
|
<?php $i = 0; ?>
|
||||||
|
<?php foreach ($data as $tabName => $tabData): ?>
|
||||||
|
<?php $id = "{$name}-{$i}" ?>
|
||||||
|
<div class="tab">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
role='tab'
|
||||||
|
aria-controls="_<?= $id ?>"
|
||||||
|
name="<?= $name ?>"
|
||||||
|
id="<?= $id ?>"
|
||||||
|
<?= $i === 0 ? 'checked="checked"' : '' ?>
|
||||||
|
/>
|
||||||
|
<label for="<?= $id ?>"><?= $tabName ?></label>
|
||||||
|
<section
|
||||||
|
id='_<?= $id ?>'
|
||||||
|
role="tabpanel"
|
||||||
|
class="<?= $className ?>"
|
||||||
|
>
|
||||||
|
<?= $callback($tabData, $tabName) ?>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<?php $i++; ?>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</div>
|
@ -1,7 +1,6 @@
|
|||||||
<body>
|
<main>
|
||||||
<main>
|
|
||||||
<h1>404</h1>
|
<h1>404</h1>
|
||||||
<h2>Page Not Found</h2>
|
<h2><?= $message ?></h2>
|
||||||
</main>
|
<pre>(╯°□°)╯︵ ┻━┻
|
||||||
</body>
|
┬─┬ノ( º _ ºノ)</pre>
|
||||||
</html>
|
</main>
|
||||||
|
40
app/views/anime/add.php
Normal file
40
app/views/anime/add.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
|
<main>
|
||||||
|
<h2>Add Anime to your List</h2>
|
||||||
|
<form action="<?= $action_url ?>" method="post">
|
||||||
|
<?php include realpath(__DIR__ . '/../js-warning.php') ?>
|
||||||
|
<section>
|
||||||
|
<div class="cssload-loader" hidden="hidden">
|
||||||
|
<div class="cssload-inner cssload-one"></div>
|
||||||
|
<div class="cssload-inner cssload-two"></div>
|
||||||
|
<div class="cssload-inner cssload-three"></div>
|
||||||
|
</div>
|
||||||
|
<label for="search">Search for anime by name: <input type="search" id="search" /></label>
|
||||||
|
<section id="series-list" class="media-wrap">
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
<br />
|
||||||
|
<table class="invisible form">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><label for="status">Watching Status</label></td>
|
||||||
|
<td>
|
||||||
|
<select name="status" id="status">
|
||||||
|
<?php foreach($status_list as $status_key => $status_title): ?>
|
||||||
|
<option value="<?= $status_key ?>"><?= $status_title ?></option>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td> </td>
|
||||||
|
<td>
|
||||||
|
<input type="hidden" name="type" value="anime" />
|
||||||
|
<button type="submit">Save</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
<?php endif ?>
|
@ -1,28 +0,0 @@
|
|||||||
<main>
|
|
||||||
<?php foreach ($sections as $name => $items): ?>
|
|
||||||
<section class="status">
|
|
||||||
<h2><?= $name ?></h2>
|
|
||||||
<section class="media-wrap">
|
|
||||||
<?php foreach($items as $item): ?>
|
|
||||||
<a href="https://hummingbird.me/anime/<?= $item['slug'] ?>">
|
|
||||||
<article class="media" id="a-<?= $item['hummingbird_id'] ?>">
|
|
||||||
<img src="<?= $item['cover_image'] ?>" />
|
|
||||||
<div class="name">
|
|
||||||
<?= $item['title'] ?>
|
|
||||||
<?= ($item['alternate_title'] != "") ? "<br />({$item['alternate_title']})" : ""; ?>
|
|
||||||
</div>
|
|
||||||
<div class="table">
|
|
||||||
<div class="row">
|
|
||||||
<div class="completion">Episodes: <?= $item['episode_count'] ?></div>
|
|
||||||
<div class="media_type"><?= $item['show_type'] ?></div>
|
|
||||||
<div class="age_rating"><?= $item['age_rating'] ?></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</article>
|
|
||||||
</a>
|
|
||||||
<?php endforeach ?>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<?php endforeach ?>
|
|
||||||
</main>
|
|
@ -1,43 +0,0 @@
|
|||||||
<main>
|
|
||||||
<?php foreach ($sections as $name => $items): ?>
|
|
||||||
<h2><?= $name ?></h2>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Title</th>
|
|
||||||
<th>Alternate Title</th>
|
|
||||||
<th>Episode Count</th>
|
|
||||||
<th>Episode Length</th>
|
|
||||||
<th>Show Type</th>
|
|
||||||
<th>Age Rating</th>
|
|
||||||
<th>Notes</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach($items as $item): ?>
|
|
||||||
<tr>
|
|
||||||
<td class="align_left">
|
|
||||||
<a href="https://hummingbird.me/anime/<?= $item['slug'] ?>">
|
|
||||||
<?= $item['title'] ?>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td class="align_left"><?= $item['alternate_title'] ?></td>
|
|
||||||
<td><?= $item['episode_count'] ?></td>
|
|
||||||
<td><?= $item['episode_length'] ?></td>
|
|
||||||
<td><?= $item['show_type'] ?></td>
|
|
||||||
<td><?= $item['age_rating'] ?></td>
|
|
||||||
<td class="align_left"><?= $item['notes'] ?></td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<br />
|
|
||||||
<?php endforeach ?>
|
|
||||||
</main>
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
|
||||||
<script src="/public/js/table_sorter/jquery.tablesorter.min.js"></script>
|
|
||||||
<script>
|
|
||||||
$(function() {
|
|
||||||
$('table').tablesorter();
|
|
||||||
});
|
|
||||||
</script>
|
|
@ -1,40 +1,30 @@
|
|||||||
<main>
|
<main class="media-list">
|
||||||
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
|
<a class="bracketed" href="<?= $url->generate('anime.add.get') ?>">Add Item</a>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php if (empty($sections)): ?>
|
||||||
|
<h3>There's nothing here!</h3>
|
||||||
|
<?php else: ?>
|
||||||
|
<br />
|
||||||
|
<label>Filter: <input type='text' class='media-filter' /></label>
|
||||||
|
<br />
|
||||||
<?php foreach ($sections as $name => $items): ?>
|
<?php foreach ($sections as $name => $items): ?>
|
||||||
|
<?php if (empty($items)): ?>
|
||||||
<section class="status">
|
<section class="status">
|
||||||
<h2><?= $name ?></h2>
|
<h2><?= $escape->html($name) ?></h2>
|
||||||
|
<h3>There's nothing here!</h3>
|
||||||
|
</section>
|
||||||
|
<?php else: ?>
|
||||||
|
<section class="status">
|
||||||
|
<h2><?= $escape->html($name) ?></h2>
|
||||||
<section class="media-wrap">
|
<section class="media-wrap">
|
||||||
<?php foreach($items as $item): ?>
|
<?php foreach($items as $item): ?>
|
||||||
<article class="media" id="a-<?= $item['anime']['id'] ?>">
|
<?php if ($item['private'] && ! $auth->isAuthenticated()) continue; ?>
|
||||||
<?php if (is_logged_in()): ?>
|
<?= $component->animeCover($item) ?>
|
||||||
<button class="plus_one" hidden>+1 Episode</button>
|
<?php endforeach ?>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
<img src="<?= $item['anime']['cover_image'] ?>" />
|
|
||||||
<div class="name">
|
|
||||||
<a href="<?= $item['anime']['url'] ?>">
|
|
||||||
<?= $item['anime']['title'] ?>
|
|
||||||
<?= ($item['anime']['alternate_title'] != "") ? "<br />({$item['anime']['alternate_title']})" : ""; ?>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="table">
|
|
||||||
<div class="row">
|
|
||||||
<div class="user_rating">Rating: <?= ($item['rating']['value'] > 0) ? (int)($item['rating']['value'] * 2) : " - " ?> / 10</div>
|
|
||||||
<div class="completion">Episodes:
|
|
||||||
<span class="completed_number"><?= $item['episodes_watched'] ?></span> /
|
|
||||||
<span class="total_number"><?= ($item['anime']['episode_count'] != 0) ? $item['anime']['episode_count'] : "-" ?></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="media_type"><?= $item['anime']['show_type'] ?></div>
|
|
||||||
<div class="airing_status"><?= $item['anime']['status'] ?></div>
|
|
||||||
<div class="age_rating"><?= $item['anime']['age_rating'] ?></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<?php endforeach ?>
|
|
||||||
</main>
|
|
||||||
<?php if (is_logged_in()): ?>
|
|
||||||
<script src="<?= asset_url('js.php?g=edit') ?>"></script>
|
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
</main>
|
||||||
|
199
app/views/anime/details.php
Normal file
199
app/views/anime/details.php
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Aviat\AnimeClient\Kitsu;
|
||||||
|
use function Aviat\AnimeClient\getLocalImg;
|
||||||
|
|
||||||
|
?>
|
||||||
|
<main class="details fixed">
|
||||||
|
<section class="flex" unselectable>
|
||||||
|
<aside class="info">
|
||||||
|
<?= $helper->picture("images/anime/{$data['id']}-original.webp") ?>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<table class="media-details">
|
||||||
|
<tr>
|
||||||
|
<td class="align-right">Airing Status</td>
|
||||||
|
<td><?= $data['status'] ?></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Show Type</td>
|
||||||
|
<td><?= (strlen($data['show_type']) > 3) ? ucfirst(strtolower($data['show_type'])) : $data['show_type'] ?></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<?php if ($data['episode_count'] !== 1): ?>
|
||||||
|
<tr>
|
||||||
|
<td>Episode Count</td>
|
||||||
|
<td><?= $data['episode_count'] ?? '-' ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<?php if (( ! empty($data['episode_length'])) && $data['episode_count'] !== 1): ?>
|
||||||
|
<tr>
|
||||||
|
<td>Episode Length</td>
|
||||||
|
<td><?= Kitsu::friendlyTime($data['episode_length']) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<?php if (isset($data['total_length'], $data['episode_count']) && $data['total_length'] > 0): ?>
|
||||||
|
<tr>
|
||||||
|
<td>Total Length</td>
|
||||||
|
<td><?= Kitsu::friendlyTime($data['total_length']) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<?php if ( ! empty($data['age_rating'])): ?>
|
||||||
|
<tr>
|
||||||
|
<td>Age Rating</td>
|
||||||
|
<td><abbr title="<?= $data['age_rating_guide'] ?>"><?= $data['age_rating'] ?></abbr>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<?php if (count($data['links']) > 0): ?>
|
||||||
|
<tr>
|
||||||
|
<td>External Links</td>
|
||||||
|
<td>
|
||||||
|
<?php foreach ($data['links'] as $urlName => $externalUrl): ?>
|
||||||
|
<a rel='external' href="<?= $externalUrl ?>"><?= $urlName ?></a><br />
|
||||||
|
<?php endforeach ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Genres</td>
|
||||||
|
<td>
|
||||||
|
<?= implode(', ', $data['genres']) ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
</aside>
|
||||||
|
<article class="text">
|
||||||
|
<h2 class="toph"><?= $data['title'] ?></h2>
|
||||||
|
<?php foreach ($data['titles_more'] as $title): ?>
|
||||||
|
<h3><?= $title ?></h3>
|
||||||
|
<?php endforeach ?>
|
||||||
|
<br />
|
||||||
|
<div class="description">
|
||||||
|
<p><?= str_replace("\n", '</p><p>', $data['synopsis']) ?></p>
|
||||||
|
</div>
|
||||||
|
<?php if (count($data['streaming_links']) > 0): ?>
|
||||||
|
<hr />
|
||||||
|
<h4>Streaming on:</h4>
|
||||||
|
<table class="full-width invisible streaming-links">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="align-left">Service</th>
|
||||||
|
<th>Subtitles</th>
|
||||||
|
<th>Dubs</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($data['streaming_links'] as $link): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="align-left">
|
||||||
|
<?php if ($link['meta']['link'] !== FALSE): ?>
|
||||||
|
<a
|
||||||
|
href="<?= $link['link'] ?>"
|
||||||
|
title="Stream '<?= $data['title'] ?>' on <?= $link['meta']['name'] ?>"
|
||||||
|
>
|
||||||
|
<?= $helper->img("/public/images/{$link['meta']['image']}", [
|
||||||
|
'class' => 'streaming-logo',
|
||||||
|
'width' => 50,
|
||||||
|
'height' => 50,
|
||||||
|
'alt' => "{$link['meta']['name']} logo",
|
||||||
|
]) ?>
|
||||||
|
<?= $link['meta']['name'] ?>
|
||||||
|
</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<?= $helper->img("/public/images/{$link['meta']['image']}", [
|
||||||
|
'class' => 'streaming-logo',
|
||||||
|
'width' => 50,
|
||||||
|
'height' => 50,
|
||||||
|
'alt' => "{$link['meta']['name']} logo",
|
||||||
|
]) ?>
|
||||||
|
<?= $link['meta']['name'] ?>
|
||||||
|
<?php endif ?>
|
||||||
|
</td>
|
||||||
|
<td><?= implode(', ', $link['subs']) ?></td>
|
||||||
|
<td><?= implode(', ', $link['dubs']) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php if ( ! empty($data['trailer_id'])): ?>
|
||||||
|
<div class="responsive-iframe">
|
||||||
|
<h4>Trailer</h4>
|
||||||
|
<iframe
|
||||||
|
width="560"
|
||||||
|
height="315"
|
||||||
|
role='img'
|
||||||
|
src="https://www.youtube.com/embed/<?= $data['trailer_id'] ?>"
|
||||||
|
allow="autoplay; encrypted-media"
|
||||||
|
allowfullscreen
|
||||||
|
tabindex='0'
|
||||||
|
title="<?= $data['title'] ?> trailer video"
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<?php if (count($data['characters']) > 0): ?>
|
||||||
|
<section>
|
||||||
|
<h2>Characters</h2>
|
||||||
|
|
||||||
|
<?= $component->tabs('character-types', $data['characters'], static function ($characterList, $role)
|
||||||
|
use ($component, $url, $helper) {
|
||||||
|
$rendered = [];
|
||||||
|
foreach ($characterList as $id => $character):
|
||||||
|
if (empty($character['image']['original']))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$rendered[] = $component->character(
|
||||||
|
$character['name'],
|
||||||
|
$url->generate('character', ['slug' => $character['slug']]),
|
||||||
|
$helper->picture("images/characters/{$id}.webp"),
|
||||||
|
(strtolower($role) !== 'main') ? 'small-character' : 'character'
|
||||||
|
);
|
||||||
|
endforeach;
|
||||||
|
|
||||||
|
return implode('', array_map('mb_trim', $rendered));
|
||||||
|
}) ?>
|
||||||
|
</section>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<?php if (count($data['staff']) > 0): ?>
|
||||||
|
<section>
|
||||||
|
<h2>Staff</h2>
|
||||||
|
|
||||||
|
<?= $component->verticalTabs('staff-role', $data['staff'], static function ($staffList)
|
||||||
|
use ($component, $url, $helper) {
|
||||||
|
$rendered = [];
|
||||||
|
foreach ($staffList as $id => $person):
|
||||||
|
if (empty($person['image']['original']))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$rendered[] = $component->character(
|
||||||
|
$person['name'],
|
||||||
|
$url->generate('person', ['slug' => $person['slug']]),
|
||||||
|
$helper->picture(getLocalImg($person['image']['original'] ?? NULL)),
|
||||||
|
'character small-person',
|
||||||
|
);
|
||||||
|
endforeach;
|
||||||
|
|
||||||
|
return implode('', array_map('mb_trim', $rendered));
|
||||||
|
}) ?>
|
||||||
|
</section>
|
||||||
|
<?php endif ?>
|
||||||
|
</main>
|
@ -1,8 +1,112 @@
|
|||||||
<body>
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
<?php include 'nav.php' ?>
|
|
||||||
<main>
|
<main>
|
||||||
|
<h2>Edit Anime List Item</h2>
|
||||||
<form action="<?= $action ?>" method="post">
|
<form action="<?= $action ?>" method="post">
|
||||||
|
<table class="invisible form">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<h3><?= $escape->html($item['anime']['title']) ?></h3>
|
||||||
|
<?php foreach($item['anime']['titles'] as $title): ?>
|
||||||
|
<h4><?= $escape->html($title) ?></h4>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td rowspan="9">
|
||||||
|
<?= $helper->picture("images/anime/{$item['anime']['id']}-original.webp", "jpg", [], ["width" => "390"]) ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="private">Is Private?</label></td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="private" id="private"
|
||||||
|
<?php if($item['private']): ?>checked="checked"<?php endif ?>
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="watching_status">Watching Status</label></td>
|
||||||
|
<td>
|
||||||
|
<select name="watching_status" id="watching_status">
|
||||||
|
<?php foreach($statuses as $status_key => $status_title): ?>
|
||||||
|
<option <?php if(strtolower($item['watching_status']) === $status_key): ?>selected="selected"<?php endif ?>
|
||||||
|
value="<?= $status_key ?>"><?= $status_title ?></option>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="series_rating">Rating</label></td>
|
||||||
|
<td>
|
||||||
|
<input type="number" min="0" max="10" maxlength="2" name="user_rating" id="series_rating" value="<?= $item['user_rating'] ?>" id="series_rating" size="2" /> / 10
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="episodes_watched">Episodes Watched</label></td>
|
||||||
|
<td>
|
||||||
|
<input type="number" min="0" size="4" maxlength="4" value="<?= $item['episodes']['watched'] ?>" name="episodes_watched" id="episodes_watched" />
|
||||||
|
<?php if($item['episodes']['total'] > 0): ?>
|
||||||
|
/ <?= $item['episodes']['total'] ?>
|
||||||
|
<?php endif ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="rewatching_flag">Rewatching?</label></td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="rewatching" id="rewatching_flag"
|
||||||
|
<?php if($item['rewatching'] === TRUE): ?>checked="checked"<?php endif ?>
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="rewatched">Rewatch Count</label></td>
|
||||||
|
<td>
|
||||||
|
<input type="number" min="0" id="rewatched" name="rewatched" value="<?= $item['rewatched'] ?>" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="notes">Notes</label></td>
|
||||||
|
<td>
|
||||||
|
<textarea name="notes" id="notes"><?= $escape->html($item['notes']) ?></textarea>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td> </td>
|
||||||
|
<td>
|
||||||
|
<input type="hidden" value="<?= $item['id'] ?>" name="id" />
|
||||||
|
<?php if ( ! empty($item['mal_id'])): ?>
|
||||||
|
<input type="hidden" value="<?= $item['mal_id'] ?? '' ?>" name="mal_id" />
|
||||||
|
<?php endif ?>
|
||||||
|
<input type="hidden" value="true" name="edit" />
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
<form class="js-delete" action="<?= $url->generate('anime.delete') ?>" method="post">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Danger Zone</legend>
|
||||||
|
<table class="form invisible">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="danger">
|
||||||
|
<strong>Permanently</strong> remove this list item and <strong>all</strong> its data?
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="hidden" value="<?= $item['id'] ?>" name="id" />
|
||||||
|
<?php if (!empty($item['mal_id'])): ?>
|
||||||
|
<input type="hidden" value="<?= $item['mal_id'] ?? '' ?>" name="mal_id" />
|
||||||
|
<?php endif ?>
|
||||||
|
<button type="submit" class="danger">Delete Entry</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
<?php endif ?>
|
||||||
</html>
|
|
@ -1,36 +1,113 @@
|
|||||||
<main>
|
<?php use function Aviat\AnimeClient\colNotEmpty; ?>
|
||||||
|
<main class="media-list">
|
||||||
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
|
<a class="bracketed" href="<?= $url->generate('anime.add.get') ?>">Add Item</a>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php if (empty($sections)): ?>
|
||||||
|
<h3>There's nothing here!</h3>
|
||||||
|
<?php else: ?>
|
||||||
|
<br />
|
||||||
|
<label>Filter: <input type='text' class='media-filter' /></label>
|
||||||
|
<br />
|
||||||
<?php foreach ($sections as $name => $items): ?>
|
<?php foreach ($sections as $name => $items): ?>
|
||||||
<h2><?= $name ?></h2>
|
<h2><?= $name ?></h2>
|
||||||
<table>
|
<?php if (empty($items)): ?>
|
||||||
|
<h3>There's nothing here!</h3>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php
|
||||||
|
$hasNotes = colNotEmpty($items, 'notes');
|
||||||
|
?>
|
||||||
|
<table class='media-wrap'>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<?php if($auth->isAuthenticated()): ?>
|
||||||
|
<td class="no-border"> </td>
|
||||||
|
<?php endif ?>
|
||||||
<th>Title</th>
|
<th>Title</th>
|
||||||
<th>Alternate Title</th>
|
|
||||||
<th>Airing Status</th>
|
<th>Airing Status</th>
|
||||||
<th>Score</th>
|
<th>Score</th>
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
<th>Progress</th>
|
<th>Progress</th>
|
||||||
<th>Rated</th>
|
<th>Rated</th>
|
||||||
|
<th>Attributes</th>
|
||||||
|
<?php if($hasNotes): ?><th>Notes</th><?php endif ?>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php foreach($items as $item): ?>
|
<?php foreach($items as $item): ?>
|
||||||
<tr id="a-<?= $item['anime']['id'] ?>">
|
<?php if ($item['private'] && ! $auth->isAuthenticated()) continue; ?>
|
||||||
<td class="align_left">
|
<tr id="a-<?= $item['id'] ?>">
|
||||||
<a href="<?= $item['anime']['url'] ?>">
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
|
<td>
|
||||||
|
<a class="bracketed" href="<?= $url->generate('edit', [
|
||||||
|
'controller' => 'anime',
|
||||||
|
'id' => $item['id'],
|
||||||
|
'status' => $item['watching_status']
|
||||||
|
]) ?>">Edit</a>
|
||||||
|
</td>
|
||||||
|
<?php endif ?>
|
||||||
|
<td class="align-left justify">
|
||||||
|
<a href="<?= $url->generate('anime.details', ['id' => $item['anime']['slug']]) ?>">
|
||||||
<?= $item['anime']['title'] ?>
|
<?= $item['anime']['title'] ?>
|
||||||
</a>
|
</a>
|
||||||
|
<br />
|
||||||
|
<?= implode('<br />', $item['anime']['titles']) ?>
|
||||||
</td>
|
</td>
|
||||||
<td class="align_left"><?= $item['anime']['alternate_title'] ?></td>
|
<td><?= $item['airing']['status'] ?></td>
|
||||||
<td class="align_left"><?= $item['anime']['status'] ?></td>
|
<td><?= $item['user_rating'] ?> / 10 </td>
|
||||||
<td><?= (int)($item['rating']['value'] * 2) ?> / 10 </td>
|
|
||||||
<td><?= $item['anime']['show_type'] ?></td>
|
<td><?= $item['anime']['show_type'] ?></td>
|
||||||
<td>Episodes: <?= $item['episodes_watched'] ?> / <?= $item['anime']['episode_count'] ?></td>
|
<td id="<?= $item['anime']['slug'] ?>">
|
||||||
|
Episodes: <br />
|
||||||
|
<span class="completed_number"><?= $item['episodes']['watched'] ?></span> / <span class="total_number"><?= $item['episodes']['total'] ?></span>
|
||||||
|
</td>
|
||||||
<td><?= $item['anime']['age_rating'] ?></td>
|
<td><?= $item['anime']['age_rating'] ?></td>
|
||||||
|
<td>
|
||||||
|
<?php foreach($item['anime']['streaming_links'] as $link): ?>
|
||||||
|
<?php if ($link['meta']['link'] !== FALSE): ?>
|
||||||
|
<a href="<?= $link['link'] ?>" title="Stream '<?= $item['anime']['title'] ?>' on <?= $link['meta']['name'] ?>">
|
||||||
|
<?= $helper->img("/public/images/{$link['meta']['image']}", [
|
||||||
|
'class' => 'small-streaming-logo',
|
||||||
|
'width' => 25,
|
||||||
|
'height' => 25,
|
||||||
|
'alt' => "{$link['meta']['name']} logo",
|
||||||
|
]) ?>
|
||||||
|
</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<?= $helper->img("/public/images/{$link['meta']['image']}", [
|
||||||
|
'class' => 'small-streaming-logo',
|
||||||
|
'width' => 25,
|
||||||
|
'height' => 25,
|
||||||
|
'alt' => "{$link['meta']['name']} logo",
|
||||||
|
]) ?>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php endforeach ?>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<?php if ($item['rewatched'] > 0): ?>
|
||||||
|
<?php if ($item['rewatched'] == 1): ?>
|
||||||
|
<li>Rewatched once</li>
|
||||||
|
<?php elseif ($item['rewatched'] == 2): ?>
|
||||||
|
<li>Rewatched twice</li>
|
||||||
|
<?php elseif ($item['rewatched'] == 3): ?>
|
||||||
|
<li>Rewatched thrice</li>
|
||||||
|
<?php else: ?>
|
||||||
|
<li>Rewatched <?= $item['rewatched'] ?> times</li>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php foreach(['private','rewatching'] as $attr): ?>
|
||||||
|
<?php if($item[$attr]): ?><li><?= ucfirst($attr); ?></li><?php endif ?>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
<?php if ($hasNotes): ?><td><p><?= $escape->html($item['notes']) ?></p></td><?php endif ?>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<?php endif ?>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
|
<?php endif ?>
|
||||||
</main>
|
</main>
|
||||||
<script src="<?= asset_url('js.php?g=table') ?>"></script>
|
<script defer="defer" src="<?= $urlGenerator->assetUrl('js/tables.min.js') ?>"></script>
|
3
app/views/blank.php
Normal file
3
app/views/blank.php
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<main>
|
||||||
|
<h1><?= $title ?></h1>
|
||||||
|
</main>
|
162
app/views/character/details.php
Normal file
162
app/views/character/details.php
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use function Aviat\AnimeClient\getLocalImg;
|
||||||
|
use Aviat\AnimeClient\Kitsu;
|
||||||
|
|
||||||
|
?>
|
||||||
|
<main class="character-page details fixed">
|
||||||
|
<section class="flex flex-no-wrap">
|
||||||
|
<aside>
|
||||||
|
<?= $helper->picture("images/characters/{$data['id']}-original.webp") ?>
|
||||||
|
</aside>
|
||||||
|
<div>
|
||||||
|
<h2 class="toph"><?= $data['name'] ?></h2>
|
||||||
|
<?php foreach ($data['names'] as $name): ?>
|
||||||
|
<h3><?= $name ?></h3>
|
||||||
|
<?php endforeach ?>
|
||||||
|
|
||||||
|
<?php if ( ! empty($data['otherNames'])): ?>
|
||||||
|
<h4>Also Known As:</h4>
|
||||||
|
<ul>
|
||||||
|
<?php foreach ($data['otherNames'] as $name): ?>
|
||||||
|
<li><h5><?= $name ?></h5></li>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</ul>
|
||||||
|
<?php endif ?>
|
||||||
|
<br />
|
||||||
|
<hr />
|
||||||
|
<div class="description">
|
||||||
|
<p><?= str_replace("\n", '</p><p>', $data['description']) ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<?php if ( ! (empty($data['media']['anime']) || empty($data['media']['manga']))): ?>
|
||||||
|
<h3>Media</h3>
|
||||||
|
|
||||||
|
<?= $component->tabs('character-media', $data['media'], static function ($media, $mediaType) use ($url, $component, $helper) {
|
||||||
|
$rendered = [];
|
||||||
|
foreach ($media as $id => $item)
|
||||||
|
{
|
||||||
|
$rendered[] = $component->media(
|
||||||
|
array_merge([$item['title']], $item['titles']),
|
||||||
|
$url->generate("{$mediaType}.details", ['id' => $item['slug']]),
|
||||||
|
$helper->picture("images/{$mediaType}/{$item['id']}.webp")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode('', array_map('mb_trim', $rendered));
|
||||||
|
}, 'media-wrap content') ?>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<?php if (count($data['castings']) > 0): ?>
|
||||||
|
<h3>Castings</h3>
|
||||||
|
<?php
|
||||||
|
$vas = $data['castings']['Voice Actor'];
|
||||||
|
unset($data['castings']['Voice Actor']);
|
||||||
|
ksort($vas)
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php foreach ($data['castings'] as $role => $entries): ?>
|
||||||
|
<h4><?= $role ?></h4>
|
||||||
|
<?php foreach ($entries as $language => $casting): ?>
|
||||||
|
<h5><?= $language ?></h5>
|
||||||
|
<table class="min-table">
|
||||||
|
<tr>
|
||||||
|
<th>Cast Member</th>
|
||||||
|
<th>Series</th>
|
||||||
|
</tr>
|
||||||
|
<?php foreach ($casting as $cid => $c): ?>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<article class="character">
|
||||||
|
<?php
|
||||||
|
$link = $url->generate('person', ['id' => $c['person']['id']]);
|
||||||
|
?>
|
||||||
|
<a href="<?= $link ?>">
|
||||||
|
<?= $helper->picture(getLocalImg($c['person']['image'], TRUE)) ?>
|
||||||
|
<div class="name">
|
||||||
|
<?= $c['person']['name'] ?>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</article>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<section class="align-left media-wrap">
|
||||||
|
<?php foreach ($c['series'] as $series): ?>
|
||||||
|
<article class="media">
|
||||||
|
<?php
|
||||||
|
$link = $url->generate('anime.details', ['id' => $series['attributes']['slug']]);
|
||||||
|
$titles = Kitsu::filterTitles($series['attributes']);
|
||||||
|
?>
|
||||||
|
<a href="<?= $link ?>">
|
||||||
|
<?= $helper->picture(getLocalImg($series['attributes']['posterImage']['small'], TRUE)) ?>
|
||||||
|
</a>
|
||||||
|
<div class="name">
|
||||||
|
<a href="<?= $link ?>">
|
||||||
|
<?= array_shift($titles) ?>
|
||||||
|
<?php foreach ($titles as $title): ?>
|
||||||
|
<br />
|
||||||
|
<small><?= $title ?></small>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</section>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</table>
|
||||||
|
<?php endforeach ?>
|
||||||
|
<?php endforeach ?>
|
||||||
|
|
||||||
|
<?php if ( ! empty($vas)): ?>
|
||||||
|
<h4>Voice Actors</h4>
|
||||||
|
|
||||||
|
<?= $component->tabs('character-vas', $vas, static function ($casting) use ($url, $component, $helper) {
|
||||||
|
$castings = [];
|
||||||
|
foreach ($casting as $id => $c):
|
||||||
|
$person = $component->character(
|
||||||
|
$c['person']['name'],
|
||||||
|
$url->generate('person', ['slug' => $c['person']['slug']]),
|
||||||
|
$helper->picture(getLocalImg($c['person']['image']))
|
||||||
|
);
|
||||||
|
$medias = array_map(fn ($series) => $component->media(
|
||||||
|
array_merge([$series['title']], $series['titles']),
|
||||||
|
$url->generate('anime.details', ['id' => $series['slug']]),
|
||||||
|
$helper->picture(getLocalImg($series['posterImage'], TRUE))
|
||||||
|
), $c['series']);
|
||||||
|
$media = implode('', array_map('mb_trim', $medias));
|
||||||
|
|
||||||
|
$castings[] = <<<HTML
|
||||||
|
<tr>
|
||||||
|
<td>{$person}</td>
|
||||||
|
<td width="75%">
|
||||||
|
<section class="align-left media-wrap-flex">
|
||||||
|
{$media}
|
||||||
|
</section>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
HTML;
|
||||||
|
endforeach;
|
||||||
|
|
||||||
|
$languages = implode('', array_map('mb_trim', $castings));
|
||||||
|
|
||||||
|
return <<<HTML
|
||||||
|
<table class="borderless max-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Cast Member</th>
|
||||||
|
<th>Series</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>{$languages}</tbody>
|
||||||
|
</table>
|
||||||
|
HTML;
|
||||||
|
}, 'content') ?>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php endif ?>
|
||||||
|
</section>
|
||||||
|
</main>
|
39
app/views/collection/add.php
Normal file
39
app/views/collection/add.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
|
<main>
|
||||||
|
<h2>Add <?= ucfirst($collection_type) ?> to your Collection</h2>
|
||||||
|
<form action="<?= $action_url ?>" method="post">
|
||||||
|
<?php include realpath(__DIR__ . '/../js-warning.php') ?>
|
||||||
|
<section>
|
||||||
|
<div class="cssload-loader" hidden="hidden">
|
||||||
|
<div class="cssload-inner cssload-one"></div>
|
||||||
|
<div class="cssload-inner cssload-two"></div>
|
||||||
|
<div class="cssload-inner cssload-three"></div>
|
||||||
|
</div>
|
||||||
|
<label for="search">Search for <?= $collection_type ?> by name: <input type="search" id="search" name="search" /></label>
|
||||||
|
<section id="series-list" class="media-wrap">
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
<br />
|
||||||
|
<table class="invisible form">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="align-right"><label for="media_id">Media</label></td>
|
||||||
|
<td class='align-left'>
|
||||||
|
<?php include 'media-select-list.php' ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="notes">Notes</label></td>
|
||||||
|
<td><textarea id="notes" name="notes"></textarea></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td> </td>
|
||||||
|
<td>
|
||||||
|
<button type="submit">Save</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
<?php endif ?>
|
28
app/views/collection/cover-item.php
Normal file
28
app/views/collection/cover-item.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<article class="media" id="a-<?= $item['hummingbird_id'] ?>">
|
||||||
|
<?= $helper->picture("images/anime/{$item['hummingbird_id']}.webp") ?>
|
||||||
|
<div class="name">
|
||||||
|
<a href="<?= $url->generate('anime.details', ['id' => $item['slug']]) ?>">
|
||||||
|
<?= $item['title'] ?>
|
||||||
|
<?= ($item['alternate_title'] != "") ? "<small><br />{$item['alternate_title']}</small>" : ""; ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="table">
|
||||||
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
|
<div class="row">
|
||||||
|
<span class="edit">
|
||||||
|
<a class="bracketed"
|
||||||
|
href="<?= $url->generate($collection_type . '.collection.edit.get', [
|
||||||
|
'id' => $item['hummingbird_id']
|
||||||
|
]) ?>">Edit</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
|
<div class="row">
|
||||||
|
<?php if ($item['episode_count'] > 1): ?>
|
||||||
|
<div class="completion">Episodes: <?= $item['episode_count'] ?></div>
|
||||||
|
<?php endif ?>
|
||||||
|
<div class="media_type"><?= $item['show_type'] ?></div>
|
||||||
|
<div class="age-rating"><?= $item['age_rating'] ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
28
app/views/collection/cover.php
Normal file
28
app/views/collection/cover.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php use function Aviat\AnimeClient\renderTemplate; ?>
|
||||||
|
<main class="media-list">
|
||||||
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
|
<a class="bracketed" href="<?= $url->generate($collection_type . '.collection.add.get') ?>">Add Item</a>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php if (empty($sections)): ?>
|
||||||
|
<h3>There's nothing here!</h3>
|
||||||
|
<?php else: ?>
|
||||||
|
<br />
|
||||||
|
<label>Filter: <input type='text' class='media-filter' /></label>
|
||||||
|
<br />
|
||||||
|
<?= $component->tabs('collection-tab', $sections, static function ($items) use ($auth, $collection_type, $helper, $url, $component) {
|
||||||
|
$rendered = [];
|
||||||
|
foreach ($items as $item)
|
||||||
|
{
|
||||||
|
$rendered[] = renderTemplate(__DIR__ . '/cover-item.php', [
|
||||||
|
'auth' => $auth,
|
||||||
|
'collection_type' => $collection_type,
|
||||||
|
'helper' => $helper,
|
||||||
|
'item' => $item,
|
||||||
|
'url' => $url,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode('', array_map('mb_trim', $rendered));
|
||||||
|
}, 'media-wrap', true) ?>
|
||||||
|
<?php endif ?>
|
||||||
|
</main>
|
66
app/views/collection/edit.php
Normal file
66
app/views/collection/edit.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?php use function Aviat\AnimeClient\renderTemplate ?>
|
||||||
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
|
<main>
|
||||||
|
<h2>Edit Anime Collection Item</h2>
|
||||||
|
<form action="<?= $action_url ?>" method="post">
|
||||||
|
<table class="invisible form">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td rowspan="6" class="align-center">
|
||||||
|
<?= $helper->picture("images/anime/{$item['hummingbird_id']}-original.webp", "jpg", [], ["width" => "390"]) ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="align-right"><label for="title">Title</label></td>
|
||||||
|
<td class="align-left">
|
||||||
|
<input type="text" id="title" name="title" value="<?= $item['title'] ?>" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="align-right"><label for="alternate_title">Alternate Title</label></td>
|
||||||
|
<td class="align-left">
|
||||||
|
<input type="text" id="alternate_title" name="alternate_title" value="<?= $item['alternate_title'] ?>"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="align-right"><label for="media_id">Media</label></td>
|
||||||
|
<td class="align-left">
|
||||||
|
<?php include 'media-select-list.php' ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="notes">Notes</label></td>
|
||||||
|
<td><textarea id="notes" name="notes"><?= $escape->html($item['notes']) ?></textarea></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td> </td>
|
||||||
|
<td>
|
||||||
|
<?php if($action === 'Edit'): ?>
|
||||||
|
<input type="hidden" name="hummingbird_id" value="<?= $item['hummingbird_id'] ?>" />
|
||||||
|
<?php endif ?>
|
||||||
|
<button type="submit">Save</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
<form class="js-delete" action="<?= $url->generate($collection_type . '.collection.delete') ?>" method="post">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Danger Zone</legend>
|
||||||
|
<table class="form invisible">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="danger">
|
||||||
|
<strong>Permanently</strong> remove this list item and <strong>all</strong> its data?
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="hidden" value="<?= $item['hummingbird_id'] ?>" name="hummingbird_id" />
|
||||||
|
<button type="submit" class="danger">Delete Entry</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
<?php endif ?>
|
23
app/views/collection/list-item.php
Normal file
23
app/views/collection/list-item.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<tr>
|
||||||
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
|
<td>
|
||||||
|
<a class="bracketed"
|
||||||
|
href="<?= $url->generate($collection_type . '.collection.edit.get', ['id' => $item['hummingbird_id']]) ?>">Edit</a>
|
||||||
|
</td>
|
||||||
|
<?php endif ?>
|
||||||
|
<td class="align-left">
|
||||||
|
<a href="<?= $url->generate('anime.details', ['id' => $item['slug']]) ?>">
|
||||||
|
<?= $item['title'] ?>
|
||||||
|
</a>
|
||||||
|
<?= ! empty($item['alternate_title']) ? ' <br /><small> ' . $item['alternate_title'] . '</small>' : '' ?>
|
||||||
|
</td>
|
||||||
|
<?php if ($hasMedia): ?>
|
||||||
|
<td><?= implode(', ', $item['media']) ?></td>
|
||||||
|
<?php endif ?>
|
||||||
|
<td><?= ($item['episode_count'] > 1) ? $item['episode_count'] : '-' ?></td>
|
||||||
|
<td><?= $item['episode_length'] ?></td>
|
||||||
|
<td><?= $item['show_type'] ?></td>
|
||||||
|
<td><?= $item['age_rating'] ?></td>
|
||||||
|
<?php if ($hasNotes): ?><td class="align-left"><?= nl2br($item['notes'], TRUE) ?></td><?php endif ?>
|
||||||
|
<td class="align-left"><?= implode(', ', $item['genres']) ?></td>
|
||||||
|
</tr>
|
56
app/views/collection/list.php
Normal file
56
app/views/collection/list.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php use function Aviat\AnimeClient\{colNotEmpty, renderTemplate}; ?>
|
||||||
|
<main>
|
||||||
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
|
<a class="bracketed" href="<?= $url->generate($collection_type . '.collection.add.get') ?>">Add Item</a>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php if (empty($sections)): ?>
|
||||||
|
<h3>There's nothing here!</h3>
|
||||||
|
<?php else: ?>
|
||||||
|
<br />
|
||||||
|
<label>Filter: <input type='text' class='media-filter' /></label>
|
||||||
|
<br />
|
||||||
|
<?= $component->tabs('collection-tab', $sections, static function ($items, $section) use ($auth, $helper, $url, $collection_type) {
|
||||||
|
$hasNotes = colNotEmpty($items, 'notes');
|
||||||
|
$hasMedia = $section === 'All';
|
||||||
|
$firstTh = ($auth->isAuthenticated()) ? '<td> </td>' : '';
|
||||||
|
$mediaTh = ($hasMedia) ? '<th>Media</th>' : '';
|
||||||
|
$noteTh = ($hasNotes) ? '<th>Notes</th>' : '';
|
||||||
|
|
||||||
|
$rendered = [];
|
||||||
|
foreach ($items as $item)
|
||||||
|
{
|
||||||
|
$rendered[] = renderTemplate(__DIR__ . '/list-item.php', [
|
||||||
|
'auth' => $auth,
|
||||||
|
'collection_type' => $collection_type,
|
||||||
|
'hasMedia' => $hasMedia,
|
||||||
|
'hasNotes' => $hasNotes,
|
||||||
|
'helper' => $helper,
|
||||||
|
'item' => $item,
|
||||||
|
'url' => $url,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$rows = implode('', array_map('mb_trim', $rendered));
|
||||||
|
|
||||||
|
return <<<HTML
|
||||||
|
<table class="full-width media-wrap">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{$firstTh}
|
||||||
|
<th>Title</th>
|
||||||
|
{$mediaTh}
|
||||||
|
<th>Episode Count</th>
|
||||||
|
<th>Episode Length</th>
|
||||||
|
<th>Show Type</th>
|
||||||
|
<th>Age Rating</th>
|
||||||
|
{$noteTh}
|
||||||
|
<th>Genres</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>{$rows}</tbody>
|
||||||
|
</table>
|
||||||
|
HTML;
|
||||||
|
|
||||||
|
}) ?>
|
||||||
|
<?php endif ?>
|
||||||
|
</main>
|
||||||
|
<script defer="defer" src="<?= $urlGenerator->assetUrl('js/tables.min.js') ?>"></script>
|
11
app/views/collection/media-select-list.php
Normal file
11
app/views/collection/media-select-list.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<select name="media_id[]" id="media_id" multiple size="13">
|
||||||
|
<?php foreach ($media_items as $group => $items): ?>
|
||||||
|
<optgroup label='<?= $group ?>'>
|
||||||
|
<?php foreach ($items as $id => $name): ?>
|
||||||
|
<option <?= in_array($id, ($item['media_id'] ?? []), FALSE) ? 'selected="selected"' : '' ?> value="<?= $id ?>">
|
||||||
|
<?= $name ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</optgroup>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</select>
|
5
app/views/error.php
Normal file
5
app/views/error.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<main>
|
||||||
|
<h1><?= $title ?></h1>
|
||||||
|
<h2><?= $message ?></h2>
|
||||||
|
<div><?= $long_message ?></div>
|
||||||
|
</main>
|
@ -1,2 +1,22 @@
|
|||||||
|
<section id="loading-shadow" hidden="hidden">
|
||||||
|
<div class="loading-wrapper">
|
||||||
|
<div class="loading-content">
|
||||||
|
<h3>Updating List Item...</h3>
|
||||||
|
<div class="cssload-loader">
|
||||||
|
<div class="cssload-inner cssload-one"></div>
|
||||||
|
<div class="cssload-inner cssload-two"></div>
|
||||||
|
<div class="cssload-inner cssload-three"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<script nomodule="nomodule" src="https://polyfill.io/v3/polyfill.min.js?features=es5%2CObject.assign"></script>
|
||||||
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
|
<script nomodule='nomodule' async="async" defer="defer" src="<?= $urlGenerator->assetUrl('js/scripts.min.js') ?>"></script>
|
||||||
|
<script type="module" src="<?= $urlGenerator->assetUrl('es/scripts.js') ?>"></script>
|
||||||
|
<?php else: ?>
|
||||||
|
<script nomodule="nomodule" async="async" defer="defer" src="<?= $urlGenerator->assetUrl('js/anon.min.js') ?>"></script>
|
||||||
|
<script type="module" src="<?= $urlGenerator->assetUrl('es/anon.js') ?>"></script>
|
||||||
|
<?php endif ?>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -1,35 +1,42 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
<title><?= $title ?></title>
|
<title><?= $title ?></title>
|
||||||
<meta charset="utf-8" />
|
<meta http-equiv="cache-control" content="no-store" />
|
||||||
<link rel="stylesheet" href="<?= asset_url('css.php?g=base') ?>" />
|
<meta http-equiv="Content-Security-Policy" content="script-src 'self'" />
|
||||||
<script>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=1" />
|
||||||
var BASE_URL = "<?= base_url($url_type) ?>";
|
<link rel="stylesheet" href="<?= $urlGenerator->assetUrl('css/' . $config->get('theme') . '.min.css') ?>" />
|
||||||
var CONTROLLER = "<?= $url_type ?>";
|
<link rel="<?= $config->get('theme') === 'dark' ? '' : 'alternate ' ?>stylesheet" title="Dark Theme" href="<?= $urlGenerator->assetUrl('css/dark.min.css') ?>" />
|
||||||
</script>
|
<link rel="icon" href="<?= $urlGenerator->assetUrl('images/icons/favicon.ico') ?>" />
|
||||||
|
<link rel="apple-touch-icon" sizes="57x57" href="<?= $urlGenerator->assetUrl('images/icons/apple-icon-57x57.png') ?>">
|
||||||
|
<link rel="apple-touch-icon" sizes="60x60" href="<?= $urlGenerator->assetUrl('images/icons/apple-icon-60x60.png') ?>">
|
||||||
|
<link rel="apple-touch-icon" sizes="72x72" href="<?= $urlGenerator->assetUrl('images/icons/apple-icon-72x72.png') ?>">
|
||||||
|
<link rel="apple-touch-icon" sizes="76x76" href="<?= $urlGenerator->assetUrl('images/icons/apple-icon-76x76.png') ?>">
|
||||||
|
<link rel="apple-touch-icon" sizes="114x114" href="<?= $urlGenerator->assetUrl('images/icons/apple-icon-114x114.png') ?>">
|
||||||
|
<link rel="apple-touch-icon" sizes="120x120" href="<?= $urlGenerator->assetUrl('images/icons/apple-icon-120x120.png') ?>">
|
||||||
|
<link rel="apple-touch-icon" sizes="144x144" href="<?= $urlGenerator->assetUrl('images/icons/apple-icon-144x144.png') ?>">
|
||||||
|
<link rel="apple-touch-icon" sizes="152x152" href="<?= $urlGenerator->assetUrl('images/icons/apple-icon-152x152.png') ?>">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="<?= $urlGenerator->assetUrl('images/icons/apple-icon-180x180.png') ?>">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="<?= $urlGenerator->assetUrl('images/icons/android-icon-192x192.png') ?>">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="<?= $urlGenerator->assetUrl('images/icons/favicon-32x32.png') ?>">
|
||||||
|
<link rel="icon" type="image/png" sizes="96x96" href="<?= $urlGenerator->assetUrl('images/icons/favicon-96x96.png') ?>">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="<?= $urlGenerator->assetUrl('images/icons/favicon-16x16.png') ?>">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body class="<?= $url_type ?> list">
|
<body class="<?= $escape->attr($url_type) ?> list">
|
||||||
<h1 class="flex flex-align-end flex-wrap">
|
<?php include 'setup-check.php' ?>
|
||||||
<span class="flex-no-wrap grow-1"><?= WHOSE ?> <?= ucfirst($url_type) ?> <?= (strpos($route_path, 'collection') !== FALSE) ? 'Collection' : 'List' ?> [<a href="<?= full_url("", $other_type) ?>"><?= ucfirst($other_type) ?> List</a>]</span>
|
<header>
|
||||||
<span class="flex-no-wrap small-font">
|
<?php
|
||||||
<?php if (is_logged_in()): ?>
|
include 'main-menu.php';
|
||||||
[<a href="<?= full_url("/logout", $url_type) ?>">Logout</a>]
|
if(isset($message) && is_array($message))
|
||||||
<?php else: ?>
|
{
|
||||||
[<a href="<?= full_url("/login", $url_type) ?>"><?= WHOSE ?> Login</a>]
|
foreach($message as $m)
|
||||||
<?php endif ?>
|
{
|
||||||
</span>
|
$message = $m['message'];
|
||||||
</h1>
|
$message_type = $m['message_type'];
|
||||||
<nav>
|
include 'message.php';
|
||||||
<ul>
|
}
|
||||||
<?php foreach($nav_routes as $title => $nav_path): ?>
|
}
|
||||||
<li class="<?= is_selected($nav_path, $route_path) ?>"><a href="<?= full_url($nav_path, $url_type) ?>"><?= $title ?></a></li>
|
?>
|
||||||
<?php endforeach ?>
|
</header>
|
||||||
</ul>
|
|
||||||
<br />
|
|
||||||
<ul>
|
|
||||||
<li class="<?= is_not_selected('list', last_segment()) ?>"><a href="<?= full_url($route_path, $url_type) ?>">Cover View</a></li>
|
|
||||||
<li class="<?= is_selected('list', last_segment()) ?>"><a href="<?= full_url("{$route_path}/list", $url_type) ?>">List View</a></li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
<br />
|
|
49
app/views/history.php
Normal file
49
app/views/history.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<main class="details fixed">
|
||||||
|
<?php if (empty($items)): ?>
|
||||||
|
<h3>No recent history.</h3>
|
||||||
|
<?php else: ?>
|
||||||
|
<section>
|
||||||
|
<?php foreach ($items as $name => $item): ?>
|
||||||
|
<article class="flex flex-no-wrap flex-justify-start">
|
||||||
|
<section class="flex-self-center history-img">
|
||||||
|
<a href="<?= $item['url'] ?>">
|
||||||
|
<?= $helper->picture(
|
||||||
|
$item['coverImg'],
|
||||||
|
'jpg',
|
||||||
|
['width' => '110px', 'height' => '156px'],
|
||||||
|
['width' => '110px', 'height' => '156px']
|
||||||
|
) ?>
|
||||||
|
</a>
|
||||||
|
</section>
|
||||||
|
<section class="flex-self-center">
|
||||||
|
<?= $helper->a($item['url'], $item['title']) ?>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<?= $item['action'] ?>
|
||||||
|
<br />
|
||||||
|
<small>
|
||||||
|
<?php if ( ! empty($item['dateRange'])):
|
||||||
|
[$startDate, $endDate] = array_map(
|
||||||
|
fn ($date) => $date->format('l, F d'),
|
||||||
|
$item['dateRange']
|
||||||
|
);
|
||||||
|
[$startTime, $endTime] = array_map(
|
||||||
|
fn ($date) => $date->format('h:i:s A'),
|
||||||
|
$item['dateRange']
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
<?php if ($startDate === $endDate): ?>
|
||||||
|
<?= "{$startDate}, {$startTime} – {$endTime}" ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<?= "{$startDate} {$startTime} – {$endDate} {$endTime}" ?>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<?= $item['updated']->format('l, F d h:i:s A') ?>
|
||||||
|
<?php endif ?>
|
||||||
|
</small>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</section>
|
||||||
|
<?php endif ?>
|
||||||
|
</main>
|
6
app/views/js-warning.php
Normal file
6
app/views/js-warning.php
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<noscript>
|
||||||
|
<div class="message error">
|
||||||
|
<span class="icon"></span>
|
||||||
|
This feature requires Javascript to function :(
|
||||||
|
</div>
|
||||||
|
</noscript>
|
@ -1,17 +1,16 @@
|
|||||||
<main>
|
<main>
|
||||||
|
<h2><?= $config->get('whose_list'); ?>'s Login</h2>
|
||||||
<?= $message ?>
|
<?= $message ?>
|
||||||
<aside>
|
<form method="post" action="<?= $url->generate('login.post') ?>">
|
||||||
<form method="post" action="<?= full_url('/login', $url_type) ?>">
|
<table class="form invisible">
|
||||||
<dl>
|
<tr>
|
||||||
<dt><label for="username">Username: </label></dt>
|
<td><label for="password">Password: </label></td>
|
||||||
<dd><input type="text" id="username" name="username" required="required" /></dd>
|
<td><input type="password" id="password" name="password" required="required" /></td>
|
||||||
|
</tr>
|
||||||
<dt><label for="password">Password: </label></dt>
|
<tr>
|
||||||
<dd><input type="password" id="password" name="password" required="required" /></dd>
|
<td> </td>
|
||||||
|
<td><button type="submit">Login</button></td>
|
||||||
<dt> </dt>
|
</tr>
|
||||||
<dd><input type="submit" value="Login" /></dd>
|
</table>
|
||||||
</dl>
|
|
||||||
</form>
|
</form>
|
||||||
</aside>
|
|
||||||
</main>
|
</main>
|
102
app/views/main-menu.php
Normal file
102
app/views/main-menu.php
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Aviat\AnimeClient;
|
||||||
|
|
||||||
|
$whose = $config->get('whose_list') . "'s ";
|
||||||
|
$lastSegment = $urlGenerator->lastSegment();
|
||||||
|
$extraSegment = $lastSegment === 'list' ? '/list' : '';
|
||||||
|
$hasAnime = stripos($_SERVER['REQUEST_URI'], 'anime') !== FALSE;
|
||||||
|
$hasManga = stripos($_SERVER['REQUEST_URI'], 'manga') !== FALSE;
|
||||||
|
|
||||||
|
?>
|
||||||
|
<div id="main-nav" class="flex flex-align-end flex-wrap">
|
||||||
|
<span class="flex-no-wrap grow-1">
|
||||||
|
<?php if(strpos($route_path, 'collection') === FALSE): ?>
|
||||||
|
<?= $helper->a(
|
||||||
|
$urlGenerator->defaultUrl($url_type),
|
||||||
|
$whose . ucfirst($url_type) . ' List',
|
||||||
|
['aria-current'=> 'page']
|
||||||
|
) ?>
|
||||||
|
<?php if($config->get("show_{$url_type}_collection")): ?>
|
||||||
|
[<?= $helper->a(
|
||||||
|
$url->generate("{$url_type}.collection.view") . $extraSegment,
|
||||||
|
ucfirst($url_type) . ' Collection'
|
||||||
|
) ?>]
|
||||||
|
<?php endif ?>
|
||||||
|
<?php if($config->get("show_{$other_type}_collection")): ?>
|
||||||
|
[<?= $helper->a(
|
||||||
|
$url->generate("{$other_type}.collection.view") . $extraSegment,
|
||||||
|
ucfirst($other_type) . ' Collection'
|
||||||
|
) ?>]
|
||||||
|
<?php endif ?>
|
||||||
|
[<?= $helper->a(
|
||||||
|
$urlGenerator->defaultUrl($other_type) . $extraSegment,
|
||||||
|
ucfirst($other_type) . ' List'
|
||||||
|
) ?>]
|
||||||
|
<?php else: ?>
|
||||||
|
<?= $helper->a(
|
||||||
|
$url->generate("{$url_type}.collection.view") . $extraSegment,
|
||||||
|
$whose . ucfirst($url_type) . ' Collection',
|
||||||
|
['aria-current'=> 'page']
|
||||||
|
) ?>
|
||||||
|
<?php if($config->get("show_{$other_type}_collection")): ?>
|
||||||
|
[<?= $helper->a(
|
||||||
|
$url->generate("{$other_type}.collection.view") . $extraSegment,
|
||||||
|
ucfirst($other_type) . ' Collection'
|
||||||
|
) ?>]
|
||||||
|
<?php endif ?>
|
||||||
|
[<?= $helper->a($urlGenerator->defaultUrl('anime') . $extraSegment, 'Anime List') ?>]
|
||||||
|
[<?= $helper->a($urlGenerator->defaultUrl('manga') . $extraSegment, 'Manga List') ?>]
|
||||||
|
<?php endif ?>
|
||||||
|
<?php if ($auth->isAuthenticated() && $config->get(['cache', 'driver']) !== 'null'): ?>
|
||||||
|
<span class="flex-no-wrap small-font">
|
||||||
|
<button type="button" class="js-clear-cache user-btn">Clear API Cache</button>
|
||||||
|
</span>
|
||||||
|
<?php endif ?>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="flex-no-wrap small-font">[<?= $helper->a(
|
||||||
|
$url->generate('default_user_info'),
|
||||||
|
'About '. $config->get('whose_list')
|
||||||
|
) ?>]</span>
|
||||||
|
|
||||||
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
|
<span class="flex-no-wrap small-font">
|
||||||
|
<?= $helper->a(
|
||||||
|
$url->generate('settings'),
|
||||||
|
'Settings',
|
||||||
|
['class' => 'bracketed']
|
||||||
|
) ?>
|
||||||
|
</span>
|
||||||
|
<span class="flex-no-wrap small-font">
|
||||||
|
<?= $helper->a(
|
||||||
|
$url->generate('logout'),
|
||||||
|
'Logout',
|
||||||
|
['class' => 'bracketed']
|
||||||
|
) ?>
|
||||||
|
</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="flex-no-wrap small-font">
|
||||||
|
[<?= $helper->a($url->generate('login'), "{$whose} Login") ?>]
|
||||||
|
</span>
|
||||||
|
<?php endif ?>
|
||||||
|
</div>
|
||||||
|
<?php if ($container->get('util')->isViewPage() && ($hasAnime || $hasManga)): ?>
|
||||||
|
<nav>
|
||||||
|
<?= $helper->menu($menu_name) ?>
|
||||||
|
<?php if (stripos($_SERVER['REQUEST_URI'], 'history') === FALSE): ?>
|
||||||
|
<br />
|
||||||
|
<ul>
|
||||||
|
<?php $currentView = Util::eq('list', $lastSegment) ? 'list' : 'cover' ?>
|
||||||
|
<li class="<?= Util::isNotSelected('list', $lastSegment) ?>">
|
||||||
|
<a aria-current="<?= Util::ariaCurrent($currentView === 'cover') ?>"
|
||||||
|
href="<?= $urlGenerator->url($route_path) ?>">Cover View</a>
|
||||||
|
</li>
|
||||||
|
<li class="<?= Util::isSelected('list', $lastSegment) ?>">
|
||||||
|
<a aria-current="<?= Util::ariaCurrent($currentView === 'list') ?>"
|
||||||
|
href="<?= $urlGenerator->url("{$route_path}/list") ?>">List View</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<?php endif ?>
|
||||||
|
</nav>
|
||||||
|
<?php endif ?>
|
40
app/views/manga/add.php
Normal file
40
app/views/manga/add.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
|
<main>
|
||||||
|
<h2>Add Manga to your List</h2>
|
||||||
|
<form action="<?= $action_url ?>" method="post">
|
||||||
|
<?php include realpath(__DIR__ . '/../js-warning.php') ?>
|
||||||
|
<section>
|
||||||
|
<div class="cssload-loader" hidden="hidden">
|
||||||
|
<div class="cssload-inner cssload-one"></div>
|
||||||
|
<div class="cssload-inner cssload-two"></div>
|
||||||
|
<div class="cssload-inner cssload-three"></div>
|
||||||
|
</div>
|
||||||
|
<label for="search">Search for manga by name: <input type="search" id="search" /></label>
|
||||||
|
<section id="series-list" class="media-wrap">
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
<br />
|
||||||
|
<table class="invisible form">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><label for="status">Reading Status</label></td>
|
||||||
|
<td>
|
||||||
|
<select name="status" id="status">
|
||||||
|
<?php foreach($status_list as $status_key => $status_title): ?>
|
||||||
|
<option value="<?= $status_key ?>"><?= $status_title ?></option>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td> </td>
|
||||||
|
<td>
|
||||||
|
<input type="hidden" name="type" value="manga" />
|
||||||
|
<button type="submit">Save</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
<?php endif ?>
|
@ -1,49 +1,29 @@
|
|||||||
<main>
|
<main class="media-list">
|
||||||
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
|
<a class="bracketed" href="<?= $url->generate('manga.add.get') ?>">Add Item</a>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php if (empty($sections)): ?>
|
||||||
|
<h3>There's nothing here!</h3>
|
||||||
|
<?php else: ?>
|
||||||
|
<br />
|
||||||
|
<label>Filter: <input type='text' class='media-filter' /></label>
|
||||||
|
<br />
|
||||||
<?php foreach ($sections as $name => $items): ?>
|
<?php foreach ($sections as $name => $items): ?>
|
||||||
|
<?php if (empty($items)): ?>
|
||||||
<section class="status">
|
<section class="status">
|
||||||
<h2><?= $name ?></h2>
|
<h2><?= $escape->html($name) ?></h2>
|
||||||
|
<h3>There's nothing here!</h3>
|
||||||
|
</section>
|
||||||
|
<?php else: ?>
|
||||||
|
<section class="status">
|
||||||
|
<h2><?= $escape->html($name) ?></h2>
|
||||||
<section class="media-wrap">
|
<section class="media-wrap">
|
||||||
<?php foreach($items as $item): ?>
|
<?php foreach($items as $item): ?>
|
||||||
<article class="media" id="manga-<?= $item['id'] ?>">
|
<?= $component->mangaCover($item, $name) ?>
|
||||||
<?php if (is_logged_in()): ?>
|
<?php endforeach ?>
|
||||||
<div class="edit_buttons" hidden>
|
</section>
|
||||||
<button class="plus_one_chapter">+1 Chapter</button>
|
</section>
|
||||||
<button class="plus_one_volume">+1 Volume</button>
|
|
||||||
</div>
|
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
<img src="<?= $item['manga']['poster_image'] ?>" />
|
|
||||||
<div class="name">
|
|
||||||
<a href="https://hummingbird.me/manga/<?= $item['manga_id'] ?>">
|
|
||||||
<?= $item['manga']['romaji_title'] ?>
|
|
||||||
<?= (isset($item['manga']['english_title'])) ? "<br />({$item['manga']['english_title']})" : ""; ?>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="table">
|
|
||||||
<div class="row">
|
|
||||||
<div class="user_rating">Rating: <?= ($item['rating'] > 0) ? (int)($item['rating'] * 2) : '-' ?> / 10</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="chapter_completion">
|
|
||||||
Chapters: <span class="chapters_read"><?= $item['chapters_read'] ?></span> /
|
|
||||||
<span class="chapter_count"><?= ($item['manga']['chapter_count'] > 0) ? $item['manga']['chapter_count'] : "-" ?></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="volume_completion">
|
|
||||||
Volumes: <span class="volumes_read"><?= $item['volumes_read'] ?></span> /
|
|
||||||
<span class="volume_count"><?= ($item['manga']['volume_count'] > 0) ? $item['manga']['volume_count'] : "-" ?></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php /*<div class="medium_metadata">
|
|
||||||
<div class="media_type"><?= $item['manga']['manga_type'] ?></div>
|
|
||||||
</div> */ ?>
|
|
||||||
</article>
|
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<?php endforeach ?>
|
|
||||||
</main>
|
|
||||||
<?php if (is_logged_in()): ?>
|
|
||||||
<script src="<?= asset_url('js.php?g=edit') ?>"></script>
|
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
</main>
|
||||||
|
105
app/views/manga/details.php
Normal file
105
app/views/manga/details.php
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<main class="details fixed">
|
||||||
|
<section class="flex flex-no-wrap">
|
||||||
|
<aside class="info">
|
||||||
|
<?= $helper->picture("images/manga/{$data['id']}-original.webp", 'jpg', ['class' => 'cover']) ?>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<table class="media-details">
|
||||||
|
<tr>
|
||||||
|
<td class="align-right">Publishing Status</td>
|
||||||
|
<td><?= $data['status'] ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Manga Type</td>
|
||||||
|
<td><?= ucfirst(strtolower($data['manga_type'])) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php if ( ! empty($data['volume_count'])): ?>
|
||||||
|
<tr>
|
||||||
|
<td>Volume Count</td>
|
||||||
|
<td><?= $data['volume_count'] ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php if ( ! empty($data['chapter_count'])): ?>
|
||||||
|
<tr>
|
||||||
|
<td>Chapter Count</td>
|
||||||
|
<td><?= $data['chapter_count'] ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<?php if ( ! empty($data['age_rating'])): ?>
|
||||||
|
<tr>
|
||||||
|
<td>Age Rating</td>
|
||||||
|
<td><abbr title="<?= $data['age_rating_guide'] ?>"><?= $data['age_rating'] ?></abbr>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<?php if (count($data['links']) > 0): ?>
|
||||||
|
<tr>
|
||||||
|
<td>External Links</td>
|
||||||
|
<td>
|
||||||
|
<?php foreach ($data['links'] as $urlName => $externalUrl): ?>
|
||||||
|
<a rel='external' href="<?= $externalUrl ?>"><?= $urlName ?></a><br />
|
||||||
|
<?php endforeach ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>Genres</td>
|
||||||
|
<td>
|
||||||
|
<?= implode(', ', $data['genres']); ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
</aside>
|
||||||
|
<article class="text">
|
||||||
|
<h2 class="toph"><?= $data['title'] ?></h2>
|
||||||
|
<?php foreach ($data['titles_more'] as $title): ?>
|
||||||
|
<h3><?= $title ?></h3>
|
||||||
|
<?php endforeach ?>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<div class="description">
|
||||||
|
<p><?= str_replace("\n", '</p><p>', $data['synopsis']) ?></p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<?php if (count($data['characters']) > 0): ?>
|
||||||
|
<h2>Characters</h2>
|
||||||
|
|
||||||
|
<?= $component->tabs('manga-characters', $data['characters'], static function($list, $role) use ($component, $helper, $url) {
|
||||||
|
$rendered = [];
|
||||||
|
foreach ($list as $id => $char)
|
||||||
|
{
|
||||||
|
$rendered[] = $component->character(
|
||||||
|
$char['name'],
|
||||||
|
$url->generate('character', ['slug' => $char['slug']]),
|
||||||
|
$helper->picture("images/characters/{$id}.webp"),
|
||||||
|
($role !== 'main') ? 'small-character' : 'character'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode('', array_map('mb_trim', $rendered));
|
||||||
|
}) ?>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<?php if (count($data['staff']) > 0): ?>
|
||||||
|
<h2>Staff</h2>
|
||||||
|
|
||||||
|
<?= $component->verticalTabs('manga-staff', $data['staff'],
|
||||||
|
fn($people) => implode('', array_map(
|
||||||
|
fn ($person) => $component->character(
|
||||||
|
$person['name'],
|
||||||
|
$url->generate('person', ['slug' => $person['slug']]),
|
||||||
|
$helper->picture("images/people/{$person['id']}.webp")
|
||||||
|
),
|
||||||
|
$people
|
||||||
|
))
|
||||||
|
) ?>
|
||||||
|
<?php endif ?>
|
||||||
|
</main>
|
111
app/views/manga/edit.php
Normal file
111
app/views/manga/edit.php
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
|
<main>
|
||||||
|
<h2>
|
||||||
|
Edit Manga List Item
|
||||||
|
</h2>
|
||||||
|
<form action="<?= $action ?>" method="post">
|
||||||
|
<table class="invisible form">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<h3><?= $escape->html($item['manga']['title']) ?></h3>
|
||||||
|
<?php foreach ($item['manga']['titles'] as $title): ?>
|
||||||
|
<h4><?= $escape->html($title) ?></h4>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td rowspan="9">
|
||||||
|
<?= $helper->picture("images/manga/{$item['manga']['id']}-original.webp", "jpg", [], ["width" => "390"]) ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="status">Reading Status</label></td>
|
||||||
|
<td>
|
||||||
|
<select name="status" id="status">
|
||||||
|
<?php foreach ($status_list as $val => $status): ?>
|
||||||
|
<option <?php if ($item['reading_status'] === $val): ?>selected="selected"<?php endif ?>
|
||||||
|
value="<?= $val ?>"><?= $status ?></option>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="series_rating">Rating</label></td>
|
||||||
|
<td>
|
||||||
|
<input type="number" min="0" max="10" maxlength="2" name="new_rating"
|
||||||
|
value="<?= $item['user_rating'] ?>" id="series_rating" size="2"/> / 10
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="chapters_read">Chapters Read</label></td>
|
||||||
|
<td>
|
||||||
|
<input type="number" min="0" name="chapters_read" id="chapters_read"
|
||||||
|
value="<?= $item['chapters']['read'] ?>"/> / <?= $item['chapters']['total'] ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="volumes_read">Volumes Read</label></td>
|
||||||
|
<td>
|
||||||
|
<?php /*<input type="number" disabled="disabled" min="0" name="volumes_read" id="volumes_read" value="" /> */ ?>
|
||||||
|
- / <?= $item['volumes']['total'] ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="rereading_flag">Rereading?</label></td>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="rereading" id="rereading_flag"
|
||||||
|
<?php if ($item['rereading'] === TRUE): ?>checked="checked"<?php endif ?>
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="reread_count">Reread Count</label></td>
|
||||||
|
<td>
|
||||||
|
<input type="number" min="0" id="reread_count" name="reread_count"
|
||||||
|
value="<?= $item['reread'] ?>"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="notes">Notes</label></td>
|
||||||
|
<td>
|
||||||
|
<textarea name="notes" id="notes"><?= $escape->html($item['notes']) ?></textarea>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td> </td>
|
||||||
|
<td>
|
||||||
|
<input type="hidden" value="<?= $item['id'] ?>" name="id"/>
|
||||||
|
<input type="hidden" value="<?= $item['mal_id'] ?>" name="mal_id"/>
|
||||||
|
<input type="hidden" value="<?= $item['manga']['slug'] ?>" name="manga_id"/>
|
||||||
|
<input type="hidden" value="<?= $item['user_rating'] ?>" name="old_rating"/>
|
||||||
|
<input type="hidden" value="true" name="edit"/>
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
<fieldset>
|
||||||
|
<legend>Danger Zone</legend>
|
||||||
|
<form class="js-delete" action="<?= $url->generate('manga.delete') ?>" method="post">
|
||||||
|
<table class="form invisible">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="danger">
|
||||||
|
<strong>Permanently</strong> remove this list item and <strong>all</strong> its data?
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="hidden" value="<?= $item['id'] ?>" name="id"/>
|
||||||
|
<input type="hidden" value="<?= $item['mal_id'] ?>" name="mal_id"/>
|
||||||
|
<button type="submit" class="danger">Delete Entry</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
</fieldset>
|
||||||
|
</main>
|
||||||
|
<?php endif ?>
|
@ -1,33 +1,80 @@
|
|||||||
<main>
|
<main class="media-list">
|
||||||
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
|
<a class="bracketed" href="<?= $url->generate('manga.add.get') ?>">Add Item</a>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php if (empty($sections)): ?>
|
||||||
|
<h3>There's nothing here!</h3>
|
||||||
|
<?php else: ?>
|
||||||
|
<br />
|
||||||
|
<label>Filter: <input type='text' class='media-filter' /></label>
|
||||||
|
<br />
|
||||||
<?php foreach ($sections as $name => $items): ?>
|
<?php foreach ($sections as $name => $items): ?>
|
||||||
<h2><?= $name ?></h2>
|
<h2><?= $name ?></h2>
|
||||||
<table>
|
<?php if (empty($items)): ?>
|
||||||
|
<h3>There's nothing here!</h3>
|
||||||
|
<?php else: ?>
|
||||||
|
<table class='media-wrap'>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
|
<td> </td>
|
||||||
|
<?php endif ?>
|
||||||
<th>Title</th>
|
<th>Title</th>
|
||||||
<th>Rating</th>
|
<th>Rating</th>
|
||||||
<th>Chapters</th>
|
<th>Completed Chapters</th>
|
||||||
<th>Volumes</th>
|
<th># of Volumes</th>
|
||||||
|
<th>Attributes</th>
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php foreach($items as $item): ?>
|
<?php foreach($items as $item): ?>
|
||||||
<tr id="manga-<?= $item['manga']['id'] ?>">
|
<tr id="manga-<?= $item['id'] ?>">
|
||||||
<td class="align_left">
|
<?php if($auth->isAuthenticated()): ?>
|
||||||
<a href="https://hummingbird.me/manga/<?= $item['manga']['id'] ?>">
|
<td>
|
||||||
<?= $item['manga']['romaji_title'] ?>
|
<a class="bracketed" href="<?= $url->generate('edit', [
|
||||||
</a>
|
'controller' => 'manga',
|
||||||
<?= (array_key_exists('english_title', $item['manga'])) ? " · " . $item['manga']['english_title'] : "" ?>
|
'id' => $item['id'],
|
||||||
|
'status' => $name
|
||||||
|
]) ?>">Edit</a>
|
||||||
</td>
|
</td>
|
||||||
<td><?= ($item['rating'] > 0) ? (int)($item['rating'] * 2) : '-' ?> / 10</td>
|
<?php endif ?>
|
||||||
<td><?= $item['chapters_read'] ?> / <?= ($item['manga']['chapter_count'] > 0) ? $item['manga']['chapter_count'] : "-" ?></td>
|
<td class="align-left">
|
||||||
<td><?= $item['volumes_read'] ?> / <?= ($item['manga']['volume_count'] > 0) ? $item['manga']['volume_count'] : "-" ?></td>
|
<a href="<?= $url->generate('manga.details', ['id' => $item['manga']['slug']]) ?>">
|
||||||
<td><?= $item['manga']['manga_type'] ?></td>
|
<?= $item['manga']['title'] ?>
|
||||||
|
</a>
|
||||||
|
<?php foreach($item['manga']['titles'] as $title): ?>
|
||||||
|
<br /><?= $title ?>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</td>
|
||||||
|
<td><?= $item['user_rating'] ?> / 10</td>
|
||||||
|
<td><?= $item['chapters']['read'] ?> / <?= $item['chapters']['total'] ?></td>
|
||||||
|
<td><?= $item['volumes']['total'] ?></td>
|
||||||
|
<td>
|
||||||
|
<ul>
|
||||||
|
<?php if ($item['reread'] == 1): ?>
|
||||||
|
<li>Reread once</li>
|
||||||
|
<?php elseif ($item['reread'] == 2): ?>
|
||||||
|
<li>Reread twice</li>
|
||||||
|
<?php elseif ($item['reread'] == 3): ?>
|
||||||
|
<li>Reread thrice</li>
|
||||||
|
<?php elseif ($item['reread'] > 3): ?>
|
||||||
|
<li>Reread <?= $item['reread'] ?> times</li>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php foreach(['rereading'] as $attr): ?>
|
||||||
|
<?php if($item[$attr]): ?>
|
||||||
|
<li><?= ucfirst($attr); ?></li>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
<td><?= $item['manga']['type'] ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<?php endif ?>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
|
<?php endif ?>
|
||||||
</main>
|
</main>
|
||||||
<script src="<?= asset_url('js.php?g=table') ?>"></script>
|
<script defer="defer" src="<?= $urlGenerator->assetUrl('js/tables.min.js') ?>"></script>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<div class="message <?= $stat_class ?>">
|
<div class="message <?= $escape->attr($message_type) ?>">
|
||||||
<span class="icon"></span>
|
<span class="icon"></span>
|
||||||
<?= $message ?>
|
<?= $escape->html($message) ?>
|
||||||
<span class="close" onclick="this.parentElement.style.display='none'">x</span>
|
<span class="close"></span>
|
||||||
</div>
|
</div>
|
104
app/views/person/details.php
Normal file
104
app/views/person/details.php
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
use function Aviat\AnimeClient\getLocalImg;
|
||||||
|
?>
|
||||||
|
<main class="details fixed">
|
||||||
|
<section class="flex flex-no-wrap">
|
||||||
|
<div>
|
||||||
|
<?= $helper->picture("images/people/{$data['id']}-original.webp", 'jpg', ['class' => 'cover' ]) ?>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 class="toph"><?= $data['name'] ?></h2>
|
||||||
|
<?php foreach ($data['names'] as $name): ?>
|
||||||
|
<h3><?= $name ?></h3>
|
||||||
|
<?php endforeach ?>
|
||||||
|
<br />
|
||||||
|
<hr />
|
||||||
|
<div class="description">
|
||||||
|
<p><?= str_replace("\n", '</p><p>', $data['description']) ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<?php if ( ! empty($data['staff'])): ?>
|
||||||
|
<section>
|
||||||
|
<h3>Castings</h3>
|
||||||
|
|
||||||
|
<div class="vertical-tabs">
|
||||||
|
<?php $i = 0 ?>
|
||||||
|
<?php foreach ($data['staff'] as $role => $entries): ?>
|
||||||
|
<div class="tab">
|
||||||
|
<input
|
||||||
|
type="radio" name="staff-roles" id="staff-role<?= $i ?>" <?= $i === 0 ? 'checked' : '' ?> />
|
||||||
|
<label for="staff-role<?= $i ?>"><?= $role ?></label>
|
||||||
|
<?php foreach ($entries as $type => $casting): ?>
|
||||||
|
<?php if (isset($entries['manga'], $entries['anime'])): ?>
|
||||||
|
<h4><?= ucfirst($type) ?></h4>
|
||||||
|
<?php endif ?>
|
||||||
|
<section class="content media-wrap flex flex-wrap flex-justify-start">
|
||||||
|
<?php foreach ($casting as $sid => $series): ?>
|
||||||
|
<?php $mediaType = in_array($type, ['anime', 'manga'], TRUE) ? $type : 'anime'; ?>
|
||||||
|
<?= $component->media(
|
||||||
|
$series['titles'],
|
||||||
|
$url->generate("{$mediaType}.details", ['id' => $series['slug']]),
|
||||||
|
$helper->picture("images/{$type}/{$sid}.webp")
|
||||||
|
) ?>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</section>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</div>
|
||||||
|
<?php $i++ ?>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<?php if ( ! empty($data['characters'])): ?>
|
||||||
|
<section>
|
||||||
|
<h3>Voice Acting Roles</h3>
|
||||||
|
<?= $component->tabs('voice-acting-roles', $data['characters'], static function ($characterList) use ($component, $helper, $url) {
|
||||||
|
$voiceRoles = [];
|
||||||
|
foreach ($characterList as $cid => $item):
|
||||||
|
$character = $component->character(
|
||||||
|
$item['character']['canonicalName'],
|
||||||
|
$url->generate('character', ['slug' => $item['character']['slug']]),
|
||||||
|
$helper->picture(getLocalImg($item['character']['image']['original'] ?? null))
|
||||||
|
);
|
||||||
|
$medias = [];
|
||||||
|
foreach ($item['media'] as $sid => $series)
|
||||||
|
{
|
||||||
|
$medias[] = $component->media(
|
||||||
|
$series['titles'],
|
||||||
|
$url->generate('anime.details', ['id' => $series['slug']]),
|
||||||
|
$helper->picture("images/anime/{$sid}.webp")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$media = implode('', array_map('mb_trim', $medias));
|
||||||
|
|
||||||
|
$voiceRoles[] = <<<HTML
|
||||||
|
<tr>
|
||||||
|
<td>{$character}</td>
|
||||||
|
<td>
|
||||||
|
<section class="align-left media-wrap">{$media}</section>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
HTML;
|
||||||
|
endforeach;
|
||||||
|
|
||||||
|
$roles = implode('', array_map('mb_trim', $voiceRoles));
|
||||||
|
|
||||||
|
return <<<HTML
|
||||||
|
<table class="borderless max-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Character</th>
|
||||||
|
<th>Series</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>{$roles}</tbody>
|
||||||
|
</table>
|
||||||
|
HTML;
|
||||||
|
|
||||||
|
}) ?>
|
||||||
|
</section>
|
||||||
|
<?php endif ?>
|
||||||
|
</main>
|
5
app/views/settings/_field.php
Normal file
5
app/views/settings/_field.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<article>
|
||||||
|
<label for="<?= $fieldName ?>"><?= $field['title'] ?></label><br />
|
||||||
|
<small><?= $field['description'] ?></small><br />
|
||||||
|
<?= $helper->field($fieldName, $field); ?>
|
||||||
|
</article>
|
24
app/views/settings/_form.php
Normal file
24
app/views/settings/_form.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
// Higher scoped variables:
|
||||||
|
// $fields
|
||||||
|
// $hiddenFields
|
||||||
|
// $nestedPrefix
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php foreach ($fields as $name => $field): ?>
|
||||||
|
<?php
|
||||||
|
$fieldName = ($section === 'config' || $nestedPrefix !== 'config')
|
||||||
|
? "{$nestedPrefix}[{$name}]"
|
||||||
|
: "{$nestedPrefix}[{$section}][{$name}]";
|
||||||
|
?>
|
||||||
|
<?php if ($field['type'] === 'subfield'): ?>
|
||||||
|
<section>
|
||||||
|
<h4><?= $field['title'] ?></h4>
|
||||||
|
<?php include '_subfield.php'; ?>
|
||||||
|
</section>
|
||||||
|
<?php elseif ( ! empty($field['display'])): ?>
|
||||||
|
<?php include '_field.php' ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php $hiddenFields[] = $helper->field($fieldName, $field); ?>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php endforeach ?>
|
20
app/views/settings/_subfield.php
Normal file
20
app/views/settings/_subfield.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
// Higher scoped variables:
|
||||||
|
// $field
|
||||||
|
// $fields
|
||||||
|
// $hiddenFields
|
||||||
|
// $nestedPrefix
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php foreach ($field['fields'] as $name => $field): ?>
|
||||||
|
<?php
|
||||||
|
$fieldName = ($section === 'config' || $nestedPrefix !== 'config')
|
||||||
|
? "{$nestedPrefix}[{$name}]"
|
||||||
|
: "{$nestedPrefix}[{$section}][{$name}]";
|
||||||
|
?>
|
||||||
|
<?php if ( ! empty($field['display'])): ?>
|
||||||
|
<?php include '_field.php' ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php $hiddenFields[] = $helper->field($fieldName, $field); ?>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php endforeach ?>
|
68
app/views/settings/settings.php
Normal file
68
app/views/settings/settings.php
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
if ( ! $auth->isAuthenticated())
|
||||||
|
{
|
||||||
|
echo '<h1>Not Authorized</h1>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sectionMapping = [
|
||||||
|
'anilist' => 'Anilist API Integration',
|
||||||
|
'config' => 'General Settings',
|
||||||
|
'cache' => 'Caching',
|
||||||
|
'database' => 'Collection Database Settings',
|
||||||
|
];
|
||||||
|
|
||||||
|
$hiddenFields = [];
|
||||||
|
$nestedPrefix = 'config';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<form action="<?= $url->generate('settings-post') ?>" method="POST">
|
||||||
|
<main class='settings form'>
|
||||||
|
<button type="submit">Save Changes</button>
|
||||||
|
<div class="tabs">
|
||||||
|
<?php $i = 0; ?>
|
||||||
|
|
||||||
|
<?php foreach ($form as $section => $fields): ?>
|
||||||
|
<input <?= $i === 0 ? 'checked="checked"' : '' ?> type="radio" id="settings-tab<?= $i ?>"
|
||||||
|
name="settings-tabs"
|
||||||
|
/>
|
||||||
|
<label for="settings-tab<?= $i ?>"><h3><?= $sectionMapping[$section] ?></h3></label>
|
||||||
|
<section class="content">
|
||||||
|
<?php if ($section === 'anilist'): ?>
|
||||||
|
<?php $auth = $anilistModel->checkAuth(); ?>
|
||||||
|
<?php if (array_key_exists('errors', $auth)): ?>
|
||||||
|
<p class="static-message error">Not Authorized.</p>
|
||||||
|
<?= $helper->a(
|
||||||
|
$url->generate('anilist-redirect'),
|
||||||
|
'Link Anilist Account'
|
||||||
|
) ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php $expires = $config->get(['anilist', 'access_token_expires']); ?>
|
||||||
|
<p class="static-message info">
|
||||||
|
Linked to Anilist. Your access token will expire around <?= date('F j, Y, g:i a T', $expires) ?>
|
||||||
|
</p>
|
||||||
|
<?php require __DIR__ . '/_form.php' ?>
|
||||||
|
<?= $helper->a(
|
||||||
|
$url->generate('anilist-redirect'),
|
||||||
|
'Update Access Token',
|
||||||
|
['class' => 'bracketed user-btn']
|
||||||
|
) ?>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php require __DIR__ . '/_form.php' ?>
|
||||||
|
<?php endif ?>
|
||||||
|
</section>
|
||||||
|
<?php $i++; ?>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<?php foreach ($hiddenFields as $field): ?>
|
||||||
|
<?= $field->__toString() ?>
|
||||||
|
<?php endforeach ?>
|
||||||
|
<button type="submit">Save Changes</button>
|
||||||
|
</main>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
30
app/views/setup-check.php
Normal file
30
app/views/setup-check.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
use function Aviat\AnimeClient\checkFolderPermissions;
|
||||||
|
|
||||||
|
$setupErrors = checkFolderPermissions($container->get('config'));
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php if ( ! empty($setupErrors)): ?>
|
||||||
|
<aside class="message error">
|
||||||
|
<h1>Issues with server setup:</h1>
|
||||||
|
|
||||||
|
<?php if (array_key_exists('missing', $setupErrors)): ?>
|
||||||
|
<h3>The following folders need to be created, and writable.</h3>
|
||||||
|
<ul>
|
||||||
|
<?php foreach ($setupErrors['missing'] as $error): ?>
|
||||||
|
<li><?= $error ?></li>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</ul>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<?php if (array_key_exists('writable', $setupErrors)): ?>
|
||||||
|
<h3>The following folders are not writable by the server.</h3>
|
||||||
|
<ul>
|
||||||
|
<?php foreach($setupErrors['writable'] as $error): ?>
|
||||||
|
<li><?= $error ?></li>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</ul>
|
||||||
|
<?php endif ?>
|
||||||
|
</aside>
|
||||||
|
<?php endif ?>
|
100
app/views/user/details.php
Normal file
100
app/views/user/details.php
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
use Aviat\AnimeClient\Kitsu;
|
||||||
|
?>
|
||||||
|
<main class="user-page details">
|
||||||
|
<h2 class="toph">
|
||||||
|
<?= $helper->a(
|
||||||
|
"https://kitsu.io/users/{$data['slug']}",
|
||||||
|
$data['name'], [
|
||||||
|
'title' => 'View profile on Kitsu'
|
||||||
|
])
|
||||||
|
?>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p><?= $escape->html($data['about']) ?></p>
|
||||||
|
|
||||||
|
<section class="flex flex-no-wrap">
|
||||||
|
<aside class="info">
|
||||||
|
<center>
|
||||||
|
<?= $helper->img($urlGenerator->assetUrl($data['avatar']), ['alt' => '']); ?>
|
||||||
|
</center>
|
||||||
|
<br />
|
||||||
|
<table class="media-details">
|
||||||
|
<tr>
|
||||||
|
<td>Location</td>
|
||||||
|
<td><?= $data['location'] ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Website</td>
|
||||||
|
<td><?= $helper->a($data['website'], $data['website']) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php if ( ! empty($data['waifu'])): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?= $escape->html($data['waifu']['label']) ?></td>
|
||||||
|
<td>
|
||||||
|
<?php
|
||||||
|
$character = $data['waifu']['character'];
|
||||||
|
echo $helper->a(
|
||||||
|
$url->generate('character', ['slug' => $character['slug']]),
|
||||||
|
$character['names']['canonical']
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endif ?>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>User Stats</h3><br />
|
||||||
|
<table class="media-details">
|
||||||
|
<?php foreach($data['stats'] as $label => $stat): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?= $label ?></td>
|
||||||
|
<td><?= $stat ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</table>
|
||||||
|
</aside>
|
||||||
|
<article>
|
||||||
|
<?php if ( ! empty($data['favorites'])): ?>
|
||||||
|
<h3>Favorites</h3>
|
||||||
|
<?= $component->tabs('user-favorites', $data['favorites'], static function ($items, $type) use ($component, $helper, $url) {
|
||||||
|
$rendered = [];
|
||||||
|
if ($type === 'character')
|
||||||
|
{
|
||||||
|
uasort($items, fn ($a, $b) => $a['names']['canonical'] <=> $b['names']['canonical']);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uasort($items, fn ($a, $b) => $a['titles']['canonical'] <=> $b['titles']['canonical']);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($items as $id => $item)
|
||||||
|
{
|
||||||
|
if ($type === 'character')
|
||||||
|
{
|
||||||
|
$rendered[] = $component->character(
|
||||||
|
$item['names']['canonical'],
|
||||||
|
$url->generate('character', ['slug' => $item['slug']]),
|
||||||
|
$helper->picture("images/characters/{$item['id']}.webp")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$rendered[] = $component->media(
|
||||||
|
array_merge(
|
||||||
|
[$item['titles']['canonical']],
|
||||||
|
Kitsu::getFilteredTitles($item['titles']),
|
||||||
|
),
|
||||||
|
$url->generate("{$type}.details", ['id' => $item['slug']]),
|
||||||
|
$helper->picture("images/{$type}/{$item['id']}.webp"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode('', array_map('mb_trim', $rendered));
|
||||||
|
|
||||||
|
}, 'content full-width media-wrap') ?>
|
||||||
|
<?php endif ?>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
</main>
|
@ -0,0 +1,28 @@
|
|||||||
|
<documentation title="Closing comments instead of PHP closing tag">
|
||||||
|
<standard>
|
||||||
|
<![CDATA[
|
||||||
|
The PHP closing tag on a PHP document ?> is optional to the PHP parser. However, if used, any whitespace following the closing tag, whether introduced by the developer, user, or an FTP application, can cause unwanted output, PHP errors, or if the latter are suppressed, blank pages. For this reason, all PHP files should OMIT the closing PHP tag, and instead use a comment block to mark the end of file and it's location relative to the application root. This allows you to still identify a file as being complete and not truncated.
|
||||||
|
]]>
|
||||||
|
</standard>
|
||||||
|
<code_comparison>
|
||||||
|
<code title="Examples of valid closing comments">
|
||||||
|
<![CDATA[
|
||||||
|
<?php
|
||||||
|
|
||||||
|
echo "Here's my code!";
|
||||||
|
|
||||||
|
/* End of file myfile.php */
|
||||||
|
/* Location: ./system/modules/mymodule/myfile.php */
|
||||||
|
]]>
|
||||||
|
</code>
|
||||||
|
<code title="Examples of invalid closing comments">
|
||||||
|
<![CDATA[
|
||||||
|
<?php
|
||||||
|
|
||||||
|
echo "Here's my code!";
|
||||||
|
|
||||||
|
?>
|
||||||
|
]]>
|
||||||
|
</code>
|
||||||
|
</code_comparison>
|
||||||
|
</documentation>
|
@ -0,0 +1,7 @@
|
|||||||
|
<documentation title="Unicode (UTF-8) encoding without BOM">
|
||||||
|
<standard>
|
||||||
|
<![CDATA[
|
||||||
|
Files should be saved with Unicode (UTF-8) encoding. The BOM should not be used. Unlike UTF-16 and UTF-32, there's no byte order to indicate in a UTF-8 encoded file, and the BOM can have a negative side effect in PHP of sending output, preventing the application from being able to set its own headers. Unix line endings should be used (LF).
|
||||||
|
]]>
|
||||||
|
</standard>
|
||||||
|
</documentation>
|
@ -0,0 +1,31 @@
|
|||||||
|
<documentation title="Constructor Names">
|
||||||
|
<standard>
|
||||||
|
<![CDATA[
|
||||||
|
Class names should always start with an uppercase letter. Multiple words should be separated with an underscore, and not CamelCased. All other class methods should be entirely lowercased and named to clearly indicate their function, preferably including a verb. Try to avoid overly long and verbose names.
|
||||||
|
]]>
|
||||||
|
</standard>
|
||||||
|
<code_comparison>
|
||||||
|
<code title="Examples of valid constructor name">
|
||||||
|
<![CDATA[
|
||||||
|
class Super_class
|
||||||
|
{
|
||||||
|
function Super_class()
|
||||||
|
{
|
||||||
|
echo 'Some code here !';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</code>
|
||||||
|
<code title="Examples of invalid constructor name">
|
||||||
|
<![CDATA[
|
||||||
|
class Super_class
|
||||||
|
{
|
||||||
|
function __constructor()
|
||||||
|
{
|
||||||
|
echo 'Some code here !';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</code>
|
||||||
|
</code_comparison>
|
||||||
|
</documentation>
|
@ -0,0 +1,21 @@
|
|||||||
|
<documentation title="Class names">
|
||||||
|
<standard>
|
||||||
|
<![CDATA[
|
||||||
|
Class names should always start with an uppercase letter. Multiple words should be separated with an underscore, and not CamelCased. All other class methods should be entirely lowercased and named to clearly indicate their function, preferably including a verb. Try to avoid overly long and verbose names.
|
||||||
|
]]>
|
||||||
|
</standard>
|
||||||
|
<code_comparison>
|
||||||
|
<code title="Examples of valid class names">
|
||||||
|
<![CDATA[
|
||||||
|
class Super_class
|
||||||
|
]]>
|
||||||
|
</code>
|
||||||
|
<code title="Examples of invalid class names">
|
||||||
|
<![CDATA[
|
||||||
|
class SuperClass // words not separated with underscores and words next to the first one start with an upper case
|
||||||
|
class superclass // words not separated with underscores
|
||||||
|
class Super_Class // words next to the first one start with an upper case
|
||||||
|
]]>
|
||||||
|
</code>
|
||||||
|
</code_comparison>
|
||||||
|
</documentation>
|
@ -0,0 +1,21 @@
|
|||||||
|
<documentation title="File names">
|
||||||
|
<standard>
|
||||||
|
<![CDATA[
|
||||||
|
To be able to find which class is contained in a file, file names should be case-insensitively equal to class names. Some operating systems and tools are case-insensitive, though other are. So, file names should be in lower case to avoid any trouble.
|
||||||
|
]]>
|
||||||
|
</standard>
|
||||||
|
<code_comparison>
|
||||||
|
<code title="Examples of valid file names">
|
||||||
|
<![CDATA[
|
||||||
|
super_class.php
|
||||||
|
]]>
|
||||||
|
</code>
|
||||||
|
<code title="Examples of invalid file names">
|
||||||
|
<![CDATA[
|
||||||
|
superclass.php // words not separated with underscores
|
||||||
|
SuperClass.php // not in lower case and words not separated with underscores
|
||||||
|
Super_class.php // not in lower case
|
||||||
|
]]>
|
||||||
|
</code>
|
||||||
|
</code_comparison>
|
||||||
|
</documentation>
|
@ -0,0 +1,27 @@
|
|||||||
|
<documentation title="Function and Method Names">
|
||||||
|
<standard>
|
||||||
|
<![CDATA[
|
||||||
|
Class names should always start with an uppercase letter. Multiple words should be separated with an underscore, and not CamelCased. All other class methods should be entirely lowercased and named to clearly indicate their function, preferably including a verb. Try to avoid overly long and verbose names.
|
||||||
|
Methods and variables that are only accessed internally by your class, such as utility and helper functions that your public methods use for code abstraction, should be prefixed with an underscore.
|
||||||
|
]]>
|
||||||
|
</standard>
|
||||||
|
<code_comparison>
|
||||||
|
<code title="Examples of valid method names">
|
||||||
|
<![CDATA[
|
||||||
|
function get_file_properties() // descriptive, underscore separator, and all lowercase letters
|
||||||
|
private function _get_file_properties()
|
||||||
|
]]>
|
||||||
|
</code>
|
||||||
|
<code title="Examples of invalid method names">
|
||||||
|
<![CDATA[
|
||||||
|
function fileproperties() // not descriptive and needs underscore separator
|
||||||
|
function fileProperties() // not descriptive and uses CamelCase
|
||||||
|
function getfileproperties() // Better! But still missing underscore separator
|
||||||
|
function getFileProperties() // uses CamelCase
|
||||||
|
function get_the_file_properties_from_the_file() // wordy
|
||||||
|
private function get_the_file_properties() // not prefixed with an underscor, though private
|
||||||
|
function _get_the_file_properties() // prefixed with an underscor, though public
|
||||||
|
]]>
|
||||||
|
</code>
|
||||||
|
</code_comparison>
|
||||||
|
</documentation>
|
@ -0,0 +1,31 @@
|
|||||||
|
<documentation title="Variable names">
|
||||||
|
<standard>
|
||||||
|
<![CDATA[
|
||||||
|
Namely, variables should contain only lowercase letters, use underscore separators, and be reasonably named to indicate their purpose and contents. Very short, non-word variables should only be used as iterators in for() loops.
|
||||||
|
Methods and variables that are only accessed internally by your class, such as utility and helper functions that your public methods use for code abstraction, should be prefixed with an underscore.
|
||||||
|
]]>
|
||||||
|
</standard>
|
||||||
|
<code_comparison>
|
||||||
|
<code title="Examples of valid variable names">
|
||||||
|
<![CDATA[
|
||||||
|
for ($j = 0; $j < 10; $j++)
|
||||||
|
$str
|
||||||
|
$buffer
|
||||||
|
$group_id
|
||||||
|
$last_city
|
||||||
|
private $_internal_data;
|
||||||
|
]]>
|
||||||
|
</code>
|
||||||
|
<code title="Examples of invalid variable names">
|
||||||
|
<![CDATA[
|
||||||
|
$j = 'foo'; // single letter variables should only be used in for() loops
|
||||||
|
$Str // contains uppercase letters
|
||||||
|
$bufferedText // uses CamelCasing, and could be shortened without losing semantic meaning
|
||||||
|
$groupid // multiple words, needs underscore separator
|
||||||
|
$name_of_last_city_used // too long
|
||||||
|
private $internal_data; // not prefixed with an underscor, though private
|
||||||
|
$_public_attribute; // prefixed with an underscor, though public
|
||||||
|
]]>
|
||||||
|
</code>
|
||||||
|
</code_comparison>
|
||||||
|
</documentation>
|
@ -0,0 +1,40 @@
|
|||||||
|
<documentation title="Strict comparison operators">
|
||||||
|
<standard>
|
||||||
|
<![CDATA[
|
||||||
|
Some PHP functions return FALSE on failure, but may also have a valid return value of "" or 0, which would evaluate to FALSE in loose comparisons. Be explicit by comparing the variable type when using these return values in conditionals to ensure the return value is indeed what you expect, and not a value that has an equivalent loose-type evaluation.
|
||||||
|
Use the same stringency in returning and checking your own variables. Use === and !== as necessary.
|
||||||
|
]]>
|
||||||
|
</standard>
|
||||||
|
<code_comparison>
|
||||||
|
<code title="Valid strict comparison">
|
||||||
|
<![CDATA[
|
||||||
|
if (strpos($str, 'foo') === FALSE) {
|
||||||
|
echo 'Do something.';
|
||||||
|
}
|
||||||
|
|
||||||
|
function build_string($str = "")
|
||||||
|
{
|
||||||
|
if ($str === "") {
|
||||||
|
echo 'Buid string.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</code>
|
||||||
|
<code title="Invalid loose comparison">
|
||||||
|
<![CDATA[
|
||||||
|
// If 'foo' is at the beginning of the string, strpos will return a 0,
|
||||||
|
// resulting in this conditional evaluating as TRUE
|
||||||
|
if (strpos($str, 'foo') == FALSE) {
|
||||||
|
echo 'Do something.';
|
||||||
|
}
|
||||||
|
|
||||||
|
function build_string($str = "")
|
||||||
|
{
|
||||||
|
if ($str == "") { // uh-oh! What if FALSE or the integer 0 is passed as an argument?
|
||||||
|
echo 'Buid string.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</code>
|
||||||
|
</code_comparison>
|
||||||
|
</documentation>
|
28
build/CodeIgniter/Docs/Strings/DoubleQuoteUsageStandard.xml
Executable file
28
build/CodeIgniter/Docs/Strings/DoubleQuoteUsageStandard.xml
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
<documentation title="Double-quoted strings">
|
||||||
|
<standard>
|
||||||
|
<![CDATA[
|
||||||
|
Always use single quoted strings unless you need variables parsed, and in cases where you do need variables parsed, use braces to prevent greedy token parsing. You may also use double-quoted strings if the string contains single quotes, so you do not have to use escape characters.
|
||||||
|
]]>
|
||||||
|
</standard>
|
||||||
|
<code_comparison>
|
||||||
|
<code title="Examples of invalid double-quoted strings">
|
||||||
|
<![CDATA[
|
||||||
|
"My String" // no variable parsing, so no use for double quotes
|
||||||
|
"My string $foo" // needs braces
|
||||||
|
'SELECT foo FROM bar WHERE baz = \'bag\'' // ugly
|
||||||
|
'\r\n' // it isn't wrong, but it won't be interpreted as a new line feed
|
||||||
|
]]>
|
||||||
|
</code>
|
||||||
|
<code title="Examples of valid double-quoted strings">
|
||||||
|
<![CDATA[
|
||||||
|
'My String'
|
||||||
|
"My string {$foo}" // variables in strings may be enclosed with braces in 2 ways
|
||||||
|
"My string ${foo}"
|
||||||
|
"My string {$foo['bar']}" // variables in strings may be an array entry
|
||||||
|
"My string {$foo->bar}" // variables in strings may be an object attribute
|
||||||
|
"SELECT foo FROM bar WHERE baz = 'bag'"
|
||||||
|
"\n" // not specified in Code Igniter coding standard, but it should be allowed
|
||||||
|
]]>
|
||||||
|
</code>
|
||||||
|
</code_comparison>
|
||||||
|
</documentation>
|
187
build/CodeIgniter/Sniffs/Commenting/InlineCommentSniff.php
Normal file
187
build/CodeIgniter/Sniffs/Commenting/InlineCommentSniff.php
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* CodeIgniter_Sniffs_Commenting_InlineCommentSniff.
|
||||||
|
*
|
||||||
|
* PHP version 5
|
||||||
|
*
|
||||||
|
* @category PHP
|
||||||
|
* @package PHP_CodeSniffer
|
||||||
|
* @author Thomas Ernest <thomas.ernest@baobaz.com>
|
||||||
|
* @copyright 2011 Thomas Ernest
|
||||||
|
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
|
||||||
|
* @link http://pear.php.net/package/PHP_CodeSniffer
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CodeIgniter_Sniffs_Commenting_InlineCommentSniff.
|
||||||
|
*
|
||||||
|
* Ensure the use of single line comments within code (i.e //)
|
||||||
|
* and blank lines between large comment blocks and code.
|
||||||
|
*
|
||||||
|
* @category PHP
|
||||||
|
* @package PHP_CodeSniffer
|
||||||
|
* @author Thomas Ernest <thomas.ernest@baobaz.com>
|
||||||
|
* @copyright 2011 Thomas Ernest
|
||||||
|
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
|
||||||
|
* @link http://pear.php.net/package/PHP_CodeSniffer
|
||||||
|
*/
|
||||||
|
namespace CodeIgniter\Sniffs\Commenting;
|
||||||
|
|
||||||
|
use PHP_CodeSniffer\Sniffs\Sniff;
|
||||||
|
use PHP_CodeSniffer\Files\File;
|
||||||
|
|
||||||
|
class InlineCommentSniff implements Sniff
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var int Limit defining long comments.
|
||||||
|
* Long comments count $longCommentLimit or more lines.
|
||||||
|
*/
|
||||||
|
public $longCommentLimit = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of tokens this test wants to listen for.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
T_COMMENT
|
||||||
|
);
|
||||||
|
}//end register()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes this test, when one of its tokens is encountered.
|
||||||
|
*
|
||||||
|
* @param File $phpcsFile The current file being scanned.
|
||||||
|
* @param int $stackPtr The position of the current token
|
||||||
|
* in the stack passed in $tokens.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function process(File $phpcsFile, $stackPtr)
|
||||||
|
{
|
||||||
|
$tokens = $phpcsFile->getTokens();
|
||||||
|
|
||||||
|
// keep testing only if it's about the first comment of the block
|
||||||
|
$previousCommentPtr = $phpcsFile->findPrevious($tokens[$stackPtr]['code'], $stackPtr - 1);
|
||||||
|
if ($tokens[$previousCommentPtr]['line'] !== $tokens[$stackPtr]['line'] - 1) {
|
||||||
|
if (TRUE !== $this->_checkCommentStyle($phpcsFile, $stackPtr)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$commentLines = $this->_getCommentBlock($phpcsFile, $stackPtr);
|
||||||
|
|
||||||
|
if (count($commentLines) >= $this->longCommentLimit) {
|
||||||
|
$this->_checkBlankLinesAroundLongComment($phpcsFile, $commentLines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}//end process()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add error to $phpcsFile, if comment pointed by $stackPtr doesn't start
|
||||||
|
* with '//'.
|
||||||
|
*
|
||||||
|
* @param File $phpcsFile The current file being scanned.
|
||||||
|
* @param int $stackPtr The position of the current token
|
||||||
|
* that has to be a comment.
|
||||||
|
*
|
||||||
|
* @return bool TRUE if the content of the token pointed by $stackPtr starts
|
||||||
|
* with //, FALSE if an error was added to $phpcsFile.
|
||||||
|
*/
|
||||||
|
private function _checkCommentStyle(File $phpcsFile, $stackPtr)
|
||||||
|
{
|
||||||
|
$tokens = $phpcsFile->getTokens();
|
||||||
|
if ($tokens[$stackPtr]['content'][0] === '#') {
|
||||||
|
$error = 'Perl-style comments are not allowed; use "// Comment" or DocBlock comments instead';
|
||||||
|
$phpcsFile->addError($error, $stackPtr, 'WrongStyle');
|
||||||
|
return FALSE;
|
||||||
|
} else if (substr($tokens[$stackPtr]['content'], 0, 2) === '/*'
|
||||||
|
|| $tokens[$stackPtr]['content'][0] === '*'
|
||||||
|
) {
|
||||||
|
$error = 'Multi lines comments are not allowed; use "// Comment" DocBlock comments instead';
|
||||||
|
$phpcsFile->addError($error, $stackPtr, 'WrongStyle');
|
||||||
|
return FALSE;
|
||||||
|
} else if (substr($tokens[$stackPtr]['content'], 0, 2) !== '//') {
|
||||||
|
$error = 'Use single line or DocBlock comments within code';
|
||||||
|
$phpcsFile->addError($error, $stackPtr, 'WrongStyle');
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}//_checkCommentStyle()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gather into an array all comment lines to which $stackPtr belongs.
|
||||||
|
*
|
||||||
|
* @param File $phpcsFile The current file being scanned.
|
||||||
|
* @param int $stackPtr Pointer to the first comment line.
|
||||||
|
*
|
||||||
|
* @return type array Pointers to tokens making up the comment block.
|
||||||
|
*/
|
||||||
|
private function _getCommentBlock(File $phpcsFile, $stackPtr)
|
||||||
|
{
|
||||||
|
$tokens = $phpcsFile->getTokens();
|
||||||
|
$commentLines = array($stackPtr);
|
||||||
|
$nextComment = $stackPtr;
|
||||||
|
$lastLine = $tokens[$stackPtr]['line'];
|
||||||
|
|
||||||
|
while (($nextComment = $phpcsFile->findNext($tokens[$stackPtr]['code'], ($nextComment + 1), null, false)) !== false) {
|
||||||
|
if (($tokens[$nextComment]['line'] - 1) !== $lastLine) {
|
||||||
|
// Not part of the block.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$lastLine = $tokens[$nextComment]['line'];
|
||||||
|
$commentLines[] = $nextComment;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $commentLines;
|
||||||
|
}//_getCommentBlock()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add errors to $phpcsFile, if $commentLines isn't enclosed with blank lines.
|
||||||
|
*
|
||||||
|
* @param File $phpcsFile The current file being scanned.
|
||||||
|
* @param array $commentLines Lines of the comment block being checked.
|
||||||
|
*
|
||||||
|
* @return bool TRUE if $commentLines is enclosed with at least a blank line
|
||||||
|
* before and after, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
private function _checkBlankLinesAroundLongComment(File $phpcsFile, array $commentLines)
|
||||||
|
{
|
||||||
|
$hasBlankLinesAround = TRUE;
|
||||||
|
$tokens = $phpcsFile->getTokens();
|
||||||
|
|
||||||
|
// check blank line before the long comment
|
||||||
|
$firstCommentPtr = reset($commentLines);
|
||||||
|
$firstPreviousSpacePtr = $firstCommentPtr - 1;
|
||||||
|
while (T_WHITESPACE === $tokens[$firstPreviousSpacePtr]['code'] && $firstPreviousSpacePtr > 0) {
|
||||||
|
$firstPreviousSpacePtr--;
|
||||||
|
}
|
||||||
|
if ($tokens[$firstPreviousSpacePtr]['line'] >= $tokens[$firstCommentPtr]['line'] - 1) {
|
||||||
|
$error = "Please add a blank line before comments counting more than {$this->longCommentLimit} lines.";
|
||||||
|
$phpcsFile->addError($error, $firstCommentPtr, 'LongCommentWithoutSpacing');
|
||||||
|
$hasBlankLinesAround = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check blank line after the long comment
|
||||||
|
$lastCommentPtr = end($commentLines);
|
||||||
|
$lastNextSpacePtr = $lastCommentPtr + 1;
|
||||||
|
while (T_WHITESPACE === $tokens[$lastNextSpacePtr]['code'] && $lastNextSpacePtr < count($tokens)) {
|
||||||
|
$lastNextSpacePtr++;
|
||||||
|
}
|
||||||
|
if ($tokens[$lastNextSpacePtr]['line'] <= $tokens[$lastCommentPtr]['line'] + 1) {
|
||||||
|
$error = "Please add a blank line after comments counting more than {$this->longCommentLimit} lines.";
|
||||||
|
$phpcsFile->addError($error, $lastCommentPtr, 'LongCommentWithoutSpacing');
|
||||||
|
$hasBlankLinesAround = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $hasBlankLinesAround;
|
||||||
|
}//end _checkBlanksAroundLongComment()
|
||||||
|
|
||||||
|
}//end class
|
||||||
|
|
||||||
|
?>
|
98
build/CodeIgniter/Sniffs/Files/ByteOrderMarkSniff.php
Executable file
98
build/CodeIgniter/Sniffs/Files/ByteOrderMarkSniff.php
Executable file
@ -0,0 +1,98 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* CodeIgniter_Sniffs_Files_ByteOrderMarkSniff.
|
||||||
|
*
|
||||||
|
* PHP version 5
|
||||||
|
*
|
||||||
|
* @category PHP
|
||||||
|
* @package PHP_CodeSniffer
|
||||||
|
* @author Thomas Ernest <thomas.ernest@baobaz.com>
|
||||||
|
* @copyright 2006 Thomas Ernest
|
||||||
|
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
|
||||||
|
* @link http://pear.php.net/package/PHP_CodeSniffer
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CodeIgniter_Sniffs_Files_ByteOrderMarkSniff.
|
||||||
|
*
|
||||||
|
* Ensures that no BOM appears at the beginning of file.
|
||||||
|
*
|
||||||
|
* @category PHP
|
||||||
|
* @package PHP_CodeSniffer
|
||||||
|
* @author Thomas Ernest <thomas.ernest@baobaz.com>
|
||||||
|
* @copyright 2006 Thomas Ernest
|
||||||
|
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
|
||||||
|
* @link http://pear.php.net/package/PHP_CodeSniffer
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace CodeIgniter\Sniffs\Files;
|
||||||
|
|
||||||
|
use PHP_CodeSniffer\Sniffs\Sniff;
|
||||||
|
use PHP_CodeSniffer\Files\File;
|
||||||
|
|
||||||
|
class ByteOrderMarkSniff implements Sniff
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns an array of tokens this test wants to listen for.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
return array( T_OPEN_TAG );
|
||||||
|
}//end register()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of supported BOM definitions.
|
||||||
|
*
|
||||||
|
* Use encoding names as keys and hex BOM representations as values.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getBomDefinitions()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
'UTF-8' => 'efbbbf',
|
||||||
|
'UTF-16 (BE)' => 'feff',
|
||||||
|
'UTF-16 (LE)' => 'fffe',
|
||||||
|
'UTF-32 (BE)' => '0000feff',
|
||||||
|
'UTF-32 (LE)' => 'fffe0000'
|
||||||
|
);
|
||||||
|
}//end getBomDefinitions()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process tokens.
|
||||||
|
*
|
||||||
|
* Actually, only proceed when we're at index 0, this should be the only case
|
||||||
|
* that will contain BOM. Then check if BOM definition matches what
|
||||||
|
* we've found as file's inline HTML. Inline HTML could be longer than just BOM
|
||||||
|
* so make sure you test as much as needed.
|
||||||
|
*
|
||||||
|
* @param File $phpcsFile The current file being scanned.
|
||||||
|
* @param int $stackPtr The position of the current token
|
||||||
|
* in the stack passed in $tokens.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function process(File $phpcsFile, $stackPtr )
|
||||||
|
{
|
||||||
|
// We are only interested if this is the first open tag.
|
||||||
|
if ($stackPtr !== 0) {
|
||||||
|
if ($phpcsFile->findPrevious(T_OPEN_TAG, ($stackPtr - 1)) !== false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$tokens = $phpcsFile->getTokens();
|
||||||
|
$fileStartString = $tokens[0]['content'];
|
||||||
|
foreach ($this->getBomDefinitions() as $bomName => $expectedBomHex) {
|
||||||
|
$bomByteLength = strlen($expectedBomHex) / 2;
|
||||||
|
$fileStartHex = bin2hex(substr($fileStartString, 0, $bomByteLength));
|
||||||
|
if ($fileStartHex === $expectedBomHex) {
|
||||||
|
$error = "File contains a $bomName byte order mark (BOM).";
|
||||||
|
$phpcsFile->addError($error, $stackPtr, 123);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}//end process()
|
||||||
|
}
|
222
build/CodeIgniter/Sniffs/Files/Utf8EncodingSniff.php
Executable file
222
build/CodeIgniter/Sniffs/Files/Utf8EncodingSniff.php
Executable file
@ -0,0 +1,222 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* CodeIgniter_Sniffs_Files_Utf8EncodingSniff.
|
||||||
|
*
|
||||||
|
* PHP version 5
|
||||||
|
*
|
||||||
|
* @category PHP
|
||||||
|
* @package PHP_CodeSniffer
|
||||||
|
* @author Thomas Ernest <thomas.ernest@baobaz.com>
|
||||||
|
* @copyright 2006 Thomas Ernest
|
||||||
|
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
|
||||||
|
* @link http://pear.php.net/package/PHP_CodeSniffer
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CodeIgniter_Sniffs_Files_Utf8EncodingSniff.
|
||||||
|
*
|
||||||
|
* Ensures that PHP files are encoded with Unicode (UTF-8) encoding.
|
||||||
|
*
|
||||||
|
* @category PHP
|
||||||
|
* @package PHP_CodeSniffer
|
||||||
|
* @author Thomas Ernest <thomas.ernest@baobaz.com>
|
||||||
|
* @copyright 2006 Thomas Ernest
|
||||||
|
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
|
||||||
|
* @link http://pear.php.net/package/PHP_CodeSniffer
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace CodeIgniter\Sniffs\Files;
|
||||||
|
|
||||||
|
use PHP_CodeSniffer\Sniffs\Sniff;
|
||||||
|
use PHP_CodeSniffer\Files\File;
|
||||||
|
|
||||||
|
class Utf8EncodingSniff implements Sniff
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of tokens this test wants to listen for.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
T_OPEN_TAG
|
||||||
|
);
|
||||||
|
|
||||||
|
}//end register()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes this test, when one of its tokens is encountered.
|
||||||
|
*
|
||||||
|
* @param File $phpcsFile The current file being scanned.
|
||||||
|
* @param int $stackPtr The position of the current token
|
||||||
|
* in the stack passed in $tokens.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function process(File $phpcsFile, $stackPtr)
|
||||||
|
{
|
||||||
|
// We are only interested if this is the first open tag.
|
||||||
|
if ($stackPtr !== 0) {
|
||||||
|
if ($phpcsFile->findPrevious(T_OPEN_TAG, ($stackPtr - 1)) !== false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$file_path = $phpcsFile->getFilename();
|
||||||
|
$file_name = basename($file_path);
|
||||||
|
$file_content = file_get_contents($file_path);
|
||||||
|
if (false === mb_check_encoding($file_content, 'UTF-8')) {
|
||||||
|
$error = 'File "' . $file_name . '" should be saved with Unicode (UTF-8) encoding.';
|
||||||
|
$phpcsFile->addError($error, 0);
|
||||||
|
}
|
||||||
|
if ( ! self::_checkUtf8W3c($file_content)) {
|
||||||
|
$error = 'File "' . $file_name . '" should be saved with Unicode (UTF-8) encoding, but it did not successfully pass the W3C test.';
|
||||||
|
$phpcsFile->addError($error, 0);
|
||||||
|
}
|
||||||
|
if ( ! self::_checkUtf8Rfc3629($file_content)) {
|
||||||
|
$error = 'File "' . $file_name . '" should be saved with Unicode (UTF-8) encoding, but it did not meet RFC3629 requirements.';
|
||||||
|
$phpcsFile->addError($error, 0);
|
||||||
|
}
|
||||||
|
}//end process()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that the string $content contains only valid UTF-8 chars
|
||||||
|
* using W3C's method.
|
||||||
|
* Returns true if $content contains only UTF-8 chars, false otherwise.
|
||||||
|
*
|
||||||
|
* @param string $content String to check.
|
||||||
|
*
|
||||||
|
* @return bool true if $content contains only UTF-8 chars, false otherwise.
|
||||||
|
*
|
||||||
|
* @see http://w3.org/International/questions/qa-forms-utf-8.html
|
||||||
|
*/
|
||||||
|
private static function _checkUtf8W3c($content)
|
||||||
|
{
|
||||||
|
$content_chunks=self::mb_chunk_split($content, 4096, '');
|
||||||
|
foreach($content_chunks as $content_chunk)
|
||||||
|
{
|
||||||
|
$preg_result= preg_match(
|
||||||
|
'%^(?:
|
||||||
|
[\x09\x0A\x0D\x20-\x7E] # ASCII
|
||||||
|
| [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
|
||||||
|
| \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
|
||||||
|
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
|
||||||
|
| \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
|
||||||
|
| \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
|
||||||
|
| [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
|
||||||
|
| \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
|
||||||
|
)*$%xs',
|
||||||
|
$content_chunk
|
||||||
|
);
|
||||||
|
if($preg_result!==1)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}//end _checkUtf8W3c()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that the string $content contains only valid UTF-8 chars
|
||||||
|
* using the method described in RFC 3629.
|
||||||
|
* Returns true if $content contains only UTF-8 chars, false otherwise.
|
||||||
|
*
|
||||||
|
* @param string $content String to check.
|
||||||
|
*
|
||||||
|
* @return bool true if $content contains only UTF-8 chars, false otherwise.
|
||||||
|
*
|
||||||
|
* @see http://www.php.net/manual/en/function.mb-detect-encoding.php#85294
|
||||||
|
*/
|
||||||
|
private static function _checkUtf8Rfc3629($content)
|
||||||
|
{
|
||||||
|
$len = strlen($content);
|
||||||
|
for ($i = 0; $i < $len; $i++) {
|
||||||
|
$c = ord($content[$i]);
|
||||||
|
if ($c > 128) {
|
||||||
|
if (($c >= 254)) {
|
||||||
|
return false;
|
||||||
|
} elseif ($c >= 252) {
|
||||||
|
$bits=6;
|
||||||
|
} elseif ($c >= 248) {
|
||||||
|
$bits=5;
|
||||||
|
} elseif ($c >= 240) {
|
||||||
|
$bytes = 4;
|
||||||
|
} elseif ($c >= 224) {
|
||||||
|
$bytes = 3;
|
||||||
|
} elseif ($c >= 192) {
|
||||||
|
$bytes = 2;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
} if (($i + $bytes) > $len) {
|
||||||
|
return false;
|
||||||
|
} while ($bytes > 1) {
|
||||||
|
$i++;
|
||||||
|
$b = ord($content[$i]);
|
||||||
|
if ($b < 128 || $b > 191) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$bytes--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}//_checkUtf8Rfc3629()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits a string to chunks of given size
|
||||||
|
* This helps to avoid segmentation fault errors when large text is given
|
||||||
|
* Returns array of strings after splitting
|
||||||
|
*
|
||||||
|
* @param string $str String to split.
|
||||||
|
* @param int $len number of characters per chunk
|
||||||
|
*
|
||||||
|
* @return array string array after splitting
|
||||||
|
*
|
||||||
|
* @see http://php.net/manual/en/function.chunk-split.php
|
||||||
|
*/
|
||||||
|
private static function mb_chunk_split($str, $len, $glue)
|
||||||
|
{
|
||||||
|
if (empty($str)) return false;
|
||||||
|
$array = self::mbStringToArray ($str);
|
||||||
|
$n = -1;
|
||||||
|
$new = Array();
|
||||||
|
foreach ($array as $char) {
|
||||||
|
$n++;
|
||||||
|
if ($n < $len) $new []= $char;
|
||||||
|
elseif ($n == $len) {
|
||||||
|
$new []= $glue . $char;
|
||||||
|
$n = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $new;
|
||||||
|
}//mb_chunk_split
|
||||||
|
/**
|
||||||
|
* Supporting function for mb_chunk_split
|
||||||
|
*
|
||||||
|
* @param string $str
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @see http://php.net/manual/en/function.chunk-split.php
|
||||||
|
*/
|
||||||
|
private static function mbStringToArray ($str)
|
||||||
|
{
|
||||||
|
if (empty($str)) return false;
|
||||||
|
$len = mb_strlen($str);
|
||||||
|
$array = array();
|
||||||
|
for ($i = 0; $i < $len; $i++) {
|
||||||
|
$array[] = mb_substr($str, $i, 1);
|
||||||
|
}
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}//end class
|
||||||
|
|
||||||
|
?>
|
81
build/CodeIgniter/Sniffs/Operators/StrictComparisonOperatorSniff.php
Executable file
81
build/CodeIgniter/Sniffs/Operators/StrictComparisonOperatorSniff.php
Executable file
@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* CodeIgniter_Sniffs_Operators_StrictComparisonOperatorSniff.
|
||||||
|
*
|
||||||
|
* PHP version 5
|
||||||
|
*
|
||||||
|
* @category PHP
|
||||||
|
* @package PHP_CodeSniffer
|
||||||
|
* @author Thomas Ernest <thomas.ernest@baobaz.com>
|
||||||
|
* @copyright 2006 Thomas Ernest
|
||||||
|
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
|
||||||
|
* @link http://pear.php.net/package/PHP_CodeSniffer
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CodeIgniter_Sniffs_Operators_StrictComparisonOperatorSniff.
|
||||||
|
*
|
||||||
|
* Ensures that only strict comparison operators are used instead of
|
||||||
|
* equal and not equal operators.
|
||||||
|
*
|
||||||
|
* @category PHP
|
||||||
|
* @package PHP_CodeSniffer
|
||||||
|
* @author Thomas Ernest <thomas.ernest@baobaz.com>
|
||||||
|
* @copyright 2006 Thomas Ernest
|
||||||
|
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
|
||||||
|
* @link http://pear.php.net/package/PHP_CodeSniffer
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace CodeIgniter\Sniffs\Operators;
|
||||||
|
|
||||||
|
use PHP_CodeSniffer\Sniffs\Sniff;
|
||||||
|
use PHP_CodeSniffer\Files\File;
|
||||||
|
|
||||||
|
class StrictComparisonOperatorSniff implements Sniff
|
||||||
|
{
|
||||||
|
private static $_replacements = array(
|
||||||
|
T_IS_EQUAL => '===',
|
||||||
|
T_IS_NOT_EQUAL => '!==',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of tokens this test wants to listen for.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
T_IS_EQUAL,
|
||||||
|
T_IS_NOT_EQUAL,
|
||||||
|
);
|
||||||
|
}//end register()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes this test, when one of its tokens is encountered.
|
||||||
|
*
|
||||||
|
* @param File $phpcsFile The current file being scanned.
|
||||||
|
* @param int $stackPtr The position of the current token
|
||||||
|
* in the stack passed in $tokens.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function process(File $phpcsFile, $stackPtr)
|
||||||
|
{
|
||||||
|
$tokens = $phpcsFile->getTokens();
|
||||||
|
|
||||||
|
$operator_token = $tokens[$stackPtr];
|
||||||
|
$operator_string = $operator_token['content'];
|
||||||
|
$operator_code = $operator_token['code'];
|
||||||
|
|
||||||
|
$error_message = '"==" and "!=" are prohibited; use "'
|
||||||
|
. self::$_replacements[$operator_code] . '" instead of "'
|
||||||
|
. $operator_string . '".';
|
||||||
|
$phpcsFile->addError($error_message, $stackPtr, 'NonStrictComparisonUsed');
|
||||||
|
}//end process()
|
||||||
|
|
||||||
|
|
||||||
|
}//end class
|
||||||
|
|
||||||
|
?>
|
465
build/CodeIgniter/Sniffs/Strings/DoubleQuoteUsageSniff.php
Executable file
465
build/CodeIgniter/Sniffs/Strings/DoubleQuoteUsageSniff.php
Executable file
@ -0,0 +1,465 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CodeIgniter_Sniffs_Strings_DoubleQuoteUsageSniff.
|
||||||
|
*
|
||||||
|
* PHP version 5
|
||||||
|
*
|
||||||
|
* @category PHP
|
||||||
|
* @package PHP_CodeSniffer
|
||||||
|
* @author Thomas Ernest <thomas.ernest@baobaz.com>
|
||||||
|
* @copyright 2011 Thomas Ernest
|
||||||
|
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
|
||||||
|
* @link http://pear.php.net/package/PHP_CodeSniffer
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace CodeIgniter\Sniffs\Strings;
|
||||||
|
|
||||||
|
use PHP_CodeSniffer\Sniffs\Sniff;
|
||||||
|
use PHP_CodeSniffer\Files\File;
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CodeIgniter_Sniffs_Strings_DoubleQuoteUsageSniff.
|
||||||
|
*
|
||||||
|
* Ensures that double-quoted strings are used only to parse variables,
|
||||||
|
* to avoid escape characters before single quotes or for chars that need
|
||||||
|
* to be interpreted like \r, \n or \t.
|
||||||
|
* If a double-quoted string contain both single and double quotes
|
||||||
|
* but no variable, then a warning is raised to encourage the use of
|
||||||
|
* single-quoted strings.
|
||||||
|
*
|
||||||
|
* @category PHP
|
||||||
|
* @package PHP_CodeSniffer
|
||||||
|
* @author Thomas Ernest <thomas.ernest@baobaz.com>
|
||||||
|
* @copyright 2011 Thomas Ernest
|
||||||
|
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
|
||||||
|
* @link http://pear.php.net/package/PHP_CodeSniffer
|
||||||
|
*/
|
||||||
|
class VariableUsageSniff implements Sniff
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns an array of tokens this test wants to listen for.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
return array(
|
||||||
|
T_DOUBLE_QUOTED_STRING,
|
||||||
|
T_CONSTANT_ENCAPSED_STRING,
|
||||||
|
);
|
||||||
|
*/
|
||||||
|
return array();
|
||||||
|
}//end register()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes this test, when one of its tokens is encountered.
|
||||||
|
*
|
||||||
|
* @param File $phpcsFile The current file being scanned.
|
||||||
|
* @param int $stackPtr The position of the current token
|
||||||
|
* in the stack passed in $tokens.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function process(File $phpcsFile, $stackPtr)
|
||||||
|
{
|
||||||
|
$tokens = $phpcsFile->getTokens();
|
||||||
|
$string = $tokens[$stackPtr]['content'];
|
||||||
|
// makes sure that it is about a double quote string,
|
||||||
|
// since variables are not parsed out of double quoted string
|
||||||
|
$openDblQtStr = substr($string, 0, 1);
|
||||||
|
if (0 === strcmp($openDblQtStr, '"')) {
|
||||||
|
$this->processDoubleQuotedString($phpcsFile, $stackPtr, $string);
|
||||||
|
} else if (0 === strcmp($openDblQtStr, "'")) {
|
||||||
|
$this->processSingleQuotedString($phpcsFile, $stackPtr, $string);
|
||||||
|
}
|
||||||
|
}//end process()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes this test, when the token encountered is a double-quoted string.
|
||||||
|
*
|
||||||
|
* @param File $phpcsFile The current file being scanned.
|
||||||
|
* @param int $stackPtr The position of the current token
|
||||||
|
* in the stack passed in $tokens.
|
||||||
|
* @param string $dblQtString The double-quoted string content,
|
||||||
|
* i.e. without quotes.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function processDoubleQuotedString (File $phpcsFile, $stackPtr, $dblQtString)
|
||||||
|
{
|
||||||
|
$variableFound = FALSE;
|
||||||
|
$strTokens = token_get_all('<?php '.$dblQtString);
|
||||||
|
$strPtr = 1; // skip php opening tag added by ourselves
|
||||||
|
$requireDblQuotes = FALSE;
|
||||||
|
while ($strPtr < count($strTokens)) {
|
||||||
|
$strToken = $strTokens[$strPtr];
|
||||||
|
if (is_array($strToken)) {
|
||||||
|
if (in_array($strToken[0], array(T_DOLLAR_OPEN_CURLY_BRACES, T_CURLY_OPEN))) {
|
||||||
|
$strPtr++;
|
||||||
|
try {
|
||||||
|
$this->_parseVariable($strTokens, $strPtr);
|
||||||
|
} catch (Exception $err) {
|
||||||
|
$error = 'There is no variable, object nor array between curly braces. Please use the escape char for $ or {.';
|
||||||
|
$phpcsFile->addError($error, $stackPtr, 234);
|
||||||
|
}
|
||||||
|
$variableFound = TRUE;
|
||||||
|
if ('}' !== $strTokens[$strPtr]) {
|
||||||
|
$error = 'There is no matching closing curly brace.';
|
||||||
|
$phpcsFile->addError($error, $stackPtr, 345);
|
||||||
|
}
|
||||||
|
// don't move forward, since it will be done in the main loop
|
||||||
|
// $strPtr++;
|
||||||
|
} else if (T_VARIABLE === $strToken[0]) {
|
||||||
|
$variableFound = TRUE;
|
||||||
|
$error = "Variable {$strToken[1]} in double-quoted strings should be enclosed with curly braces. Please consider {{$strToken[1]}}";
|
||||||
|
$phpcsFile->addError($error, $stackPtr, 456);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$strPtr++;
|
||||||
|
}
|
||||||
|
return $variableFound;
|
||||||
|
}//end processDoubleQuotedString()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes this test, when the token encountered is a single-quoted string.
|
||||||
|
*
|
||||||
|
* @param File $phpcsFile The current file being scanned.
|
||||||
|
* @param int $stackPtr The position of the current token
|
||||||
|
* in the stack passed in $tokens.
|
||||||
|
* @param string $sglQtString The single-quoted string content,
|
||||||
|
* i.e. without quotes.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function processSingleQuotedString (File $phpcsFile, $stackPtr, $sglQtString)
|
||||||
|
{
|
||||||
|
$variableFound = FALSE;
|
||||||
|
$strTokens = token_get_all('<?php '.$sglQtString);
|
||||||
|
$strPtr = 1; // skip php opening tag added by ourselves
|
||||||
|
while ($strPtr < count($strTokens)) {
|
||||||
|
$strToken = $strTokens[$strPtr];
|
||||||
|
if (is_array($strToken)) {
|
||||||
|
if (T_VARIABLE === $strToken[0]) {
|
||||||
|
$error = "Variables like {$strToken[1]} should be in double-quoted strings only.";
|
||||||
|
$phpcsFile->addError($error, $stackPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$strPtr++;
|
||||||
|
}
|
||||||
|
return $variableFound;
|
||||||
|
}//end processSingleQuotedString()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grammar rule to parse the use of a variable. Please notice that it
|
||||||
|
* doesn't manage the leading $.
|
||||||
|
*
|
||||||
|
* _parseVariable ::= <variable>
|
||||||
|
* | <variable>_parseObjectAttribute()
|
||||||
|
* | <variable>_parseArrayIndexes()
|
||||||
|
*
|
||||||
|
* @exception Exception raised if $strTokens starting from $strPtr
|
||||||
|
* doesn't matched the rule.
|
||||||
|
*
|
||||||
|
* @param array $strTokens Tokens to parse.
|
||||||
|
* @param int $strPtr Pointer to the token where parsing starts.
|
||||||
|
*
|
||||||
|
* @return array The attribute name associated to index 'var', an array with
|
||||||
|
* indexes 'obj' and 'attr' or an array with indexes 'arr' and 'idx'.
|
||||||
|
*/
|
||||||
|
private function _parseVariable ($strTokens, &$strPtr)
|
||||||
|
{
|
||||||
|
if ( ! in_array($strTokens[$strPtr][0], array(T_VARIABLE, T_STRING_VARNAME))) {
|
||||||
|
throw new Exception ('Expected variable name.');
|
||||||
|
}
|
||||||
|
$var = $strTokens[$strPtr][1];
|
||||||
|
$strPtr++;
|
||||||
|
$startStrPtr = $strPtr;
|
||||||
|
try {
|
||||||
|
$attr = $this->_parseObjectAttribute($strTokens, $strPtr);
|
||||||
|
return array ('obj' => $var, 'attr' => $attr);
|
||||||
|
} catch (Exception $err) {
|
||||||
|
if ($strPtr !== $startStrPtr) {
|
||||||
|
throw $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$idx = $this->_parseArrayIndexes($strTokens, $strPtr);
|
||||||
|
return array ('arr' => $var, 'idx' => $idx);
|
||||||
|
} catch (Exception $err) {
|
||||||
|
if ($strPtr !== $startStrPtr) {
|
||||||
|
throw $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array ('var' => $var);
|
||||||
|
}//end _parseVariable()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grammar rule to parse the use of an object attribute.
|
||||||
|
*
|
||||||
|
* _parseObjectAttribute ::= -><attribute>
|
||||||
|
* | -><attribute>_parseObjectAttribute()
|
||||||
|
* | -><attribute>_parseArrayIndexes()
|
||||||
|
*
|
||||||
|
* @exception Exception raised if $strTokens starting from $strPtr
|
||||||
|
* doesn't matched the rule.
|
||||||
|
*
|
||||||
|
* @param array $strTokens Tokens to parse.
|
||||||
|
* @param int $strPtr Pointer to the token where parsing starts.
|
||||||
|
*
|
||||||
|
* @return mixed The attribute name as a string, an array with indexes
|
||||||
|
* 'obj' and 'attr' or an array with indexes 'arr' and 'idx'.
|
||||||
|
*/
|
||||||
|
private function _parseObjectAttribute ($strTokens, &$strPtr)
|
||||||
|
{
|
||||||
|
if (T_OBJECT_OPERATOR !== $strTokens[$strPtr][0]) {
|
||||||
|
throw new Exception ('Expected ->.');
|
||||||
|
}
|
||||||
|
$strPtr++;
|
||||||
|
if (T_STRING !== $strTokens[$strPtr][0]) {
|
||||||
|
throw new Exception ('Expected an object attribute.');
|
||||||
|
}
|
||||||
|
$attr = $strTokens[$strPtr][1];
|
||||||
|
$strPtr++;
|
||||||
|
$startStrPtr = $strPtr;
|
||||||
|
try {
|
||||||
|
$sub_attr = $this->_parseObjectAttribute($strTokens, $strPtr);
|
||||||
|
return array ('obj' => $attr, 'attr' => $sub_attr);
|
||||||
|
} catch (Exception $err) {
|
||||||
|
if ($strPtr !== $startStrPtr) {
|
||||||
|
throw $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$idx = $this->_parseArrayIndexes($strTokens, $strPtr);
|
||||||
|
return array ('arr' => $attr, 'idx' => $idx);
|
||||||
|
} catch (Exception $err) {
|
||||||
|
if ($strPtr !== $startStrPtr) {
|
||||||
|
throw $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $attr;
|
||||||
|
}//end _parseObjectAttribute()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grammar rule to parse the use of one or more array indexes.
|
||||||
|
*
|
||||||
|
* _parseArrayIndexes ::= _parseArrayIndex()+
|
||||||
|
*
|
||||||
|
* @exception Exception raised if $strTokens starting from $strPtr
|
||||||
|
* doesn't matched the rule.
|
||||||
|
*
|
||||||
|
* @param array $strTokens Tokens to parse.
|
||||||
|
* @param int $strPtr Pointer to the token where parsing starts.
|
||||||
|
*
|
||||||
|
* @return array Indexes in the same order as in the string.
|
||||||
|
*/
|
||||||
|
private function _parseArrayIndexes ($strTokens, &$strPtr)
|
||||||
|
{
|
||||||
|
$indexes = array($this->_parseArrayIndex($strTokens, $strPtr));
|
||||||
|
try {
|
||||||
|
while (1) {
|
||||||
|
$startStrPtr = $strPtr;
|
||||||
|
$indexes [] = $this->_parseArrayIndex($strTokens, $strPtr);
|
||||||
|
}
|
||||||
|
} catch (Exception $err) {
|
||||||
|
if (0 !== ($strPtr - $startStrPtr)) {
|
||||||
|
throw $err;
|
||||||
|
}
|
||||||
|
return $indexes;
|
||||||
|
}
|
||||||
|
}//end _parseArrayIndexes()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grammar rule to parse the use of array index.
|
||||||
|
*
|
||||||
|
* _parseArrayIndex ::= [<index>]
|
||||||
|
*
|
||||||
|
* @exception Exception raised if $strTokens starting from $strPtr
|
||||||
|
* doesn't matched the rule.
|
||||||
|
*
|
||||||
|
* @param array $strTokens Tokens to parse.
|
||||||
|
* @param int $strPtr Pointer to the token where parsing starts.
|
||||||
|
*
|
||||||
|
* @return string Index between the 2 square brackets
|
||||||
|
*/
|
||||||
|
private function _parseArrayIndex ($strTokens, &$strPtr)
|
||||||
|
{
|
||||||
|
if ('[' !== $strTokens[$strPtr]) {
|
||||||
|
throw new Exception ('Expected [.');
|
||||||
|
}
|
||||||
|
$strPtr++;
|
||||||
|
if (! in_array($strTokens[$strPtr][0], array(T_CONSTANT_ENCAPSED_STRING, T_LNUMBER))) {
|
||||||
|
throw new Exception ('Expected an array index.');
|
||||||
|
}
|
||||||
|
$index = $strTokens[$strPtr][1];
|
||||||
|
$strPtr++;
|
||||||
|
if (']' !== $strTokens[$strPtr]) {
|
||||||
|
throw new Exception ('Expected ].');
|
||||||
|
}
|
||||||
|
$strPtr++;
|
||||||
|
return $index;
|
||||||
|
}//end _parseArrayIndex()
|
||||||
|
|
||||||
|
}//end class
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CodeIgniter_Sniffs_Strings_VariableUsageSniff.
|
||||||
|
*
|
||||||
|
* Ensures that variables parsed in double-quoted strings are enclosed with
|
||||||
|
* braces to prevent greedy token parsing.
|
||||||
|
* Single-quoted strings don't parse variables, so there is no risk of greedy
|
||||||
|
* token parsing.
|
||||||
|
*
|
||||||
|
* @category PHP
|
||||||
|
* @package PHP_CodeSniffer
|
||||||
|
* @author Thomas Ernest <thomas.ernest@baobaz.com>
|
||||||
|
* @copyright 2011 Thomas Ernest
|
||||||
|
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
|
||||||
|
* @link http://pear.php.net/package/PHP_CodeSniffer
|
||||||
|
*/
|
||||||
|
class DoubleQuoteUsageSniff extends VariableUsageSniff
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns an array of tokens this test wants to listen for.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
T_DOUBLE_QUOTED_STRING,
|
||||||
|
T_CONSTANT_ENCAPSED_STRING,
|
||||||
|
);
|
||||||
|
}//end register()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes this test, when one of its tokens is encountered.
|
||||||
|
*
|
||||||
|
* @param File $phpcsFile The current file being scanned.
|
||||||
|
* @param int $stackPtr The position of the current token
|
||||||
|
* in the stack passed in $tokens.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function process(File $phpcsFile, $stackPtr)
|
||||||
|
{
|
||||||
|
// no variable are in the string from here
|
||||||
|
$tokens = $phpcsFile->getTokens();
|
||||||
|
$qtString = $tokens[$stackPtr]['content'];
|
||||||
|
// makes sure that it is about a double quote string,
|
||||||
|
// since variables are not parsed out of double quoted string
|
||||||
|
$open_qt_str = substr($qtString, 0, 1);
|
||||||
|
|
||||||
|
// clean the enclosing quotes
|
||||||
|
$qtString = substr($qtString, 1, strlen($qtString) - 1 - 1);
|
||||||
|
|
||||||
|
if (0 === strcmp($open_qt_str, '"')) {
|
||||||
|
$this->processDoubleQuotedString($phpcsFile, $stackPtr, $qtString);
|
||||||
|
} else if (0 === strcmp($open_qt_str, "'")) {
|
||||||
|
$this->processSingleQuotedString($phpcsFile, $stackPtr, $qtString);
|
||||||
|
}
|
||||||
|
}//end process()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes this test, when the token encountered is a double-quoted string.
|
||||||
|
*
|
||||||
|
* @param File $phpcsFile The current file being scanned.
|
||||||
|
* @param int $stackPtr The position of the current token
|
||||||
|
* in the stack passed in $tokens.
|
||||||
|
* @param string $qtString The double-quoted string content,
|
||||||
|
* i.e. without quotes.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function processDoubleQuotedString (File $phpcsFile, $stackPtr, $qtString)
|
||||||
|
{
|
||||||
|
// so there should be at least a single quote or a special char
|
||||||
|
// if there are the 2 kinds of quote and no special char, then add a warning
|
||||||
|
$has_variable = parent::processDoubleQuotedString($phpcsFile, $stackPtr, '"'.$qtString.'"');
|
||||||
|
$has_specific_sequence = $this->_hasSpecificSequence($qtString);
|
||||||
|
$dbl_qt_at = strpos($qtString, '"');
|
||||||
|
$smpl_qt_at = strpos($qtString, "'");
|
||||||
|
if (false === $has_variable && false === $has_specific_sequence
|
||||||
|
&& false === $smpl_qt_at
|
||||||
|
) {
|
||||||
|
$error = 'Single-quoted strings should be used unless it contains variables, special chars like \n or single quotes.';
|
||||||
|
$phpcsFile->addError($error, $stackPtr, 111);
|
||||||
|
} else if (false !== $smpl_qt_at && false !== $dbl_qt_at
|
||||||
|
&& false === $has_variable && false === $has_specific_sequence
|
||||||
|
) {
|
||||||
|
$warning = 'It is encouraged to use a single-quoted string, since it doesn\'t contain any variable nor special char though it mixes single and double quotes.';
|
||||||
|
$phpcsFile->addWarning($warning, $stackPtr, 222);
|
||||||
|
}
|
||||||
|
}//end processDoubleQuotedString()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes this test, when the token encountered is a single-quoted string.
|
||||||
|
*
|
||||||
|
* @param File $phpcsFile The current file being scanned.
|
||||||
|
* @param int $stackPtr The position of the current token
|
||||||
|
* in the stack passed in $tokens.
|
||||||
|
* @param string $qtString The single-quoted string content,
|
||||||
|
* i.e. without quotes.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function processSingleQuotedString (File $phpcsFile, $stackPtr, $qtString)
|
||||||
|
{
|
||||||
|
// if there is single quotes without additional double quotes,
|
||||||
|
// then user is allowed to use double quote to avoid having to
|
||||||
|
// escape single quotes. Don't add the warning, if an error was
|
||||||
|
// already added, because a variable was found in a single-quoted
|
||||||
|
// string.
|
||||||
|
$has_variable = parent::processSingleQuotedString($phpcsFile, $stackPtr, "'".$qtString."'");
|
||||||
|
$dbl_qt_at = strpos($qtString, '"');
|
||||||
|
$smpl_qt_at = strpos($qtString, "'");
|
||||||
|
if (false === $has_variable && false !== $smpl_qt_at && false === $dbl_qt_at) {
|
||||||
|
$warning = 'You may also use double-quoted strings if the string contains single quotes, so you do not have to use escape characters.';
|
||||||
|
$phpcsFile->addWarning($warning, $stackPtr, 333);
|
||||||
|
}
|
||||||
|
}//end processSingleQuotedString()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return TRUE, if a sequence of chars that is parsed in a specific way
|
||||||
|
* in double-quoted strings is found, FALSE otherwise.
|
||||||
|
*
|
||||||
|
* @param string $string String in which sequence of special chars will
|
||||||
|
* be researched.
|
||||||
|
*
|
||||||
|
* @return TRUE, if a sequence of chars that is parsed in a specific way
|
||||||
|
* in double-quoted strings is found, FALSE otherwise.
|
||||||
|
*
|
||||||
|
* @link http://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.double
|
||||||
|
*/
|
||||||
|
private function _hasSpecificSequence($string)
|
||||||
|
{
|
||||||
|
$hasSpecificSequence = FALSE;
|
||||||
|
$specialMeaningStrs = array('\n', '\r', '\t', '\v', '\f');
|
||||||
|
foreach ($specialMeaningStrs as $splStr) {
|
||||||
|
if (FALSE !== strpos($string, $splStr)) {
|
||||||
|
$hasSpecificSequence = TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$specialMeaningPtrns = array('\[0-7]{1,3}', '\x[0-9A-Fa-f]{1,2}');
|
||||||
|
foreach ($specialMeaningPtrns as $splPtrn) {
|
||||||
|
if (1 === preg_match("/{$splPtrn}/", $string)) {
|
||||||
|
$hasSpecificSequence = TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $hasSpecificSequence;
|
||||||
|
}//end _hasSpecificSequence()
|
||||||
|
|
||||||
|
}//end class
|
||||||
|
|
||||||
|
?>
|
87
build/CodeIgniter/Sniffs/WhiteSpace/DisallowSpaceIndentSniff.php
Executable file
87
build/CodeIgniter/Sniffs/WhiteSpace/DisallowSpaceIndentSniff.php
Executable file
@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* CodeIgniter_Sniffs_WhiteSpace_DisallowSpaceIndentSniff.
|
||||||
|
*
|
||||||
|
* PHP version 5
|
||||||
|
*
|
||||||
|
* @category PHP
|
||||||
|
* @package PHP_CodeSniffer
|
||||||
|
* @author Thomas Ernest <thomas.ernest@gmail.com>
|
||||||
|
* @copyright 2011 Thomas ERNEST
|
||||||
|
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
|
||||||
|
* @link http://pear.php.net/package/PHP_CodeSniffer
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CodeIgniter_Sniffs_WhiteSpace_DisallowSpaceIndentSniff.
|
||||||
|
*
|
||||||
|
* Ensures the use of tabs for indentation.
|
||||||
|
*
|
||||||
|
* @category PHP
|
||||||
|
* @package PHP_CodeSniffer
|
||||||
|
* @author Thomas Ernest <thomas.ernest@gmail.com>
|
||||||
|
* @copyright 2011 Thomas ERNEST
|
||||||
|
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
|
||||||
|
* @link http://pear.php.net/package/PHP_CodeSniffer
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace CodeIgniter\Sniffs\WhiteSpace;
|
||||||
|
|
||||||
|
use PHP_CodeSniffer\Sniffs\Sniff;
|
||||||
|
use PHP_CodeSniffer\Files\File;
|
||||||
|
|
||||||
|
class DisallowSpaceIndentSniff implements Sniff
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of tokenizers this sniff supports.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public $supportedTokenizers = array(
|
||||||
|
'PHP',
|
||||||
|
'JS',
|
||||||
|
'CSS',
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of tokens this test wants to listen for.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
return array(T_WHITESPACE);
|
||||||
|
}//end register()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes this test, when one of its tokens is encountered.
|
||||||
|
*
|
||||||
|
* @param File $phpcsFile All the tokens found in the document.
|
||||||
|
* @param int $stackPtr The position of the current token
|
||||||
|
* in the stack passed in $tokens.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function process(File $phpcsFile, $stackPtr)
|
||||||
|
{
|
||||||
|
$tokens = $phpcsFile->getTokens();
|
||||||
|
|
||||||
|
// Make sure this is whitespace used for indentation.
|
||||||
|
$line = $tokens[$stackPtr]['line'];
|
||||||
|
if ($stackPtr > 0 && $tokens[($stackPtr - 1)]['line'] === $line) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($tokens[$stackPtr]['content'], " ") !== false) {
|
||||||
|
$error = 'Tabs must be used to indent lines; spaces are not allowed for code indentation';
|
||||||
|
$phpcsFile->addError($error, $stackPtr, 'SpacesUsedForIndentation');
|
||||||
|
}
|
||||||
|
}//end process()
|
||||||
|
|
||||||
|
|
||||||
|
}//end class
|
||||||
|
|
||||||
|
?>
|
95
build/CodeIgniter/Sniffs/WhiteSpace/DisallowWitheSpaceAroundPhpTagsSniff.php
Executable file
95
build/CodeIgniter/Sniffs/WhiteSpace/DisallowWitheSpaceAroundPhpTagsSniff.php
Executable file
@ -0,0 +1,95 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* CodeIgniter_Sniffs_WhiteSpace_DisallowWitheSpaceAroundPhpTagsSniff.
|
||||||
|
*
|
||||||
|
* PHP version 5
|
||||||
|
*
|
||||||
|
* @category PHP
|
||||||
|
* @package PHP_CodeSniffer
|
||||||
|
* @author Thomas Ernest <thomas.ernest@baobaz.com>
|
||||||
|
* @copyright 2006 Thomas Ernest
|
||||||
|
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
|
||||||
|
* @link http://pear.php.net/package/PHP_CodeSniffer
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CodeIgniter_Sniffs_WhiteSpace_DisallowWitheSpaceAroundPhpTagsSniff.
|
||||||
|
*
|
||||||
|
* Ensures that no whitespace precedes the opening PHP tag
|
||||||
|
* or follows the closing PHP tag.
|
||||||
|
*
|
||||||
|
* @category PHP
|
||||||
|
* @package PHP_CodeSniffer
|
||||||
|
* @author Thomas Ernest <thomas.ernest@baobaz.com>
|
||||||
|
* @copyright 2006 Thomas Ernest
|
||||||
|
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
|
||||||
|
* @link http://pear.php.net/package/PHP_CodeSniffer
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace CodeIgniter\Sniffs\WhiteSpace;
|
||||||
|
|
||||||
|
use PHP_CodeSniffer\Sniffs\Sniff;
|
||||||
|
use PHP_CodeSniffer\Files\File;
|
||||||
|
|
||||||
|
class DisallowWitheSpaceAroundPhpTagsSniff implements Sniff
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of tokens this test wants to listen for.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
T_OPEN_TAG,
|
||||||
|
T_CLOSE_TAG
|
||||||
|
);
|
||||||
|
|
||||||
|
}//end register()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes this test, when one of its tokens is encountered.
|
||||||
|
*
|
||||||
|
* @param File $phpcsFile The current file being scanned.
|
||||||
|
* @param int $stackPtr The position of the current token
|
||||||
|
* in the stack passed in $tokens.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function process(File $phpcsFile, $stackPtr)
|
||||||
|
{
|
||||||
|
$tokens = $phpcsFile->getTokens();
|
||||||
|
|
||||||
|
$php_tag_token = $tokens[$stackPtr];
|
||||||
|
$php_tag_code = $php_tag_token['code'];
|
||||||
|
|
||||||
|
if (T_OPEN_TAG === $php_tag_code) {
|
||||||
|
// opening php tag should be the first token.
|
||||||
|
// any whitespace beofre an opening php tag is tokenized
|
||||||
|
// as T_INLINE_HTML, so no need to check the content of the token.
|
||||||
|
$isFirst = 0 === $stackPtr;
|
||||||
|
if ( ! $isFirst) {
|
||||||
|
$error = 'Any char before the opening PHP tag is prohibited. Please remove newline or indentation before the opening PHP tag.';
|
||||||
|
$phpcsFile->addError($error, $stackPtr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if (T_CLOSE_TAG === $php_tag_code)
|
||||||
|
// closing php tag should be the last token
|
||||||
|
// and it must not contain any whitespace.
|
||||||
|
$php_tag_string = $php_tag_token['content'];
|
||||||
|
$isLast = count($tokens) - 1 === $stackPtr;
|
||||||
|
// both of the two closing php tags contains 2 chars exactly.
|
||||||
|
$containsEndTagOnly = strlen($php_tag_string) > 2;
|
||||||
|
if ( ! $isLast || ! $containsEndTagOnly ) {
|
||||||
|
$error = 'Any char after the closing PHP tag is prohibited. Please removes newline or spaces after the closing PHP tag.';
|
||||||
|
$phpcsFile->addError($error, $stackPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}//end process()
|
||||||
|
|
||||||
|
|
||||||
|
}//end class
|
||||||
|
|
||||||
|
?>
|
82
build/CodeIgniter/Sniffs/WhiteSpace/ElseOnNewLineSniff.php
Normal file
82
build/CodeIgniter/Sniffs/WhiteSpace/ElseOnNewLineSniff.php
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* CodeIgniter_Sniffs_WhiteSpace_ElseOnNewLineSniff.
|
||||||
|
*
|
||||||
|
* PHP version 5
|
||||||
|
*
|
||||||
|
* @category PHP
|
||||||
|
* @package PHP_CodeSniffer
|
||||||
|
* @author Thomas Ernest <thomas.ernest@baobaz.com>
|
||||||
|
* @copyright 2006 Thomas Ernest
|
||||||
|
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
|
||||||
|
* @link http://pear.php.net/package/PHP_CodeSniffer
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CodeIgniter_Sniffs_WhiteSpace_ElseOnNewLineSniff.
|
||||||
|
*
|
||||||
|
* Ensures that control structures else and elseif stand on new lines.
|
||||||
|
*
|
||||||
|
* @category PHP
|
||||||
|
* @package PHP_CodeSniffer
|
||||||
|
* @author Thomas Ernest <thomas.ernest@baobaz.com>
|
||||||
|
* @copyright 2006 Thomas Ernest
|
||||||
|
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
|
||||||
|
* @link http://pear.php.net/package/PHP_CodeSniffer
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace CodeIgniter\Sniffs\WhiteSpace;
|
||||||
|
|
||||||
|
use PHP_CodeSniffer\Sniffs\Sniff;
|
||||||
|
use PHP_CodeSniffer\Files\File;
|
||||||
|
|
||||||
|
class ElseOnNewLineSniff implements Sniff
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of tokens this test wants to listen for.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
T_ELSE,
|
||||||
|
T_ELSEIF,
|
||||||
|
);
|
||||||
|
}//end register()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes this test, when one of its tokens is encountered.
|
||||||
|
*
|
||||||
|
* @param File $phpcsFile The current file being scanned.
|
||||||
|
* @param int $stackPtr The position of the current token
|
||||||
|
* in the stack passed in $tokens.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function process(File $phpcsFile, $stackPtr)
|
||||||
|
{
|
||||||
|
$tokens = $phpcsFile->getTokens();
|
||||||
|
|
||||||
|
$else_token = $tokens[$stackPtr];
|
||||||
|
$previous_non_blank_token_ptr = $phpcsFile->findPrevious(array(T_WHITESPACE), $stackPtr - 1, null, true);
|
||||||
|
|
||||||
|
if (false === $previous_non_blank_token_ptr) {
|
||||||
|
// else is no preceded with any symbol, but it is not the responsibility of this sniff.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$previous_non_blank_token = $tokens[$previous_non_blank_token_ptr];
|
||||||
|
if ($previous_non_blank_token['line'] === $else_token['line']) {
|
||||||
|
$error = '"' . $else_token['content'] . '" should be on a new line.';
|
||||||
|
$phpcsFile->addError($error, $stackPtr, 123423);
|
||||||
|
}
|
||||||
|
|
||||||
|
}//end process()
|
||||||
|
|
||||||
|
|
||||||
|
}//end class
|
||||||
|
|
||||||
|
?>
|
75
build/CodeIgniter/Sniffs/WhiteSpace/LogicalNotSpacingSniff.php
Executable file
75
build/CodeIgniter/Sniffs/WhiteSpace/LogicalNotSpacingSniff.php
Executable file
@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* CodeIgniter_Sniffs_WhiteSpace_LogicalNotSpacingSniff.
|
||||||
|
*
|
||||||
|
* PHP version 5
|
||||||
|
*
|
||||||
|
* @category PHP
|
||||||
|
* @package PHP_CodeSniffer
|
||||||
|
* @author Thomas Ernest <thomas.ernest@baobaz.com>
|
||||||
|
* @copyright 2006 Thomas Ernest
|
||||||
|
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
|
||||||
|
* @link http://pear.php.net/package/PHP_CodeSniffer
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CodeIgniter_Sniffs_WhiteSpace_LogicalNotSpacingSniff.
|
||||||
|
*
|
||||||
|
* Ensures that at exactly a space precedes and follows the logical operator !.
|
||||||
|
*
|
||||||
|
* @category PHP
|
||||||
|
* @package PHP_CodeSniffer
|
||||||
|
* @author Thomas Ernest <thomas.ernest@baobaz.com>
|
||||||
|
* @copyright 2006 Thomas Ernest
|
||||||
|
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
|
||||||
|
* @link http://pear.php.net/package/PHP_CodeSniffer
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace CodeIgniter\Sniffs\WhiteSpace;
|
||||||
|
|
||||||
|
use PHP_CodeSniffer\Sniffs\Sniff;
|
||||||
|
use PHP_CodeSniffer\Files\File;
|
||||||
|
|
||||||
|
class LogicalNotSpacingSniff implements Sniff
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of tokens this test wants to listen for.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
T_BOOLEAN_NOT,
|
||||||
|
);
|
||||||
|
}//end register()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes this test, when one of its tokens is encountered.
|
||||||
|
*
|
||||||
|
* @param File $phpcsFile The current file being scanned.
|
||||||
|
* @param int $stackPtr The position of the current token
|
||||||
|
* in the stack passed in $tokens.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function process(File $phpcsFile, $stackPtr)
|
||||||
|
{
|
||||||
|
$tokens = $phpcsFile->getTokens();
|
||||||
|
|
||||||
|
$operator_token = $tokens[$stackPtr];
|
||||||
|
|
||||||
|
$previous_token = $tokens[$stackPtr - 1];
|
||||||
|
$next_token = $tokens[$stackPtr + 1];
|
||||||
|
if (T_WHITESPACE !== $previous_token['code'] || T_WHITESPACE !== $next_token['code']) {
|
||||||
|
$error = 'Logical operator ! should always be preceded and followed with a whitespace.';
|
||||||
|
$phpcsFile->addError($error, $stackPtr, 'badNot');
|
||||||
|
}
|
||||||
|
}//end process()
|
||||||
|
|
||||||
|
|
||||||
|
}//end class
|
||||||
|
|
||||||
|
?>
|
@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* CodeIgniter_Sniffs_Files_AbstractClosingCommentSniff.
|
||||||
|
*
|
||||||
|
* PHP version 5
|
||||||
|
*
|
||||||
|
* @category PHP
|
||||||
|
* @package PHP_CodeSniffer
|
||||||
|
* @author Thomas Ernest <thomas.ernest@baobaz.com>
|
||||||
|
* @copyright 2006 Thomas Ernest
|
||||||
|
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
|
||||||
|
* @link http://pear.php.net/package/PHP_CodeSniffer
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CodeIgniter_Sniffs_Files_AbstractClosingCommentSniff.
|
||||||
|
*
|
||||||
|
* Defines some methods used by
|
||||||
|
* CodeIgniter_Sniffs_Files_ClosingFileCommentSniff
|
||||||
|
* and CodeIgniter_Sniffs_Files_ClosingLocationCommentSniff.
|
||||||
|
*
|
||||||
|
* @category PHP
|
||||||
|
* @package PHP_CodeSniffer
|
||||||
|
* @author Thomas Ernest <thomas.ernest@baobaz.com>
|
||||||
|
* @copyright 2006 Thomas Ernest
|
||||||
|
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
|
||||||
|
* @link http://pear.php.net/package/PHP_CodeSniffer
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace CodeIgniter\Sniffs\Files;
|
||||||
|
|
||||||
|
use PHP_CodeSniffer\Sniffs\Sniff;
|
||||||
|
use PHP_CodeSniffer\Files\File;
|
||||||
|
|
||||||
|
class AbstractClosingCommentSniff implements Sniff
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* As an abstract class, this sniff is not associated to any token.
|
||||||
|
*/
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* As an abstract class, this sniff is not dedicated to process a token.
|
||||||
|
*/
|
||||||
|
public function process(File $phpcsFile, $stackPtr)
|
||||||
|
{
|
||||||
|
$error = __CLASS__.'::'.__METHOD__.' is abstract. Please develop this method in a child class.';
|
||||||
|
throw new PHP_CodeSniffer_Exception($error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the comment without its delimiter(s) as well as leading
|
||||||
|
* and traling whitespaces.
|
||||||
|
*
|
||||||
|
* It removes the first #, the two first / (i.e. //) or the first /*
|
||||||
|
* and last \*\/. If a comment starts with /**, then the last * will remain
|
||||||
|
* as well as whitespaces between this star and the comment content.
|
||||||
|
*
|
||||||
|
* @param string $comment Comment containing either comment delimiter(s) and
|
||||||
|
* trailing or leading whitspaces to clean.
|
||||||
|
*
|
||||||
|
* @return string Comment without comment delimiter(s) and whitespaces.
|
||||||
|
*/
|
||||||
|
protected static function _getCommentContent ($comment)
|
||||||
|
{
|
||||||
|
if (self::_stringStartsWith($comment, '#')) {
|
||||||
|
$comment = substr($comment, 1);
|
||||||
|
} else if (self::_stringStartsWith($comment, '//')) {
|
||||||
|
$comment = substr($comment, 2);
|
||||||
|
} else if (self::_stringStartsWith($comment, '/*')) {
|
||||||
|
$comment = substr($comment, 2, strlen($comment) - 2 - 2);
|
||||||
|
}
|
||||||
|
$comment = trim($comment);
|
||||||
|
return $comment;
|
||||||
|
}//_getCommentContent()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binary safe string comparison between $needle and
|
||||||
|
* the beginning of $haystack. Returns true if $haystack starts with
|
||||||
|
* $needle, false otherwise.
|
||||||
|
*
|
||||||
|
* @param string $haystack The string to search in.
|
||||||
|
* @param string $needle The string to search for.
|
||||||
|
*
|
||||||
|
* @return bool true if $haystack starts with $needle, false otherwise.
|
||||||
|
*/
|
||||||
|
protected static function _stringStartsWith ($haystack, $needle)
|
||||||
|
{
|
||||||
|
$startsWith = false;
|
||||||
|
if (strlen($needle) <= strlen($haystack)) {
|
||||||
|
$haystackBeginning = substr($haystack, 0, strlen($needle));
|
||||||
|
if (0 === strcmp($haystackBeginning, $needle)) {
|
||||||
|
$startsWith = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $startsWith;
|
||||||
|
}//_stringStartsWith()
|
||||||
|
}//end class
|
||||||
|
|
||||||
|
?>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user