mirror of
https://github.com/ad-aures/castopod.git
synced 2026-04-02 14:29:11 +02:00
Compare commits
477 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc041702dd |
||
|
|
c13bbdffdf |
||
|
|
aad17646f1 |
||
|
|
385a3cb13a |
||
|
|
ed57e13b40 |
||
|
|
6b302ad8bf |
||
|
|
cc86ce030f |
||
|
|
3943441683 |
||
|
|
b747967a18 |
||
|
|
a585362827 |
||
|
|
abf214757c |
||
|
|
f01de13637 |
||
|
|
77826552f1 |
||
|
|
e5fb676cb6 |
||
|
|
49a43d08cc | ||
|
|
89d0fe4a7e | ||
|
|
950d42c838 | ||
|
|
6be7a1f4d7 | ||
|
|
4c46c15e39 | ||
|
|
bbfaa1bfc3 | ||
|
|
85503ee282 | ||
|
|
265cbbac09 | ||
|
|
e291b6239c | ||
|
|
40f671c8b6 | ||
|
|
3d0db5c64a | ||
|
|
b5a403b990 | ||
|
|
835f099f2e | ||
|
|
9dffc8d5f1 | ||
|
|
8ec42c33ff | ||
|
|
346c00e7b5 | ||
|
|
96b2df15b0 | ||
|
|
61d6a6b60f | ||
|
|
00870ceff2 | ||
|
|
31fee52208 | ||
|
|
567d5e01a3 | ||
|
|
94cea0ce91 | ||
|
|
0e4e301b81 | ||
|
|
f8fb25f52d | ||
|
|
93b4741333 | ||
|
|
5578104207 | ||
|
|
5dce8cb949 | ||
|
|
1e6477db67 | ||
|
|
0265775177 | ||
|
|
c9fabe8888 | ||
|
|
6934c8aa8f | ||
|
|
70c97971fc | ||
|
|
9f74cca342 | ||
|
|
f295e9aa4c | ||
|
|
fc2e7a0d83 | ||
|
|
f981937645 | ||
|
|
f288a750f5 | ||
|
|
7e8f0003d1 | ||
|
|
888d610c2d | ||
|
|
775b302f7c | ||
|
|
09256b4eb7 | ||
|
|
0736050d1a | ||
|
|
a90cdfdcdb | ||
|
|
8402cc29d2 | ||
|
|
08c7df2a5d | ||
|
|
34be5bccab | ||
|
|
d3a98db6d0 | ||
|
|
00bd4c02ee | ||
|
|
85704bfbe0 | ||
|
|
8cf9c6dc83 | ||
|
|
b869acb3a9 | ||
|
|
11ccd0ebe7 | ||
|
|
f50098ec89 | ||
|
|
77e55835c0 | ||
|
|
fa6967e65c | ||
|
|
ea720e01ba | ||
|
|
cbf739e95c | ||
|
|
63f93f585b | ||
|
|
65d74f14e6 | ||
|
|
1667f5b202 | ||
|
|
1d7583d738 | ||
|
|
d30c49cdff | ||
|
|
a83afb0004 | ||
|
|
732d42923d | ||
|
|
63c763f941 | ||
|
|
a68959c906 | ||
|
|
74f9325946 | ||
|
|
2b1bbf3430 | ||
|
|
37ee6d35b4 | ||
|
|
3cd30205d9 | ||
|
|
53232d3b61 | ||
|
|
7405f8897d | ||
|
|
fc9ea7597e | ||
|
|
fee7905935 | ||
|
|
1a439083a2 | ||
|
|
0ba0a25b11 | ||
|
|
c21864ee25 | ||
|
|
1c5fe1fea6 | ||
|
|
a8c81b3fa1 | ||
|
|
322836254e | ||
|
|
e9c04548de | ||
|
|
5d35524875 | ||
|
|
7a8cd4c730 | ||
|
|
5339669ea6 | ||
|
|
0eeedb9dc6 | ||
|
|
827522643e | ||
|
|
e417d45b14 | ||
|
|
cc6495dc7c | ||
|
|
8f8c61eaae | ||
|
|
91dc8c8325 | ||
|
|
3a900bbab6 | ||
|
|
2035c39fd1 | ||
|
|
d7b9730d7e | ||
|
|
b5bd2db28f | ||
|
|
e2a90def88 | ||
|
|
014facd5a1 | ||
|
|
80d2c48ee2 | ||
|
|
8ec79097bb | ||
|
|
45ac2a4be9 | ||
|
|
b62b483ad9 | ||
|
|
6f833fc76a | ||
|
|
82714e7155 | ||
|
|
dfb7888aeb | ||
|
|
e6bfdfc390 | ||
|
|
1510e36c0a | ||
|
|
b5eddf351f | ||
|
|
896f00661f | ||
|
|
9a80de4068 | ||
|
|
89ac92fb41 | ||
|
|
3d8aedf9c3 | ||
|
|
e80a33bf2a | ||
|
|
27d2a1b0ff | ||
|
|
587938d2bf | ||
|
|
7253e13ac2 | ||
|
|
3fd5efc795 | ||
|
|
56612f0c76 | ||
|
|
eb7ad2f7e1 | ||
|
|
281eefc6a3 | ||
|
|
083a766e4e | ||
|
|
fe676590f2 | ||
|
|
2ca9418138 | ||
|
|
b345c7ecd2 | ||
|
|
6dc98b329b | ||
|
|
d88b041d2c | ||
|
|
70f56a73ff | ||
|
|
bb628f355f | ||
|
|
7a6d9df6db | ||
|
|
fc4f982556 | ||
|
|
51b064d67a | ||
|
|
fe73e9fae9 | ||
|
|
d4a36f811b | ||
|
|
303a900f66 | ||
|
|
57e459e187 | ||
|
|
a67f4acb3d | ||
|
|
b554561c01 | ||
|
|
30a56546d3 | ||
|
|
499005d798 | ||
|
|
4d141fceae | ||
|
|
88851b0226 | ||
|
|
d046ecc52f | ||
|
|
80fdd9cfb4 | ||
|
|
004f804045 | ||
|
|
a5aef2a63e | ||
|
|
13db54ccce | ||
|
|
9d7d11cefa | ||
|
|
bec4f93837 | ||
|
|
523b2c610e | ||
|
|
bd205d56ca | ||
|
|
c24850bda9 | ||
|
|
656627050a | ||
|
|
cdeb8bf26e | ||
|
|
6289c42b11 | ||
|
|
37f2d2d21a | ||
|
|
83b6571a81 | ||
|
|
797516a2ec | ||
|
|
1e208c55ca | ||
|
|
efa5acd415 | ||
|
|
3187b0144f | ||
|
|
a343de4cf6 | ||
|
|
dfd66beebf | ||
|
|
6c3dee2131 | ||
|
|
cec78155f9 | ||
|
|
867dfad9ae | ||
|
|
5fd0980ff7 | ||
|
|
4af40b5a71 | ||
|
|
80c114287f | ||
|
|
419bb04716 | ||
|
|
d0a94dd2cb | ||
|
|
87cc437e1e | ||
|
|
98c6658840 | ||
|
|
d580369235 | ||
|
|
94ceba6081 | ||
|
|
7071b4b6f4 | ||
|
|
d02ac93867 | ||
|
|
630e788f0e | ||
|
|
bc4f93d2b7 | ||
|
|
6a77a9d2f2 | ||
|
|
de099ac643 | ||
|
|
76e1251ece | ||
|
|
67c037c9eb | ||
|
|
2accb0f765 | ||
|
|
3cb5ffd25b | ||
|
|
5f3752b443 | ||
|
|
a12327da8e | ||
|
|
f303171fc5 | ||
|
|
95d0861659 | ||
|
|
6cbfec0d7d | ||
|
|
28a31ca03b | ||
|
|
164f4d3be7 | ||
|
|
9899870e28 | ||
|
|
2c3cb85a35 | ||
|
|
2ed511f8a0 | ||
|
|
19799f496d | ||
|
|
f7f9bafc3e | ||
|
|
0bd7ddea58 | ||
|
|
68a599fee0 | ||
|
|
6f8217e1a6 | ||
|
|
222e02a2af | ||
|
|
9178c3f3af | ||
|
|
c1ec98c956 | ||
|
|
30a3473863 | ||
|
|
ac5336fbc5 | ||
|
|
1001ec6b76 | ||
|
|
b61a32c8a9 | ||
|
|
cc85637e18 | ||
|
|
af6fe1e4ef | ||
|
|
8cd7886676 | ||
|
|
9264a2d74c | ||
|
|
98ed36d7a4 | ||
|
|
694328f108 | ||
|
|
f5189055ff | ||
|
|
aeaee8ae64 | ||
|
|
119742cdbb | ||
|
|
de8b84c874 | ||
|
|
34a2ebfd65 | ||
|
|
2f1a5eb294 | ||
|
|
1861d67971 | ||
|
|
18e2633a49 | ||
|
|
61cf8fa3e2 | ||
|
|
dff85168b3 | ||
|
|
02d4ba69ac | ||
|
|
97d793f55e | ||
|
|
3d5fc14d5e | ||
|
|
f4ffa30ec4 | ||
|
|
02132dc466 | ||
|
|
642981fd35 | ||
|
|
6a7ef0109a | ||
|
|
2d52fa1046 | ||
|
|
b047a3c670 | ||
|
|
5f8d413b84 | ||
|
|
f2d5b272ac | ||
|
|
4ca7f9ccae | ||
|
|
04b2d8bafa | ||
|
|
5a834c0f89 | ||
|
|
fcad25a551 | ||
|
|
ffa530e187 | ||
|
|
2dd9cc9ef5 | ||
|
|
cc19c24668 | ||
|
|
4ecb42f7c8 | ||
|
|
3189f12206 | ||
|
|
18fcb5ba3e | ||
|
|
3c4df01d18 | ||
|
|
35142d8e56 | ||
|
|
27a04bd0df | ||
|
|
82013c9cde | ||
|
|
daa11eb9c1 | ||
|
|
d1b35312a4 | ||
|
|
1c96a6f5da | ||
|
|
b63e953ca8 | ||
|
|
ba5324ea19 | ||
|
|
d100fe0999 | ||
|
|
2c07070b2c | ||
|
|
ff0e681763 | ||
|
|
3c357183ca | ||
|
|
77c2d08b6e | ||
|
|
ae57601c83 | ||
|
|
7ff1dbe903 | ||
|
|
072b3ff61d | ||
|
|
b4f1b916bf | ||
|
|
ef04ce5c41 | ||
|
|
981277ae14 | ||
|
|
4ccb363a3d | ||
|
|
c6e8000bab | ||
|
|
d68595932a | ||
|
|
23842df03a | ||
|
|
8dfdaf3215 | ||
|
|
f1fe1b4764 | ||
|
|
6be38e9fda | ||
|
|
1eb680d617 | ||
|
|
b719be10c0 | ||
|
|
d10c4fd753 | ||
|
|
7d21b3509e | ||
|
|
7a1eea58d3 | ||
|
|
11aa3586a0 | ||
|
|
754e7a6b4b | ||
|
|
9346e787bd | ||
|
|
0775add678 | ||
|
|
26a714d9c2 | ||
|
|
6a9d14d24e | ||
|
|
d69b4e4857 | ||
|
|
73a5b68087 | ||
|
|
ef9e897b27 | ||
|
|
9cc5ffd143 | ||
|
|
16a3fdb56e | ||
|
|
6833dd05ab | ||
|
|
3747fa2c2b | ||
|
|
411b90b4b2 | ||
|
|
dfa93ff8e3 | ||
|
|
8ae292933a | ||
|
|
63c20da5ff | ||
|
|
8f9453b84a | ||
|
|
9b955c9ce2 | ||
|
|
d184998ed5 | ||
|
|
5e719f3e9e | ||
|
|
082cdc9ee7 | ||
|
|
7e20df6a58 | ||
|
|
3c81ef129b | ||
|
|
41a5932233 | ||
|
|
52383e0ecf | ||
|
|
0b327cb4d9 | ||
|
|
aa68386667 | ||
|
|
d15a068e0c | ||
|
|
5d1edd7e4c | ||
|
|
60814b8d20 | ||
|
|
55c1d8904c | ||
|
|
3a5fdf2f54 | ||
|
|
c4bdde2b03 | ||
|
|
2f681e9f78 | ||
|
|
e42258de1f | ||
|
|
06c4f15477 | ||
|
|
233ece4b3a |
||
|
|
3fee88ae6e | ||
|
|
b61dd57a37 | ||
|
|
aa46dca4e3 | ||
|
|
d50cbb09d1 | ||
|
|
36f7de3783 | ||
|
|
ad1ba4f8a1 | ||
|
|
c62b6261ac | ||
|
|
da93217bef | ||
|
|
d8e1d4031d | ||
|
|
85505d4b31 | ||
|
|
2b516fee14 | ||
|
|
bb3c8ba6d1 |
||
|
|
178bf998ab | ||
|
|
30db9f0667 | ||
|
|
4c1a3e5015 | ||
|
|
0de9c1ad23 | ||
|
|
7bf31c6a8f | ||
|
|
2a50f6e4d2 | ||
|
|
3fc1d8e18d | ||
|
|
d4d58b948b | ||
|
|
1b50978559 | ||
|
|
cb92dc73f1 | ||
|
|
548a11d501 | ||
|
|
4665741425 | ||
|
|
6c010fc5fd | ||
|
|
5fb43065ef | ||
|
|
1ce13c6721 | ||
|
|
c682f03a67 | ||
|
|
fbd1a0cf0d | ||
|
|
71bd124596 | ||
|
|
80dfe46323 | ||
|
|
7c02774924 | ||
|
|
108fdf84b8 | ||
|
|
b3b7f446b1 | ||
|
|
0999b02bba |
||
|
|
f966f039dd |
||
|
|
c2ffc9aec3 | ||
|
|
44eb1646db | ||
|
|
9c414ed1e7 | ||
|
|
fb9b6ec54d | ||
|
|
f727276f82 | ||
|
|
ebb9be985a | ||
|
|
855aacce0b | ||
|
|
19fcb9b0f3 | ||
|
|
a00e45ea4c | ||
|
|
23a47efefd | ||
|
|
502f53c970 | ||
|
|
c5a1359218 | ||
|
|
dfae166e4d | ||
|
|
a76724a8cf | ||
|
|
c5eb6ed590 | ||
|
|
1a69bc48bb | ||
|
|
41d8efe6e7 | ||
|
|
7e1a470ba4 | ||
|
|
4503b05a8a | ||
|
|
90f757dc93 | ||
|
|
4193946fe0 | ||
|
|
d4954e026d | ||
|
|
da7076fc2d | ||
|
|
18f6b75dee |
||
|
|
ae5e12be3b | ||
|
|
208c2715f9 | ||
|
|
0a54b413b3 | ||
|
|
a72eb0ba3a | ||
|
|
2748f23137 | ||
|
|
496c89a7e9 | ||
|
|
a4141421aa | ||
|
|
08acfd593c | ||
|
|
b3c6e05e6f | ||
|
|
0cb2e99f03 | ||
|
|
729edc9afa | ||
|
|
5d2a2d49c4 | ||
|
|
1dde11f8e4 | ||
|
|
e1b66ed7ed | ||
|
|
d2151b74bd | ||
|
|
dc34273826 | ||
|
|
73c2987d4c | ||
|
|
d93fc98469 | ||
|
|
9fc49a7430 | ||
|
|
a9b630884b | ||
|
|
b63c1dc9b1 | ||
|
|
fc009f3d00 | ||
|
|
ab275e978c | ||
|
|
1111177eb7 | ||
|
|
b794d3433c | ||
|
|
9ef58808fc | ||
|
|
e0c3ddb07d | ||
|
|
05d27400a0 | ||
|
|
84a6447fd4 | ||
|
|
34777598dd | ||
|
|
1b037a7adf | ||
|
|
8884598a56 | ||
|
|
a2a87abf7c | ||
|
|
fa6bb2f492 | ||
|
|
1cc9c11e8f | ||
|
|
77ccb30600 | ||
|
|
998a8ee6b4 | ||
|
|
0ad22e49bc | ||
|
|
964cbba54f | ||
|
|
948a3db48a | ||
|
|
46d70541d3 | ||
|
|
2e7b462d94 | ||
|
|
16527ed529 | ||
|
|
7fbbd08da6 | ||
|
|
689831c26c | ||
|
|
6e4045bb0d | ||
|
|
80666bc728 | ||
|
|
c13cfa0ea0 | ||
|
|
2e95273d97 | ||
|
|
4f7c17f420 | ||
|
|
07d5ab5af7 | ||
|
|
1d6b177a55 | ||
|
|
2d82411788 | ||
|
|
f867ab6a61 | ||
|
|
b1e52ffac3 | ||
|
|
b99f70cc60 | ||
|
|
e960440854 | ||
|
|
0eb223baa0 | ||
|
|
00836cc368 | ||
|
|
5227b5fc29 | ||
|
|
0bb1c9635a | ||
|
|
7ba5f15839 | ||
|
|
fa90decdd1 | ||
|
|
379b9be2b9 | ||
|
|
9f785db7ba | ||
|
|
e26215a11f | ||
|
|
acb067a80e | ||
|
|
45d302be29 | ||
|
|
ed7c247bcb | ||
|
|
82310a2e0b | ||
|
|
67b6e30d24 | ||
|
|
c69c0fbb40 | ||
|
|
a6395b5ce0 | ||
|
|
1192a228ef | ||
|
|
259fe5f697 | ||
|
|
c94bd7cf81 | ||
|
|
3419369af0 | ||
|
|
f9572e4125 | ||
|
|
d76a1d9fee | ||
|
|
07780c5f6f | ||
|
|
b07ac093b2 | ||
|
|
5a2ca0cc4a | ||
|
|
73f094daf2 | ||
|
|
0bab4c7af9 | ||
|
|
1686f840d1 | ||
|
|
d0836f3ee3 | ||
|
|
c668f1c151 | ||
|
|
3a57538572 | ||
|
|
c745fd8b28 | ||
|
|
88fb618c28 | ||
|
|
7213ed290c | ||
|
|
c1287cbe6c |
2662 changed files with 126959 additions and 88099 deletions
|
|
@ -3,19 +3,15 @@
|
|||
"projectOwner": "adaures",
|
||||
"repoType": "gitlab",
|
||||
"repoHost": "https://code.castopod.org",
|
||||
"files": [
|
||||
"README.md",
|
||||
"docs/src/index.md"
|
||||
],
|
||||
"files": ["README.md"],
|
||||
"imageSize": 100,
|
||||
"commit": false,
|
||||
"contributorsPerLine": 7,
|
||||
"contributors": [
|
||||
{
|
||||
"login": "yassinedoghri",
|
||||
"name": "Yassine Doghri",
|
||||
"avatar_url": "https://code.castopod.org/uploads/-/system/user/avatar/3/avatar.png",
|
||||
"profile": "https://github.com/yassinedoghri",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/11021441?v=4",
|
||||
"profile": "https://yassinedoghri.com",
|
||||
"contributions": [
|
||||
"code",
|
||||
"bug",
|
||||
|
|
@ -100,24 +96,14 @@
|
|||
"name": "Lyonel Bernard",
|
||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
||||
"profile": "https://twitter.com/lyonelbernard",
|
||||
"contributions": [
|
||||
"bug",
|
||||
"question",
|
||||
"audio",
|
||||
"ideas"
|
||||
]
|
||||
"contributions": ["bug", "question", "audio", "ideas"]
|
||||
},
|
||||
{
|
||||
"login": "ctlw83",
|
||||
"name": "Christopher Lagonick-Weitzel",
|
||||
"avatar_url": "https://secure.gravatar.com/avatar/7c2a721b52d0763673a600e8f01bd745?s=80&d=identicon",
|
||||
"profile": "https://www.crypticchameleon.com/",
|
||||
"contributions": [
|
||||
"bug",
|
||||
"question",
|
||||
"audio",
|
||||
"ideas"
|
||||
]
|
||||
"contributions": ["bug", "question", "audio", "ideas"]
|
||||
},
|
||||
{
|
||||
"login": "ernestoacostame",
|
||||
|
|
@ -135,24 +121,33 @@
|
|||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "3wen",
|
||||
"name": "Ewen",
|
||||
"avatar_url": "https://mastodon.fedi.bzh/system/accounts/avatars/000/000/002/original/6f387690a504ae46.jpg",
|
||||
"profile": "https://mastodon.fedi.bzh/@ewen",
|
||||
"contributions": [
|
||||
{
|
||||
"type": "translation",
|
||||
"url": "https://translate.castopod.org"
|
||||
},
|
||||
"ideas",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Behel",
|
||||
"name": "Bastien Luneteau",
|
||||
"avatar_url": "https://secure.gravatar.com/avatar/ad63ee8ef8e3db8253d21e5012d2724f?s=80&d=identicon",
|
||||
"profile": "https://code.castopod.org/Behel",
|
||||
"contributions": [
|
||||
"code",
|
||||
"bug"
|
||||
]
|
||||
"contributions": ["code", "bug"]
|
||||
},
|
||||
{
|
||||
"login": "cecillie",
|
||||
"name": "Cécile Ricordeau",
|
||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
||||
"profile": "https://www.cecillie.fr/",
|
||||
"contributions": [
|
||||
"design"
|
||||
]
|
||||
"contributions": ["design"]
|
||||
},
|
||||
{
|
||||
"login": "PatrykMis",
|
||||
|
|
@ -171,48 +166,35 @@
|
|||
"name": "Marcin Lewandowski",
|
||||
"avatar_url": "https://secure.gravatar.com/avatar/eed8337939641eac5ad0b570bd6acf96?s=80&d=identicon",
|
||||
"profile": "https://code.castopod.org/mspanc",
|
||||
"contributions": [
|
||||
"bug",
|
||||
"ideas"
|
||||
]
|
||||
"contributions": ["bug", "ideas"]
|
||||
},
|
||||
{
|
||||
"login": "SJanik",
|
||||
"name": "Sebastian Janik",
|
||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
||||
"profile": "https://code.castopod.org/SJanik",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
"contributions": ["code"]
|
||||
},
|
||||
{
|
||||
"login": "patryk",
|
||||
"name": "Patryk Karczmarczyk",
|
||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
||||
"profile": "https://code.castopod.org/patryk",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
"contributions": ["code"]
|
||||
},
|
||||
{
|
||||
"login": "ddenis",
|
||||
"name": "denis d",
|
||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
||||
"profile": "https://code.castopod.org/ddenis",
|
||||
"contributions": [
|
||||
"bug",
|
||||
"ideas"
|
||||
]
|
||||
"contributions": ["bug", "ideas"]
|
||||
},
|
||||
{
|
||||
"login": "douglaskastle",
|
||||
"name": "Douglas Kastle",
|
||||
"avatar_url": "https://secure.gravatar.com/avatar/b7e652ba4b6bcd440afa069e7f7bc9e6?s=80&d=identicon",
|
||||
"profile": "https://code.castopod.org/douglaskastle",
|
||||
"contributions": [
|
||||
"bug",
|
||||
"ideas"
|
||||
]
|
||||
"contributions": ["bug", "ideas"]
|
||||
},
|
||||
{
|
||||
"login": "cExplorer",
|
||||
|
|
@ -232,66 +214,49 @@
|
|||
"name": "ImaCrea",
|
||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
||||
"profile": "https://code.castopod.org/imacrea",
|
||||
"contributions": [
|
||||
"bug",
|
||||
"ideas"
|
||||
]
|
||||
"contributions": ["bug", "ideas"]
|
||||
},
|
||||
{
|
||||
"login": "jonas",
|
||||
"name": "Jonas S",
|
||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
||||
"profile": "https://code.castopod.org/jonas",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
"contributions": ["code"]
|
||||
},
|
||||
{
|
||||
"login": "yannL",
|
||||
"name": "LEFEBVRE Yann",
|
||||
"avatar_url": "https://secure.gravatar.com/avatar/9c46600ce566ec6d526370d8e104b1c8?s=80&d=identicon",
|
||||
"profile": "https://code.castopod.org/yannL",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
"contributions": ["bug"]
|
||||
},
|
||||
{
|
||||
"login": "spaetz",
|
||||
"name": "Sebastian Späth",
|
||||
"avatar_url": "https://secure.gravatar.com/avatar/278e1af65e82993efd0ba7bbbacf6435?s=80&d=identicon",
|
||||
"profile": "https://code.castopod.org/spaetz",
|
||||
"contributions": [
|
||||
"bug",
|
||||
"ideas"
|
||||
]
|
||||
"contributions": ["bug", "ideas"]
|
||||
},
|
||||
{
|
||||
"login": "rocky",
|
||||
"name": "rocky III",
|
||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
||||
"profile": "https://code.castopod.org/rocky",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
"contributions": ["bug"]
|
||||
},
|
||||
{
|
||||
"login": "Regenpfeifer",
|
||||
"name": "Hermann Josef Eckl",
|
||||
"avatar_url": "https://code.castopod.org/uploads/-/system/user/avatar/103/avatar.png",
|
||||
"profile": "https://code.castopod.org/Regenpfeifer",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
"contributions": ["bug"]
|
||||
},
|
||||
{
|
||||
"login": "cyrilledel",
|
||||
"name": "Delhaye Cyrille",
|
||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
||||
"profile": "https://code.castopod.org/cyrilledel",
|
||||
"contributions": [
|
||||
"bug",
|
||||
"ideas"
|
||||
]
|
||||
"contributions": ["bug", "ideas"]
|
||||
},
|
||||
{
|
||||
"login": "otetranome",
|
||||
|
|
@ -330,19 +295,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "3wen",
|
||||
"name": "Ewen",
|
||||
"avatar_url": "https://mastodon.fedi.bzh/system/accounts/avatars/000/000/002/original/6f387690a504ae46.jpg",
|
||||
"profile": "https://mastodon.fedi.bzh/@ewen",
|
||||
"contributions": [
|
||||
{
|
||||
"type": "translation",
|
||||
"url": "https://translate.castopod.org"
|
||||
},
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "forght",
|
||||
"name": "forght",
|
||||
|
|
@ -370,7 +322,7 @@
|
|||
{
|
||||
"login": "BoFFire",
|
||||
"name": "ButterflyOfFire",
|
||||
"avatar_url": "https://static.mstdn.fr/static/accounts/avatars/000/065/901/original/e18d44b28edd0ada.png",
|
||||
"avatar_url": "https://static.mstdn.fr/static/accounts/avatars/000/065/901/original/5908e93ad5447f15.png",
|
||||
"profile": "https://mstdn.fr/@ButterflyOfFire",
|
||||
"contributions": [
|
||||
{
|
||||
|
|
@ -492,14 +444,12 @@
|
|||
"name": "Dimitri Regnier",
|
||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
||||
"profile": "https://dimitriregnier.net/",
|
||||
"contributions": [
|
||||
"ideas"
|
||||
]
|
||||
"contributions": ["ideas"]
|
||||
},
|
||||
{
|
||||
"login": "irithys",
|
||||
"name": "irithys",
|
||||
"avatar_url": "https://crowdin-static.downloads.crowdin.com/avatar/15405614/large/e46d7f8e9f7c05997827563c3a3cf942.jpeg",
|
||||
"avatar_url": "https://crowdin-static.downloads.crowdin.com/avatar/15405614/large/3086461c47cce0a0c031925e5f943412.png",
|
||||
"profile": "https://im.irithys.com/@thy",
|
||||
"contributions": [
|
||||
{
|
||||
|
|
@ -521,16 +471,104 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"login": "ghose",
|
||||
"name": "ghose (XoseM)",
|
||||
"avatar_url": "https://crowdin-static.downloads.crowdin.com/avatar/12617257/large/a201650da44fed28890b0e0d8477a663.jpg",
|
||||
"profile": "https://crowdin.com/profile/xosem",
|
||||
"login": "basen1982",
|
||||
"name": "Andreas Olsson",
|
||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
||||
"profile": "https://crowdin.com/profile/basen1982",
|
||||
"contributions": [
|
||||
{
|
||||
"type": "translation",
|
||||
"url": "https://translate.castopod.org"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "leonfrom",
|
||||
"name": "leonfrom",
|
||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
||||
"profile": "https://crowdin.com/profile/leonfrom",
|
||||
"contributions": [
|
||||
{
|
||||
"type": "translation",
|
||||
"url": "https://translate.castopod.org"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "agentcobra57",
|
||||
"name": "agentcobra",
|
||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
||||
"profile": "https://crowdin.com/profile/agentcobra57",
|
||||
"contributions": [
|
||||
{
|
||||
"type": "translation",
|
||||
"url": "https://translate.castopod.org"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "alephoto85",
|
||||
"name": "Alessandro",
|
||||
"avatar_url": "https://crowdin-static.downloads.crowdin.com/avatar/15094649/large/530391f54157af52ae33058ec15b0f99.jpg",
|
||||
"profile": "https://crowdin.com/profile/alephoto85",
|
||||
"contributions": [
|
||||
{
|
||||
"type": "translation",
|
||||
"url": "https://translate.castopod.org"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "liimee",
|
||||
"name": "liimee",
|
||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
||||
"profile": "https://crowdin.com/profile/liimee",
|
||||
"contributions": [
|
||||
{
|
||||
"type": "translation",
|
||||
"url": "https://translate.castopod.org"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ahmedsabouni",
|
||||
"name": "Ahmed Sabouni",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/74497842?v=4",
|
||||
"profile": "https://github.com/ahmedsabouni",
|
||||
"contributions": [
|
||||
{
|
||||
"type": "translation",
|
||||
"url": "https://translate.castopod.org"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "KrzysztofDomanczyk",
|
||||
"name": "KrzysztofDomanczyk",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/75178474?v=4",
|
||||
"profile": "https://github.com/KrzysztofDomanczyk",
|
||||
"contributions": ["code"]
|
||||
},
|
||||
{
|
||||
"login": "Dwev",
|
||||
"name": "Guy Martin",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/46626050?v=4",
|
||||
"profile": "https://github.com/Dwev",
|
||||
"contributions": ["bug", "code"]
|
||||
},
|
||||
{
|
||||
"login": "prcutler",
|
||||
"name": "Paul Cutler",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/67276?v=4",
|
||||
"profile": "https://github.com/prcutler",
|
||||
"contributions": ["doc", "question", "ideas"]
|
||||
},
|
||||
{
|
||||
"login": "nateritter",
|
||||
"name": "Nate Ritter",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/198798?v=4",
|
||||
"profile": "https://github.com/nateritter",
|
||||
"contributions": ["code"]
|
||||
}
|
||||
],
|
||||
"commitConvention": "none"
|
||||
|
|
|
|||
|
|
@ -4,31 +4,20 @@
|
|||
# ⚠️ NOT optimized for production
|
||||
# should be used only for development purposes
|
||||
#---------------------------------------------------
|
||||
FROM php:8.1-fpm
|
||||
FROM php:8.5-fpm
|
||||
|
||||
LABEL maintainer="Yassine Doghri <yassine@doghri.fr>"
|
||||
|
||||
COPY . /castopod
|
||||
WORKDIR /castopod
|
||||
|
||||
# Install composer
|
||||
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
||||
|
||||
# Install server requirements
|
||||
RUN apt-get update \
|
||||
# gnupg to sign commits with gpg
|
||||
&& apt-get install --yes --no-install-recommends gnupg \
|
||||
# npm through the nodejs package
|
||||
&& curl -fsSL https://deb.nodesource.com/setup_16.x | bash - \
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
|
||||
&& apt-get update \
|
||||
&& apt-get install --yes --no-install-recommends nodejs \
|
||||
# update npm
|
||||
&& npm install --global npm@8 \
|
||||
&& apt-get update \
|
||||
&& apt-get install --yes --no-install-recommends \
|
||||
git \
|
||||
# gnupg to sign commits with gpg
|
||||
gnupg \
|
||||
openssh-client \
|
||||
vim \
|
||||
# cron for scheduled tasks
|
||||
cron \
|
||||
# unzip used by composer
|
||||
|
|
@ -38,7 +27,7 @@ RUN apt-get update \
|
|||
libicu-dev \
|
||||
libpng-dev \
|
||||
libwebp-dev \
|
||||
libjpeg-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
libfreetype6-dev \
|
||||
zlib1g-dev \
|
||||
libzip-dev \
|
||||
|
|
@ -58,11 +47,4 @@ RUN apt-get update \
|
|||
&& docker-php-ext-enable redis \
|
||||
# mysqli for database access
|
||||
&& docker-php-ext-install mysqli \
|
||||
&& docker-php-ext-enable mysqli \
|
||||
# configure php
|
||||
&& echo "file_uploads = On\n" \
|
||||
"memory_limit = 512M\n" \
|
||||
"upload_max_filesize = 500M\n" \
|
||||
"post_max_size = 512M\n" \
|
||||
"max_execution_time = 300\n" \
|
||||
> /usr/local/etc/php/conf.d/uploads.ini
|
||||
&& docker-php-ext-enable mysqli
|
||||
1
.devcontainer/crontab
Normal file
1
.devcontainer/crontab
Normal file
|
|
@ -0,0 +1 @@
|
|||
* * * * * /usr/local/bin/php /workspaces/castopod/spark tasks:run >> /dev/null 2>&1
|
||||
|
|
@ -1,46 +1,70 @@
|
|||
// For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at:
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.117.1/containers/docker-existing-dockerfile
|
||||
{
|
||||
"name": "Castopod dev",
|
||||
"dockerComposeFile": ["../docker-compose.yml", "./docker-compose.yml"],
|
||||
"name": "castopod.local",
|
||||
"dockerComposeFile": ["./docker-compose.yml"],
|
||||
"service": "app",
|
||||
"workspaceFolder": "/castopod",
|
||||
"postCreateCommand": "composer install && npm install && npm run build:static",
|
||||
"postStartCommand": "crontab ./crontab && cron && php spark serve --host 0.0.0.0",
|
||||
"postAttachCommand": "crontab ./crontab && service cron reload",
|
||||
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
|
||||
"postCreateCommand": "composer install && pnpm install && pnpm run build:static && php spark migrate --all && php spark db:seed DevSeeder",
|
||||
"postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder} && crontab .devcontainer/crontab && cron && php spark serve --host 0.0.0.0 --port ${APP_PORT:-8080}",
|
||||
"postAttachCommand": "crontab .devcontainer/crontab && service cron reload",
|
||||
"shutdownAction": "stopCompose",
|
||||
"settings": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"[php]": {
|
||||
"editor.defaultFormatter": "bmewburn.vscode-intelephense-client",
|
||||
"editor.formatOnSave": false
|
||||
},
|
||||
"css.validate": false,
|
||||
"color-highlight.markerType": "dot-before",
|
||||
"files.associations": {
|
||||
"*.xml.dist": "xml",
|
||||
"spark": "php",
|
||||
"env": "dotenv",
|
||||
".rsync-filter": "diff"
|
||||
}
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/git:1": {},
|
||||
"ghcr.io/guiyomh/features/vim:0": {},
|
||||
"ghcr.io/NicoVIII/devcontainer-features/pnpm:2": {}
|
||||
},
|
||||
"extensions": [
|
||||
"bmewburn.vscode-intelephense-client",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"breezelin.phpstan",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"eamodio.gitlens",
|
||||
"esbenp.prettier-vscode",
|
||||
"heybourn.headwind",
|
||||
"jamesbirtles.svelte-vscode",
|
||||
"kasik96.latte",
|
||||
"mikestead.dotenv",
|
||||
"naumovs.color-highlight",
|
||||
"pflannery.vscode-versionlens",
|
||||
"runem.lit-plugin",
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"stylelint.vscode-stylelint",
|
||||
"wayou.vscode-todo-highlight"
|
||||
]
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"[php]": {
|
||||
"editor.defaultFormatter": "bmewburn.vscode-intelephense-client",
|
||||
"editor.formatOnSave": false
|
||||
},
|
||||
"css.validate": false,
|
||||
"color-highlight.markerType": "dot-before",
|
||||
"files.associations": {
|
||||
"*.xml.dist": "xml",
|
||||
"spark": "php",
|
||||
"env": "dotenv",
|
||||
".rsync-filter": "diff"
|
||||
},
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": [
|
||||
"plugins/**/manifest.json",
|
||||
"tests/modules/Plugins/mocks/manifests/*.json",
|
||||
"tests/modules/Plugins/mocks/plugins/**/manifest.json"
|
||||
],
|
||||
"url": "/workspaces/castopod/modules/Plugins/Manifest/manifest.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"extensions": [
|
||||
"astro-build.astro-vscode",
|
||||
"bmewburn.vscode-intelephense-client",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"breezelin.phpstan",
|
||||
"DavidAnson.vscode-markdownlint",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"eamodio.gitlens",
|
||||
"esbenp.prettier-vscode",
|
||||
"heybourn.headwind",
|
||||
"jamesbirtles.svelte-vscode",
|
||||
"kasik96.latte",
|
||||
"mikestead.dotenv",
|
||||
"naumovs.color-highlight",
|
||||
"pflannery.vscode-versionlens",
|
||||
"runem.lit-plugin",
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"stylelint.vscode-stylelint",
|
||||
"unifiedjs.vscode-mdx",
|
||||
"wayou.vscode-todo-highlight",
|
||||
"yzhang.markdown-all-in-one",
|
||||
"42Crunch.vscode-openapi"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,76 @@
|
|||
version: "3"
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
volumes:
|
||||
# Mounts the project folder to '/workspace'. While this file is in .devcontainer,
|
||||
# mounts are relative to the first file in the list, which is a level up.
|
||||
- .:/castopod:cached
|
||||
- ../..:/workspaces:cached
|
||||
- ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
|
||||
environment:
|
||||
APP_PORT: ${APP_PORT:-8080} # used in devcontainer.json file
|
||||
VITE_PORT: ${VITE_PORT:-5173} # used in ../vite.config.js file
|
||||
CI_ENVIRONMENT: development
|
||||
vite_environment: development
|
||||
app_forceGlobalSecureRequests: 0 #false
|
||||
app_baseURL: http://localhost:${APP_PORT:-8080}/
|
||||
media_baseURL: http://localhost:${APP_PORT:-8080}/
|
||||
admin_gateway: cp-admin
|
||||
auth_gateway: cp-auth
|
||||
analytics_salt: dev_analytics_salt
|
||||
database_default_hostname: mariadb
|
||||
database_default_database: castopod
|
||||
database_default_username: castopod
|
||||
database_default_password: castopod
|
||||
database_default_DBPrefix: cp_
|
||||
restapi_enabled: 1 #true
|
||||
email_fromEmail: hello@castopod.local
|
||||
email_SMTPCrypto: ""
|
||||
email_SMTPHost: mailpit
|
||||
email_SMTPUser: castopod
|
||||
email_SMTPPass: castopod
|
||||
email_SMTPPort: ${MAILPIT_SMTP_PORT:-1025}
|
||||
depends_on:
|
||||
- mariadb
|
||||
|
||||
# Overrides default command so things don't shut down after the process ends.
|
||||
command: /bin/sh -c "while sleep 1000; do :; done"
|
||||
mariadb:
|
||||
image: mariadb:10.2
|
||||
volumes:
|
||||
- ./initdb:/docker-entrypoint-initdb.d
|
||||
- mariadb:/var/lib/mysql
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: castopod
|
||||
MYSQL_USER: castopod
|
||||
MYSQL_PASSWORD: castopod
|
||||
|
||||
phpmyadmin:
|
||||
image: phpmyadmin/phpmyadmin:latest
|
||||
environment:
|
||||
PMA_HOST: mariadb
|
||||
PMA_PORT: 3306
|
||||
UPLOAD_LIMIT: 300M
|
||||
ports:
|
||||
- 8888:80
|
||||
volumes:
|
||||
- phpmyadmin:/sessions
|
||||
depends_on:
|
||||
- mariadb
|
||||
|
||||
mailpit:
|
||||
image: axllent/mailpit
|
||||
restart: always
|
||||
volumes:
|
||||
- mailpit:/data
|
||||
ports:
|
||||
- ${MAILPIT_WEBUI_PORT:-8025}:8025
|
||||
- ${MAILPIT_SMTP_PORT:-1025}:1025
|
||||
environment:
|
||||
MP_MAX_MESSAGES: 5000
|
||||
MP_DATA_FILE: /data/mailpit.db
|
||||
MP_SMTP_AUTH_ACCEPT_ANY: 1
|
||||
MP_SMTP_AUTH_ALLOW_INSECURE: 1
|
||||
|
||||
volumes:
|
||||
mariadb:
|
||||
phpmyadmin:
|
||||
mailpit:
|
||||
|
|
|
|||
68
.dockerignore
Normal file
68
.dockerignore
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
.env
|
||||
|
||||
.git/
|
||||
node_modules/
|
||||
vendor/
|
||||
build/
|
||||
docs/
|
||||
scripts/
|
||||
tests/
|
||||
|
||||
#-------------------------
|
||||
# Temporary Files
|
||||
#-------------------------
|
||||
writable/cache/*
|
||||
!writable/cache/index.html
|
||||
|
||||
writable/logs/*
|
||||
!writable/logs/index.html
|
||||
|
||||
writable/session/*
|
||||
!writable/session/index.html
|
||||
|
||||
writable/temp/*
|
||||
!writable/temp/index.html
|
||||
|
||||
writable/uploads/*
|
||||
!writable/uploads/index.html
|
||||
|
||||
writable/debugbar/*
|
||||
!writable/debugbar/index.html
|
||||
|
||||
# public folder
|
||||
public/*
|
||||
!public/media
|
||||
!public/.htaccess
|
||||
!public/favicon.ico
|
||||
!public/icon*
|
||||
!public/castopod-banner*
|
||||
!public/castopod-avatar*
|
||||
!public/index.php
|
||||
!public/robots.txt
|
||||
!public/.well-known
|
||||
!public/.well-known/GDPR.yml
|
||||
|
||||
public/assets/*
|
||||
!public/assets/index.html
|
||||
|
||||
# public media folder
|
||||
!public/media/podcasts
|
||||
!public/media/persons
|
||||
!public/media/site
|
||||
|
||||
public/media/podcasts/*
|
||||
!public/media/podcasts/index.html
|
||||
|
||||
public/media/persons/*
|
||||
!public/media/persons/index.html
|
||||
|
||||
public/media/site/*
|
||||
!public/media/site/index.html
|
||||
|
||||
# Generated files
|
||||
modules/Admin/Language/*/PersonsTaxonomy.php
|
||||
|
||||
# Castopod bundle & packages
|
||||
castopod/
|
||||
castopod-*.zip
|
||||
castopod-*.tar.gz
|
||||
16
.env.example
16
.env.example
|
|
@ -14,9 +14,10 @@
|
|||
# Instance configuration
|
||||
#--------------------------------------------------------------------
|
||||
app.baseURL="https://YOUR_DOMAIN_NAME/"
|
||||
app.mediaBaseURL="https://YOUR_MEDIA_DOMAIN_NAME/"
|
||||
media.baseURL="https://YOUR_MEDIA_DOMAIN_NAME/"
|
||||
admin.gateway="cp-admin"
|
||||
auth.gateway="cp-auth"
|
||||
analytics.salt="RANDOM_STRING_OF_64_CHARACTERS"
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# Database configuration
|
||||
|
|
@ -50,9 +51,20 @@ cache.handler="file"
|
|||
# cache.redis.port=6379
|
||||
# cache.redis.database=0
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# S3 configuration
|
||||
#--------------------------------------------------------------------
|
||||
# media.fileManager="s3"
|
||||
# media.s3.endpoint="your_s3_host"
|
||||
# media.s3.key="your_s3_key"
|
||||
# media.s3.secret="your_s3_secret"
|
||||
# media.s3.region="your_s3_region"
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# REST API configuration
|
||||
#--------------------------------------------------------------------
|
||||
# restapi.enabled=true
|
||||
|
||||
# restapi.basicAuthUsername=castopod
|
||||
# restapi.basicAuthPassword=password
|
||||
# restapi.basicAuth=true
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2020": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 11,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {}
|
||||
}
|
||||
46
.gitignore
vendored
46
.gitignore
vendored
|
|
@ -67,6 +67,7 @@ writable/uploads/*
|
|||
!writable/uploads/index.html
|
||||
|
||||
writable/debugbar/*
|
||||
!writable/debugbar/index.html
|
||||
|
||||
php_errors.log
|
||||
|
||||
|
|
@ -85,6 +86,7 @@ tests/coverage*
|
|||
|
||||
# Don't save phpunit under version control.
|
||||
phpunit
|
||||
.phpunit.cache
|
||||
|
||||
#-------------------------
|
||||
# Composer
|
||||
|
|
@ -105,15 +107,15 @@ _modules/*
|
|||
.idea/
|
||||
*.iml
|
||||
|
||||
# Netbeans
|
||||
nbproject/
|
||||
build/
|
||||
nbbuild/
|
||||
dist/
|
||||
nbdist/
|
||||
nbactions.xml
|
||||
nb-configuration.xml
|
||||
.nb-gradle/
|
||||
# NetBeans
|
||||
/nbproject/
|
||||
/build/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/nbactions.xml
|
||||
/nb-configuration.xml
|
||||
/.nb-gradle/
|
||||
|
||||
# Sublime Text
|
||||
*.tmlanguage.cache
|
||||
|
|
@ -126,30 +128,36 @@ nb-configuration.xml
|
|||
|
||||
# Visual Studio Code
|
||||
.vscode/
|
||||
.history/
|
||||
tmp/
|
||||
|
||||
/results/
|
||||
/phpunit*.xml
|
||||
/.phpunit.*.cache
|
||||
|
||||
# npm
|
||||
# js package manager
|
||||
yarn.lock
|
||||
node_modules
|
||||
.pnpm-store
|
||||
|
||||
# JS
|
||||
.cache
|
||||
|
||||
# public folder
|
||||
public/*
|
||||
public/media/site
|
||||
!public/media
|
||||
!public/.htaccess
|
||||
!public/favicon.ico
|
||||
!public/icon*
|
||||
!public/castopod-banner*
|
||||
!public/castopod-avatar*
|
||||
!public/index.php
|
||||
!public/robots.txt
|
||||
!public/.well-known
|
||||
!public/.well-known/GDPR.yml
|
||||
|
||||
public/assets/*
|
||||
!public/assets/index.html
|
||||
|
||||
# public media folder
|
||||
!public/media/podcasts
|
||||
!public/media/persons
|
||||
|
|
@ -167,15 +175,13 @@ public/media/site/*
|
|||
# Generated files
|
||||
modules/Admin/Language/*/PersonsTaxonomy.php
|
||||
|
||||
#-------------------------
|
||||
# Docker volumes
|
||||
#-------------------------
|
||||
|
||||
mariadb
|
||||
phpmyadmin
|
||||
sessions
|
||||
|
||||
# Castopod bundle & packages
|
||||
castopod/
|
||||
castopod-*.zip
|
||||
castopod-*.tar.gz
|
||||
|
||||
# Plugins
|
||||
plugins/*
|
||||
!plugins/.gitkeep
|
||||
writable/plugins.json
|
||||
writable/plugins-lock.json
|
||||
|
|
|
|||
124
.gitlab-ci.yml
124
.gitlab-ci.yml
|
|
@ -1,32 +1,52 @@
|
|||
image: code.castopod.org:5050/adaures/castopod:latest
|
||||
image: code.castopod.org:5050/adaures/castopod:ci-php8.5
|
||||
|
||||
stages:
|
||||
- prepare
|
||||
- quality
|
||||
- bundle
|
||||
- release
|
||||
- build
|
||||
- deploy
|
||||
- build
|
||||
|
||||
php-dependencies:
|
||||
stage: prepare
|
||||
script:
|
||||
# Install all php dependencies
|
||||
- composer install --prefer-dist --no-ansi --no-interaction --no-progress --ignore-platform-reqs
|
||||
cache:
|
||||
key:
|
||||
files:
|
||||
- composer.lock
|
||||
paths:
|
||||
- .composer-cache
|
||||
artifacts:
|
||||
expire_in: 30 mins
|
||||
paths:
|
||||
- vendor/
|
||||
expire_in: 30 mins
|
||||
rules:
|
||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
||||
when: never
|
||||
- when: on_success
|
||||
|
||||
js-dependencies:
|
||||
stage: prepare
|
||||
script:
|
||||
# Install all npm dependencies
|
||||
- npm ci
|
||||
# Install all js dependencies
|
||||
- pnpm install
|
||||
cache:
|
||||
key:
|
||||
files:
|
||||
- pnpm-lock.yaml
|
||||
paths:
|
||||
- .pnpm-store
|
||||
artifacts:
|
||||
expire_in: 30 mins
|
||||
paths:
|
||||
- node_modules/
|
||||
expire_in: 30 mins
|
||||
rules:
|
||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
||||
when: never
|
||||
- when: on_success
|
||||
|
||||
lint-commit-msg:
|
||||
stage: quality
|
||||
|
|
@ -36,11 +56,10 @@ lint-commit-msg:
|
|||
- ./scripts/lint-commit-msg.sh
|
||||
dependencies:
|
||||
- js-dependencies
|
||||
only:
|
||||
- develop
|
||||
- main
|
||||
- beta
|
||||
- alpha
|
||||
rules:
|
||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
||||
when: never
|
||||
- if: $CI_COMMIT_BRANCH =~ /^(develop|main|alpha|beta|next)$/
|
||||
|
||||
lint-php:
|
||||
stage: quality
|
||||
|
|
@ -53,37 +72,46 @@ lint-php:
|
|||
- vendor/bin/rector process --dry-run --ansi
|
||||
dependencies:
|
||||
- php-dependencies
|
||||
rules:
|
||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
||||
when: never
|
||||
- when: on_success
|
||||
|
||||
lint-js:
|
||||
stage: quality
|
||||
script:
|
||||
- npm run prettier
|
||||
- npm run typecheck
|
||||
- npm run lint
|
||||
- npm run lint:css
|
||||
- pnpm run format
|
||||
- pnpm run typecheck
|
||||
- pnpm run lint
|
||||
- pnpm run lint:css
|
||||
dependencies:
|
||||
- js-dependencies
|
||||
rules:
|
||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
||||
when: never
|
||||
- when: on_success
|
||||
|
||||
tests:
|
||||
stage: quality
|
||||
services:
|
||||
- mariadb
|
||||
- mariadb:10.11
|
||||
variables:
|
||||
MYSQL_ROOT_PASSWORD: "R00Tp4ssW0RD"
|
||||
MYSQL_DATABASE: "test"
|
||||
MYSQL_USER: "castopod"
|
||||
MYSQL_PASSWORD: "castopod"
|
||||
|
||||
script:
|
||||
- apt-get update && apt-get install -y mariadb-client libmariadb-dev
|
||||
|
||||
- echo "SHOW DATABASES;" | mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mariadb "$MYSQL_DATABASE"
|
||||
- echo "SHOW DATABASES;" | mariadb --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mariadb "$MYSQL_DATABASE" --skip_ssl
|
||||
|
||||
# run phpunit without code coverage
|
||||
# TODO: add code coverage
|
||||
- vendor/bin/phpunit --no-coverage
|
||||
dependencies:
|
||||
- php-dependencies
|
||||
rules:
|
||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
||||
when: never
|
||||
- when: on_success
|
||||
|
||||
bundle:
|
||||
stage: bundle
|
||||
|
|
@ -104,13 +132,12 @@ bundle:
|
|||
name: "castopod-${CI_COMMIT_REF_SLUG}_${CI_COMMIT_SHORT_SHA}"
|
||||
paths:
|
||||
- castopod
|
||||
only:
|
||||
variables:
|
||||
- $CI_PROJECT_NAMESPACE == "adaures"
|
||||
except:
|
||||
- main
|
||||
- beta
|
||||
- alpha
|
||||
rules:
|
||||
- if: $CI_PROJECT_NAMESPACE != "adaures"
|
||||
when: never
|
||||
- if: $CI_COMMIT_BRANCH =~ /^(main|alpha|beta|next)$/ || $CI_COMMIT_TAG
|
||||
when: never
|
||||
- when: on_success
|
||||
|
||||
release:
|
||||
stage: release
|
||||
|
|
@ -127,48 +154,45 @@ release:
|
|||
- chmod +x ./scripts/package.sh
|
||||
|
||||
# run semantic-release script (configured in `.releaserc.json` file)
|
||||
- npm run release
|
||||
- pnpm run release
|
||||
dependencies:
|
||||
- php-dependencies
|
||||
- js-dependencies
|
||||
artifacts:
|
||||
paths:
|
||||
- castopod
|
||||
- CP_VERSION.env
|
||||
only:
|
||||
- main
|
||||
- beta
|
||||
- alpha
|
||||
rules:
|
||||
- if: $CI_PROJECT_NAMESPACE != "adaures"
|
||||
when: never
|
||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
||||
when: never
|
||||
- if: $CI_COMMIT_BRANCH =~ /^(main|alpha|beta|next)$/
|
||||
|
||||
website:
|
||||
stage: deploy
|
||||
trigger: adaures/castopod.org
|
||||
only:
|
||||
- main
|
||||
- beta
|
||||
- alpha
|
||||
rules:
|
||||
- if: $CI_PROJECT_NAMESPACE != "adaures"
|
||||
when: never
|
||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/ && $CI_COMMIT_TAG
|
||||
|
||||
documentation:
|
||||
stage: deploy
|
||||
trigger:
|
||||
include: docs/.gitlab-ci.yml
|
||||
strategy: depend
|
||||
only:
|
||||
changes:
|
||||
- docs/**/*
|
||||
rules:
|
||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
||||
when: never
|
||||
- when: on_success
|
||||
|
||||
docker:
|
||||
stage: build
|
||||
trigger:
|
||||
include: docker/production/.gitlab-ci.yml
|
||||
strategy: depend
|
||||
variables:
|
||||
PARENT_PIPELINE_ID: $CI_PIPELINE_ID
|
||||
only:
|
||||
refs:
|
||||
- develop
|
||||
- main
|
||||
- beta
|
||||
- alpha
|
||||
variables:
|
||||
- $CI_PROJECT_NAMESPACE == "adaures"
|
||||
rules:
|
||||
- if: $CI_PROJECT_NAMESPACE != "adaures"
|
||||
when: never
|
||||
- if: $CI_COMMIT_BRANCH == "develop"
|
||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/ && $CI_COMMIT_TAG
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
1. [First step]
|
||||
2. [Second step]
|
||||
3. [and so on...]
|
||||
3. [and so on…]
|
||||
|
||||
### Expected behavior
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ logs, and code as it's very hard to read otherwise.
|
|||
- OS: [e.g. Ubuntu server]
|
||||
- Browser: [e.g. chrome, safari]
|
||||
- Web server: [eg. Apache]
|
||||
- [any other relevant context...]
|
||||
- [any other relevant context…]
|
||||
|
||||
### Possible fixes
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
### Is your feature request related to a problem? Please describe
|
||||
|
||||
A clear and concise description of what the problem is. Ex. I'm always
|
||||
frustrated when [...]
|
||||
frustrated when […]
|
||||
|
||||
### Describe the solution you'd like
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1 @@
|
|||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx --no-install commitlint --verbose --edit "$1"
|
||||
pnpm exec commitlint --verbose --edit "$1"
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
# CaptainHook 5.10.0
|
||||
|
||||
INTERACTIVE="--no-interaction"
|
||||
|
||||
vendor/bin/captainhook $INTERACTIVE --configuration=captainhook.json --bootstrap=vendor/autoload.php hook:pre-commit "$@" <&0
|
||||
|
||||
npm run typecheck
|
||||
npx lint-staged
|
||||
pnpm run typecheck
|
||||
pnpm exec lint-staged
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
# CaptainHook 5.10.0
|
||||
|
||||
INTERACTIVE="--no-interaction"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"trailingComma": "es5",
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.md",
|
||||
"files": ["*.md", "*.mdx"],
|
||||
"options": {
|
||||
"proseWrap": "always"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,16 +8,79 @@
|
|||
{
|
||||
"name": "beta",
|
||||
"prerelease": true
|
||||
},
|
||||
{
|
||||
"name": "next",
|
||||
"prerelease": true
|
||||
}
|
||||
],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
[
|
||||
"@semantic-release/commit-analyzer",
|
||||
{
|
||||
"preset": "conventionalcommits",
|
||||
"releaseRules": [
|
||||
{
|
||||
"type": "docs",
|
||||
"scope": "README",
|
||||
"release": "patch"
|
||||
},
|
||||
{
|
||||
"type": "refactor",
|
||||
"scope": "core-*",
|
||||
"release": "minor"
|
||||
},
|
||||
{
|
||||
"type": "refactor",
|
||||
"release": "patch"
|
||||
}
|
||||
],
|
||||
"parserOpts": {
|
||||
"noteKeywords": ["BREAKING CHANGE", "BREAKING CHANGES", "BREAKING"]
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/release-notes-generator",
|
||||
{
|
||||
"preset": "conventionalcommits",
|
||||
"parserOpts": {
|
||||
"noteKeywords": ["BREAKING CHANGE", "BREAKING CHANGES", "BREAKING"]
|
||||
},
|
||||
"presetConfig": {
|
||||
"types": [
|
||||
{
|
||||
"type": "feat",
|
||||
"section": "Features"
|
||||
},
|
||||
{
|
||||
"type": "fix",
|
||||
"section": "Bug Fixes"
|
||||
},
|
||||
{
|
||||
"type": "chore",
|
||||
"section": "Internal",
|
||||
"hidden": false
|
||||
},
|
||||
{
|
||||
"type": "refactor",
|
||||
"section": "Internal",
|
||||
"hidden": false
|
||||
},
|
||||
{
|
||||
"type": "perf",
|
||||
"section": "Internal",
|
||||
"hidden": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"@semantic-release/changelog",
|
||||
[
|
||||
"@semantic-release/exec",
|
||||
{
|
||||
"prepareCmd": "./scripts/bundle.sh ${nextRelease.version} && ./scripts/package.sh ${nextRelease.version} && npx prettier --write CHANGELOG.md"
|
||||
"prepareCmd": "./scripts/bundle.sh ${nextRelease.version} && ./scripts/package.sh ${nextRelease.version} && pnpm exec prettier --write CHANGELOG.md"
|
||||
}
|
||||
],
|
||||
"@semantic-release/npm",
|
||||
|
|
@ -30,7 +93,8 @@
|
|||
"package.json",
|
||||
"package-lock.json",
|
||||
"CHANGELOG.md"
|
||||
]
|
||||
],
|
||||
"message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
|
||||
}
|
||||
],
|
||||
[
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
# rsync filter rules to copy required files for Castopod's bundle
|
||||
|
||||
- app/Resources/
|
||||
+ resources/icons/***
|
||||
+ resources/
|
||||
+ app/***
|
||||
+ modules/***
|
||||
+ plugins/***
|
||||
+ public/***
|
||||
+ themes/***
|
||||
+ vendor/***
|
||||
|
|
@ -11,4 +13,5 @@
|
|||
+ LICENSE.md
|
||||
+ README.md
|
||||
+ spark
|
||||
+ php-icons.php
|
||||
- **
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"extends": "stylelint-config-recommended",
|
||||
"extends": "stylelint-config-standard",
|
||||
"rules": {
|
||||
"at-rule-no-unknown": [
|
||||
true,
|
||||
|
|
@ -10,10 +10,24 @@
|
|||
"responsive",
|
||||
"variants",
|
||||
"screen",
|
||||
"layer"
|
||||
"layer",
|
||||
"config"
|
||||
]
|
||||
}
|
||||
],
|
||||
"no-descending-specificity": null
|
||||
"at-rule-no-deprecated": [
|
||||
true,
|
||||
{
|
||||
"ignoreAtRules": ["apply"]
|
||||
}
|
||||
],
|
||||
"function-no-unknown": [
|
||||
true,
|
||||
{
|
||||
"ignoreFunctions": ["theme"]
|
||||
}
|
||||
],
|
||||
"no-descending-specificity": null,
|
||||
"selector-class-pattern": null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1857
CHANGELOG.md
1857
CHANGELOG.md
|
|
@ -1,3 +1,1860 @@
|
|||
## [2.0.0-next.3](https://code.castopod.org/adaures/castopod/compare/v2.0.0-next.2...v2.0.0-next.3) (2024-12-30)
|
||||
|
||||
### Features
|
||||
|
||||
- **api:** add Episode create and publish endpoints
|
||||
([a90cdfd](https://code.castopod.org/adaures/castopod/commit/a90cdfdcdbde7a8fb520c6815d7b757947aea055))
|
||||
- **image:** add image size's width and height
|
||||
([f50098e](https://code.castopod.org/adaures/castopod/commit/f50098ec8926c8ae40718f5f128b6de7fe721b46))
|
||||
- **plugins:** add defaultValue for all field types
|
||||
([d3a98db](https://code.castopod.org/adaures/castopod/commit/d3a98db6d0112b5f59daddd2708c09dd2e595332))
|
||||
- **plugins:** add group field type + multiple option to render field arrays
|
||||
([11ccd0e](https://code.castopod.org/adaures/castopod/commit/11ccd0ebe71d476d8c0dbfe28edcf01f7f362b83))
|
||||
- **plugins:** add html field type + CodeEditor component + rework html head
|
||||
generation
|
||||
([8cf9c6d](https://code.castopod.org/adaures/castopod/commit/8cf9c6dc833aedcccbc4cdb309b111f84d97d629))
|
||||
- **rss:** add option for 301 redirect to new feed url
|
||||
([8402cc2](https://code.castopod.org/adaures/castopod/commit/8402cc29d2d0c61b014a7e03e5ccce7d3c11782a))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- add downloads_count to episodes table, computed every hour
|
||||
([f981937](https://code.castopod.org/adaures/castopod/commit/f9819376455c371eb5bd3c84ad938698335a3d67))
|
||||
- allow passing json to app.proxyIPs config to set it
|
||||
([cbf739e](https://code.castopod.org/adaures/castopod/commit/cbf739e95cc0ad6e83a21353b8f4678e68d74f63))
|
||||
- **api:** cast integers when creating episode
|
||||
([775b302](https://code.castopod.org/adaures/castopod/commit/775b302f7c886e30e133c8a8c68764301b6c663b))
|
||||
- **docker-image:** clear cache to account for new assets and data structure
|
||||
changes
|
||||
([63c763f](https://code.castopod.org/adaures/castopod/commit/63c763f941195b3758c4b91acd8c350a5e7bb9c2)),
|
||||
closes [#510](https://code.castopod.org/adaures/castopod/issues/510)
|
||||
- edit remap functions to get episode in episode admin controllers
|
||||
([9f74cca](https://code.castopod.org/adaures/castopod/commit/9f74cca342fedd896977efd2e89d0143959f3c4f))
|
||||
- **episode:** do not change slug when editing episode title
|
||||
([a83afb0](https://code.castopod.org/adaures/castopod/commit/a83afb0004511db80337806577fbc36f8d777116)),
|
||||
closes [#513](https://code.castopod.org/adaures/castopod/issues/513)
|
||||
- **fediverse:** add "processing" and "failed" statuses to better manage
|
||||
broadcast load
|
||||
([1d7583d](https://code.castopod.org/adaures/castopod/commit/1d7583d738219574ae3d45d294dc94e7e406472b)),
|
||||
closes [#511](https://code.castopod.org/adaures/castopod/issues/511)
|
||||
- **icons:** set correct names for lock and lock-unlock icons in premium banner
|
||||
([37ee6d3](https://code.castopod.org/adaures/castopod/commit/37ee6d35b4bb66ce23dc271fb846200d1be0e7f6))
|
||||
- **plugins:** clear cache after activating or deactivating plugin
|
||||
([08c7df2](https://code.castopod.org/adaures/castopod/commit/08c7df2a5d5be340490c78deeef823167eb1b2fc))
|
||||
- **plugins:** delete relevant cache when submitting settings
|
||||
([00bd4c0](https://code.castopod.org/adaures/castopod/commit/00bd4c02ee23b181d74e7731626bfec3b1ff4916))
|
||||
- **podcast-model:** always query podcast from database when clearing cache
|
||||
([d30c49c](https://code.castopod.org/adaures/castopod/commit/d30c49cdff380c15db4f1851631a255a5baffcbe))
|
||||
- **premium-podcasts:** update query to validate subscription
|
||||
([2b1bbf3](https://code.castopod.org/adaures/castopod/commit/2b1bbf34303ead927f433b5c7d5d888ca3799954))
|
||||
- **preview:** delete episode preview cache after editing episode
|
||||
([732d429](https://code.castopod.org/adaures/castopod/commit/732d42923d0d7a66ff1ebd5841458e4205060560)),
|
||||
closes [#514](https://code.castopod.org/adaures/castopod/issues/514)
|
||||
- **release:** add conventional-changelog-conventionalcommits for CHANGELOG
|
||||
generation
|
||||
([6934c8a](https://code.castopod.org/adaures/castopod/commit/6934c8aa8f0b7f9eea7c3f6f4089c56b2391d9a6))
|
||||
- **rss:** add subscription id to cache name to prevent premium feeds from
|
||||
overlapping
|
||||
([74f9325](https://code.castopod.org/adaures/castopod/commit/74f9325946d03a0d4efce57045e41cc9454ff97c))
|
||||
- set user as www-data when running cron jobs in docker's supervisord config
|
||||
([65d74f1](https://code.castopod.org/adaures/castopod/commit/65d74f14e612be3757c9304518eee112705f5ff9))
|
||||
- typo in EpisodeController remap function to get episode
|
||||
([f288a75](https://code.castopod.org/adaures/castopod/commit/f288a750f580ab19b04a170cc76bf8769084e19d))
|
||||
- update select and multi-select options to value/label arrays
|
||||
([63f93f5](https://code.castopod.org/adaures/castopod/commit/63f93f585bec4a11022cc8c75deb34968cba2348))
|
||||
|
||||
### Internal
|
||||
|
||||
- **plugins:** create Field objects per field type in settings forms + handle
|
||||
rendering in class
|
||||
([34be5bc](https://code.castopod.org/adaures/castopod/commit/34be5bccabb7531afdcc6ebaf1dd39e4dfbe0677))
|
||||
- remove fields from podcast and episode entities to be replaced with plugins
|
||||
([b869acb](https://code.castopod.org/adaures/castopod/commit/b869acb3a988a3616d883a41c25d9c8409bd5518))
|
||||
- rename controller methods for views and actions to be more consistent
|
||||
([85704bf](https://code.castopod.org/adaures/castopod/commit/85704bfbe03fe5e38ff5e76a0e1cf0e5f1275f57))
|
||||
- update CodeIgniter to v4.5.6
|
||||
([f295e9a](https://code.castopod.org/adaures/castopod/commit/f295e9aa4ca3129df24a22779f7c19bba7fac370))
|
||||
- update codigniter-icons to v1.0.1
|
||||
([fa6967e](https://code.castopod.org/adaures/castopod/commit/fa6967e65cef1705b19cbb205132c4c751507d53))
|
||||
- update js dependencies to latest
|
||||
([70c9797](https://code.castopod.org/adaures/castopod/commit/70c97971fcf5bbeee826578057ae0e3afbbbd8a8))
|
||||
|
||||
# [2.0.0-next.2](https://code.castopod.org/adaures/castopod/compare/v2.0.0-next.1...v2.0.0-next.2) (2024-07-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **audio-player:** set player icons to default instead of missing Castopod's
|
||||
([0ba0a25](https://code.castopod.org/adaures/castopod/commit/0ba0a25b11bd67aeeb47a8179b72152dfd4a36da))
|
||||
- broken icon call in frontend default pages template
|
||||
([3228362](https://code.castopod.org/adaures/castopod/commit/322836254e86be7878e21438177ee8f73f03a2fa))
|
||||
- **manifest:** set repository url as required in docstring typings
|
||||
([a8c81b3](https://code.castopod.org/adaures/castopod/commit/a8c81b3fa19a28dbd608027c231dcac31eafb38f))
|
||||
- set correct icons parameters in map and funding links views
|
||||
([5d35524](https://code.castopod.org/adaures/castopod/commit/5d355248753be24e3cf324144ff076f2fc23be88)),
|
||||
closes [#500](https://code.castopod.org/adaures/castopod/issues/500)
|
||||
|
||||
### Features
|
||||
|
||||
- **plugins:** add `minCastopodVersion` to denote incompatibility with previous
|
||||
Castopod versions
|
||||
([fc9ea75](https://code.castopod.org/adaures/castopod/commit/fc9ea7597e454e5c7c7af043d29af7bbe119e342))
|
||||
- **plugins:** load and display LICENSE.md file if found in plugin's directory
|
||||
([fee7905](https://code.castopod.org/adaures/castopod/commit/fee7905935a9adf963b4485b437fe4d972c14b5f))
|
||||
|
||||
# [2.0.0-next.1](https://code.castopod.org/adaures/castopod/compare/v1.11.0...v2.0.0-next.1) (6/19/2024)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- add missing php-icons config file to bundle
|
||||
([56612f0](https://code.castopod.org/adaures/castopod/commit/56612f0c762aa2d98e3c8c77fba88ffdf6f46a44))
|
||||
- **docs:** add base to og image using env variable
|
||||
([fe67659](https://code.castopod.org/adaures/castopod/commit/fe676590f23a33bdbe8905d234760923c029e350))
|
||||
- **import:** rewrite download_file helper to output curl response directly to
|
||||
file
|
||||
([eb7ad2f](https://code.castopod.org/adaures/castopod/commit/eb7ad2f7e1c0137f222f47e47062887de42c4824))
|
||||
- include app/Resources/icons folder to bundle
|
||||
([3fd5efc](https://code.castopod.org/adaures/castopod/commit/3fd5efc7956977acc19e53182f25b12813964a7d))
|
||||
- **platforms:** add platforms service + reduce memory consumption when
|
||||
rendering platform cards
|
||||
([fe73e9f](https://code.castopod.org/adaures/castopod/commit/fe73e9fae9ea5d5ce946680aec194308bb2e620c))
|
||||
- set owner email visibility when editing podcast
|
||||
([fc4f982](https://code.castopod.org/adaures/castopod/commit/fc4f9825568cd4384c5b3cfe972accd146548807)),
|
||||
closes [#473](https://code.castopod.org/adaures/castopod/issues/473)
|
||||
|
||||
### Build System
|
||||
|
||||
- release next major version as prerelease
|
||||
([8275226](https://code.castopod.org/adaures/castopod/commit/827522643e9f8a5ea9be05b4847dc637f0f43a13))
|
||||
|
||||
### Features
|
||||
|
||||
- add Plugins module with base files for plugins architecture
|
||||
([7253e13](https://code.castopod.org/adaures/castopod/commit/7253e13ac2118f6f165f54ea0cbcd63d51ab9205))
|
||||
- **plugins:** abstract settings form for general, podcast and episode types
|
||||
([b62b483](https://code.castopod.org/adaures/castopod/commit/b62b483ad9ff114a22a9ee52e1a1a2c9fa444d42))
|
||||
- **plugins:** activate / deactivate plugin using settings table
|
||||
([27d2a1b](https://code.castopod.org/adaures/castopod/commit/27d2a1b0ffba9454dd54cbb4251a2d179b09762a))
|
||||
- **plugins:** add aside with plugin metadata next to plugin's readme
|
||||
([dfb7888](https://code.castopod.org/adaures/castopod/commit/dfb7888aeb689b4066abc37084e08cd7f1d0f15d))
|
||||
- **plugins:** add before channel/item hooks to allow podcast/episode data edit
|
||||
when generating rss
|
||||
([80d2c48](https://code.castopod.org/adaures/castopod/commit/80d2c48ee265cb32ed0d710c488292fcbc120044))
|
||||
- **plugins:** add json schema definition for plugin manifest
|
||||
([b5eddf3](https://code.castopod.org/adaures/castopod/commit/b5eddf351f6f6fa1c299fbac31cbd056ef232330))
|
||||
- **plugins:** add methods to easily retrieve general, podcast and episode
|
||||
settings in hooks methods
|
||||
([3a900bb](https://code.castopod.org/adaures/castopod/commit/3a900bbab68b819cedf8943540d2ee0aeb6e8539))
|
||||
- **plugins:** add new field types + validate & cast user data before storing
|
||||
settings
|
||||
([6f833fc](https://code.castopod.org/adaures/castopod/commit/6f833fc76a3aa6c6b87c27ad18a2fb90e537e21e))
|
||||
- **plugins:** add options to manifest for building forms and storing plugin
|
||||
settings
|
||||
([3d8aedf](https://code.castopod.org/adaures/castopod/commit/3d8aedf9c34e6927b6d3b11445d5f0e669b347d7))
|
||||
- **plugins:** add settings page for podcast and episode if defined in the
|
||||
plugin's manifest
|
||||
([89ac92f](https://code.castopod.org/adaures/castopod/commit/89ac92fb412a04231ce52fd6480c9ab893b19ef5))
|
||||
- **plugins:** add siteHead hook to add custom meta tags to public pages
|
||||
([e80a33b](https://code.castopod.org/adaures/castopod/commit/e80a33bf2ad4fe1b47037add7470a6c2770f4036))
|
||||
- **plugins:** display errors when plugin is invalid instead of crashing
|
||||
([8ec7909](https://code.castopod.org/adaures/castopod/commit/8ec79097bbdbcbce622518ef61c068f20e0ef74e))
|
||||
- **plugins:** handle empty states and long strings in UI
|
||||
([45ac2a4](https://code.castopod.org/adaures/castopod/commit/45ac2a4be96532b9456e6af1d26ba4ada3649303))
|
||||
- **plugins:** load and validate plugin manifest.json
|
||||
([1510e36](https://code.castopod.org/adaures/castopod/commit/1510e36c0acd2b254622ec230acd1d2461ee9bf3))
|
||||
- **plugins:** load plugins using file locator service
|
||||
([587938d](https://code.castopod.org/adaures/castopod/commit/587938d2bf307b823af143586b9ec9e9b44e8dc1))
|
||||
- **plugins:** load README.md file to view plugin's instructions in UI
|
||||
([e6bfdfc](https://code.castopod.org/adaures/castopod/commit/e6bfdfc3902705285701c13c8067fe0f538425c6))
|
||||
- **plugins:** register plugins using Plugin.php file instead of namespace +
|
||||
simplify i18n structure
|
||||
([2035c39](https://code.castopod.org/adaures/castopod/commit/2035c39fd138a1fd408516bd1972ab6a02544c10))
|
||||
- **plugins:** uninstall plugins via CLI and admin UI
|
||||
([9a80de4](https://code.castopod.org/adaures/castopod/commit/9a80de40686bbf4288da21cc2a6dde8036580e47))
|
||||
- set owner email to hidden by default in podcast create form
|
||||
([7a6d9df](https://code.castopod.org/adaures/castopod/commit/7a6d9df6db8a6184b8250ced0475f3e741dde7f4))
|
||||
- support podcast:txt tag with verify use case
|
||||
([57e459e](https://code.castopod.org/adaures/castopod/commit/57e459e187ed048430f4137172e22396cd02bf81)),
|
||||
closes [#468](https://code.castopod.org/adaures/castopod/issues/468)
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
- next major release including plugins architecture
|
||||
|
||||
# [1.11.0](https://code.castopod.org/adaures/castopod/compare/v1.10.5...v1.11.0) (4/17/2024)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **premium:** set itunes:block on premium feeds to prevent indexing
|
||||
([88851b0](https://code.castopod.org/adaures/castopod/commit/88851b022663d575a816f0e2f33f0353767dd52d))
|
||||
- **rss:** generate podcast guid if empty
|
||||
([a5aef2a](https://code.castopod.org/adaures/castopod/commit/a5aef2a63e464632f3941649d455672835989e6c)),
|
||||
closes [#450](https://code.castopod.org/adaures/castopod/issues/450)
|
||||
|
||||
### Features
|
||||
|
||||
- add trailer tags to rss if trailer episodes are present
|
||||
([80fdd9c](https://code.castopod.org/adaures/castopod/commit/80fdd9cfb4a95feac6ed0000435a013fc83e6892))
|
||||
- add transcript display to episode page
|
||||
([4d141fc](https://code.castopod.org/adaures/castopod/commit/4d141fceae56fa9e666b42c32a830ff9c68989db)),
|
||||
closes [#411](https://code.castopod.org/adaures/castopod/issues/411)
|
||||
- **platforms:** add telegram to socials
|
||||
([004f804](https://code.castopod.org/adaures/castopod/commit/004f804045cd8e884361bb4318109fbdd7afc9a8))
|
||||
- **platforms:** add truefans.fm and episodes.fm
|
||||
([d046ecc](https://code.castopod.org/adaures/castopod/commit/d046ecc52f6ccd41d09f6de48e00d2c61d25d7f0)),
|
||||
closes [#458](https://code.castopod.org/adaures/castopod/issues/458)
|
||||
[#459](https://code.castopod.org/adaures/castopod/issues/459)
|
||||
|
||||
## [1.10.5](https://code.castopod.org/adaures/castopod/compare/v1.10.4...v1.10.5) (3/12/2024)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **file-uploads:** validate chapters json content + remove permit_empty rule to
|
||||
uploaded files
|
||||
([6289c42](https://code.castopod.org/adaures/castopod/commit/6289c42b1189f074c7e4e4cd9fbfd73bf26625c9)),
|
||||
closes [#445](https://code.castopod.org/adaures/castopod/issues/445)
|
||||
|
||||
## [1.10.4](https://code.castopod.org/adaures/castopod/compare/v1.10.3...v1.10.4) (2/26/2024)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- display chapters in episode preview page
|
||||
([797516a](https://code.castopod.org/adaures/castopod/commit/797516a2ec7d88704412a5cca50421e8eef38eec)),
|
||||
closes [#445](https://code.castopod.org/adaures/castopod/issues/445)
|
||||
|
||||
## [1.10.3](https://code.castopod.org/adaures/castopod/compare/v1.10.2...v1.10.3) (2/21/2024)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **chapters:** use episode cover when chapter img is an empty string
|
||||
([a343de4](https://code.castopod.org/adaures/castopod/commit/a343de4cf6ba38561b8fe675fa9c38d9f0ecfec7)),
|
||||
closes [#444](https://code.castopod.org/adaures/castopod/issues/444)
|
||||
- **import:** set episodes as premium if podcast is set as premium by default
|
||||
([dfd66be](https://code.castopod.org/adaures/castopod/commit/dfd66beebfcca1670b0a9d389e8e3f8d2d08d2f2))
|
||||
|
||||
## [1.10.2](https://code.castopod.org/adaures/castopod/compare/v1.10.1...v1.10.2) (2/20/2024)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **podcast-import:** move closing parenthasis when checking for owner name and
|
||||
email existence
|
||||
([cec7815](https://code.castopod.org/adaures/castopod/commit/cec78155f94a222edcf7964c0a2f3a3e0f46a98d))
|
||||
|
||||
## [1.10.1](https://code.castopod.org/adaures/castopod/compare/v1.10.0...v1.10.1) (2/20/2024)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **fediverse:** use config name to get Fediverse config properties instead of
|
||||
hardcoded class string
|
||||
([5fd0980](https://code.castopod.org/adaures/castopod/commit/5fd0980ff7101d45051a2daa3f635694f85609d7))
|
||||
|
||||
# [1.10.0](https://code.castopod.org/adaures/castopod/compare/v1.9.0...v1.10.0) (2/19/2024)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **op3:** move op3 prefix to enclosure url instead of audio proxy
|
||||
([d580369](https://code.castopod.org/adaures/castopod/commit/d5803692357952d82d54efd8d3aa71de3a1c9571))
|
||||
- **podcast-import:** rollback transaction before exception is thrown
|
||||
([419bb04](https://code.castopod.org/adaures/castopod/commit/419bb04716088586b87b2c8f24a954ca8cfd6c76)),
|
||||
closes [#429](https://code.castopod.org/adaures/castopod/issues/429)
|
||||
[#319](https://code.castopod.org/adaures/castopod/issues/319)
|
||||
[#443](https://code.castopod.org/adaures/castopod/issues/443)
|
||||
[#438](https://code.castopod.org/adaures/castopod/issues/438)
|
||||
|
||||
### Features
|
||||
|
||||
- add podcast:season and podcast:episode tags to rss feed
|
||||
([98c6658](https://code.castopod.org/adaures/castopod/commit/98c6658840eedd55bd6d8042f8a69c342b87cd71))
|
||||
- add support for podcasting 2.0 "medium" tag with podcast, music and audiobook
|
||||
([630e788](https://code.castopod.org/adaures/castopod/commit/630e788f0e1ddfe5de229bd415a8e15361efa746)),
|
||||
closes [#439](https://code.castopod.org/adaures/castopod/issues/439)
|
||||
- display chapters in episode's public page
|
||||
([87cc437](https://code.castopod.org/adaures/castopod/commit/87cc437e1ead5486ed46ca37e2055aaf5c9445c1)),
|
||||
closes [#423](https://code.castopod.org/adaures/castopod/issues/423)
|
||||
- support VTT transcript file format in addition to SRT
|
||||
([7071b4b](https://code.castopod.org/adaures/castopod/commit/7071b4b6f48cb9a2f766064f3a5c23f92b293718)),
|
||||
closes [#433](https://code.castopod.org/adaures/castopod/issues/433)
|
||||
|
||||
# [1.9.0](https://code.castopod.org/adaures/castopod/compare/v1.8.2...v1.9.0) (1/31/2024)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **i18n:** escape language strings in form fields to prevent them from
|
||||
disappearing
|
||||
([3cb5ffd](https://code.castopod.org/adaures/castopod/commit/3cb5ffd25b9604a83cd12935e641dab7c88fba47)),
|
||||
closes [#412](https://code.castopod.org/adaures/castopod/issues/412)
|
||||
- **podcast-about:** update stats query to discard scheduled episodes from
|
||||
episodes number
|
||||
([67c037c](https://code.castopod.org/adaures/castopod/commit/67c037c9eb1e15c6945eaf74ec0ff30b33f4b704))
|
||||
- **premium-subs:** clear subscription list cache after insert
|
||||
([2accb0f](https://code.castopod.org/adaures/castopod/commit/2accb0f7652330b29c3adb85a2e1b0d5d83f1389)),
|
||||
closes [#430](https://code.castopod.org/adaures/castopod/issues/430)
|
||||
- **s3:** remove proxy, set objects acl to public-read, and serve files using
|
||||
their public urls
|
||||
([6a77a9d](https://code.castopod.org/adaures/castopod/commit/6a77a9d2f29c849775a3d1bcbd819f73f21d9aa6))
|
||||
|
||||
### Features
|
||||
|
||||
- add actor domain to handle in follow page
|
||||
([de099ac](https://code.castopod.org/adaures/castopod/commit/de099ac64300b8edb86e387fde89c0a3e9472f46))
|
||||
- **admin:** add podcast's OP3 analytics dashboard link
|
||||
([5f3752b](https://code.castopod.org/adaures/castopod/commit/5f3752b4430f6f2d5f9e5f6a7a003bc4d2f9d487))
|
||||
|
||||
## [1.8.2](https://code.castopod.org/adaures/castopod/compare/v1.8.1...v1.8.2) (1/17/2024)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **transcript:** add condition when concatenating sub text to prevent second
|
||||
line duplication
|
||||
([6cbfec0](https://code.castopod.org/adaures/castopod/commit/6cbfec0d7d9bf85c8014d379026648857ea13373))
|
||||
|
||||
## [1.8.1](https://code.castopod.org/adaures/castopod/compare/v1.8.0...v1.8.1) (1/16/2024)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **models:** set updatedField as empty string when not used
|
||||
([164f4d3](https://code.castopod.org/adaures/castopod/commit/164f4d3be74ec8d371fb40d7fe730f7b2940ca05))
|
||||
|
||||
# [1.8.0](https://code.castopod.org/adaures/castopod/compare/v1.7.4...v1.8.0) (1/15/2024)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **episode-form:** add required validation rules for title and slug
|
||||
([30a3473](https://code.castopod.org/adaures/castopod/commit/30a34738635bf4f4a4c6b2a7174f7e439f0dfc6e)),
|
||||
closes [#420](https://code.castopod.org/adaures/castopod/issues/420)
|
||||
- **import:** check for empty string when generating podcast guid for feeds not
|
||||
including one
|
||||
([ac5336f](https://code.castopod.org/adaures/castopod/commit/ac5336fbc5fb8038de541dd06938a8beb2e8d733))
|
||||
- **install:** add created superadmin to most powerful group in instance, ie.
|
||||
superadmin
|
||||
([2ed511f](https://code.castopod.org/adaures/castopod/commit/2ed511f8a0005dc06eda5afd6b1d13beee1eb9dd))
|
||||
- **persons:** delete person avatar when deleting a person
|
||||
([c1ec98c](https://code.castopod.org/adaures/castopod/commit/c1ec98c95656844712011ff30b84c397b78da311)),
|
||||
closes [#419](https://code.castopod.org/adaures/castopod/issues/419)
|
||||
- **platforms:** add matrix.org as a social platform
|
||||
([9178c3f](https://code.castopod.org/adaures/castopod/commit/9178c3f3afa16e104d25ae159728e90a3bbd57c3)),
|
||||
closes [#421](https://code.castopod.org/adaures/castopod/issues/421)
|
||||
|
||||
### Features
|
||||
|
||||
- **admin:** add tooltip for not authorized routes
|
||||
([f7f9baf](https://code.castopod.org/adaures/castopod/commit/f7f9bafc3e56621fab2569d9d76baafe0a2e940d))
|
||||
- **admin:** emphasize unprivileged items in sidebar with "prohibited" icon
|
||||
([0bd7dde](https://code.castopod.org/adaures/castopod/commit/0bd7ddea58adf502121b83e5c09317e20912fb4e))
|
||||
- allow hiding owner's email in public RSS feed
|
||||
([222e02a](https://code.castopod.org/adaures/castopod/commit/222e02a2af9ecb8b8768a63d3054f4c3ef54e991))
|
||||
- **persons:** order persons by full_name ASC for easier list scanning
|
||||
([68a599f](https://code.castopod.org/adaures/castopod/commit/68a599fee08c71763b9336e14b1c0d9e28c4449b)),
|
||||
closes [#418](https://code.castopod.org/adaures/castopod/issues/418)
|
||||
|
||||
## [1.7.4](https://code.castopod.org/adaures/castopod/compare/v1.7.3...v1.7.4) (1/3/2024)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **media:** add missing HEAD route for static assets served with S3
|
||||
([b61a32c](https://code.castopod.org/adaures/castopod/commit/b61a32c8a9b10e129666804d533487430ce7432c))
|
||||
|
||||
## [1.7.3](https://code.castopod.org/adaures/castopod/compare/v1.7.2...v1.7.3) (12/21/2023)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **analytics:** upgrade opawg's user-agents-php to user-agents-v2-php
|
||||
([8cd7886](https://code.castopod.org/adaures/castopod/commit/8cd78866762e26aa63c224dace6c247e0e9dc068))
|
||||
- **platforms:** add Threads and YouTube Music
|
||||
([9264a2d](https://code.castopod.org/adaures/castopod/commit/9264a2d74cc95278c9d84c99ef914fdbcaf8a97f))
|
||||
|
||||
## [1.7.2](https://code.castopod.org/adaures/castopod/compare/v1.7.1...v1.7.2) (12/12/2023)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **episode-form:** render episode number optional when episode type is trailer
|
||||
or bonus
|
||||
([694328f](https://code.castopod.org/adaures/castopod/commit/694328f10865b2fcd6436122de46866dae81f945))
|
||||
|
||||
## [1.7.1](https://code.castopod.org/adaures/castopod/compare/v1.7.0...v1.7.1) (12/1/2023)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **housekeeping:** add where clause to check episode_id is not null on reset
|
||||
comments count
|
||||
([119742c](https://code.castopod.org/adaures/castopod/commit/119742cdbb2c2f7f847692fb76f6ff1dbb2e25b6))
|
||||
|
||||
# [1.7.0](https://code.castopod.org/adaures/castopod/compare/v1.6.5...v1.7.0) (11/29/2023)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **admin-ux:** hide navigation submenus in details panel for easier scanning
|
||||
([b047a3c](https://code.castopod.org/adaures/castopod/commit/b047a3c6707114d04c276758f2e543eef90d72f5))
|
||||
- **admin:** remove episode title truncation + display description in two lines
|
||||
in episode list
|
||||
([f4ffa30](https://code.castopod.org/adaures/castopod/commit/f4ffa30ec4341f43e22b1f983781ad04c956aa25)),
|
||||
closes [#386](https://code.castopod.org/adaures/castopod/issues/386)
|
||||
- **auth:** display error messages from validator
|
||||
([5a834c0](https://code.castopod.org/adaures/castopod/commit/5a834c0f8957fc016e73325a3c3ff05e524d0755))
|
||||
- **housekeeping:** remove unnecessary $tablePrefix variable when resetting post
|
||||
count
|
||||
([97d793f](https://code.castopod.org/adaures/castopod/commit/97d793f55e7eb3b049980e5081950baa2bb1b881)),
|
||||
closes [#383](https://code.castopod.org/adaures/castopod/issues/383)
|
||||
- **import:** handle bad values for location attributes
|
||||
([642981f](https://code.castopod.org/adaures/castopod/commit/642981fd358ccf118d3d7a957fb6be7933c016ac))
|
||||
- **import:** use cocur/slugify library to handle non latin text
|
||||
([4ca7f9c](https://code.castopod.org/adaures/castopod/commit/4ca7f9ccae1e352bf26a3b6db4de73bac7b84382))
|
||||
- move monetization outside of podcast form + add broadcast section to podcast
|
||||
menu
|
||||
([dff8516](https://code.castopod.org/adaures/castopod/commit/dff85168b32a6df77425ef51865588ebcd8b8ba9))
|
||||
- **nodeinfo2:** import database config + use dynamic table prefix for active
|
||||
local actors query
|
||||
([6a7ef01](https://code.castopod.org/adaures/castopod/commit/6a7ef0109a6e52144ca687b979ffe56fba66165b))
|
||||
- **persons:** set roles field as optional + set `Cast > Host` as default value
|
||||
([02132dc](https://code.castopod.org/adaures/castopod/commit/02132dc46640807e2bc4cfc406c911fa097f36fe)),
|
||||
closes [#347](https://code.castopod.org/adaures/castopod/issues/347)
|
||||
- **platforms:** make platforms' websites and submit urls more prominent
|
||||
([61cf8fa](https://code.castopod.org/adaures/castopod/commit/61cf8fa3e2435ee2a9bdd8e711b8d69d4ca4ec4c))
|
||||
- **podcast-form:** move fediverse section below author section
|
||||
([1861d67](https://code.castopod.org/adaures/castopod/commit/1861d67971e2cc0c20ace091f037f6436437a50d))
|
||||
- reorder podcast form fields + extract sync feeds to its own form
|
||||
([2d52fa1](https://code.castopod.org/adaures/castopod/commit/2d52fa1046faf1b8d81304e35fc24a7874315e6e))
|
||||
|
||||
### Features
|
||||
|
||||
- **admin:** add rss feed link to podcast side navigation
|
||||
([18e2633](https://code.castopod.org/adaures/castopod/commit/18e2633a49dbbeb57a685f129a2ab158397de61e))
|
||||
- **icons:** update new Deezer logo
|
||||
([f2d5b27](https://code.castopod.org/adaures/castopod/commit/f2d5b272ac385a978d7e173121faafe03d7a7200))
|
||||
- **install:** init database and create superadmin using CLI
|
||||
([02d4ba6](https://code.castopod.org/adaures/castopod/commit/02d4ba69ac007ebd1eccab428a98b54051aaf70c)),
|
||||
closes [#380](https://code.castopod.org/adaures/castopod/issues/380)
|
||||
- **ux:** add episode description to episode cards
|
||||
([5f8d413](https://code.castopod.org/adaures/castopod/commit/5f8d413b84b236077a75934da9409f37d34cb4a5))
|
||||
|
||||
## [1.6.5](https://code.castopod.org/adaures/castopod/compare/v1.6.4...v1.6.5) (2023-09-26)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **fediverse:** use NoteObject including episode link in content (hotfix)
|
||||
([ffa530e](https://code.castopod.org/adaures/castopod/commit/ffa530e187ff6488648a7cf749ca0173765a5d87))
|
||||
|
||||
## [1.6.4](https://code.castopod.org/adaures/castopod/compare/v1.6.3...v1.6.4) (2023-09-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **fediverse:** do not cache remote action form + fix typo on post routes for
|
||||
passing post uuid
|
||||
([4ecb42f](https://code.castopod.org/adaures/castopod/commit/4ecb42f7c82eb8d41d27c7b9705b3278ea04ab79))
|
||||
- **fediverse:** update post controller namespace in routes
|
||||
([3189f12](https://code.castopod.org/adaures/castopod/commit/3189f122067dc47d6de93c3185aca66d7df95e1a))
|
||||
|
||||
## [1.6.3](https://code.castopod.org/adaures/castopod/compare/v1.6.2...v1.6.3) (2023-09-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **fediverse:** add `index` to post controller-method to access post's jsonld
|
||||
contents
|
||||
([35142d8](https://code.castopod.org/adaures/castopod/commit/35142d8e565e828a977ba2b4de77c1b47a633beb))
|
||||
|
||||
## [1.6.2](https://code.castopod.org/adaures/castopod/compare/v1.6.1...v1.6.2) (2023-09-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **migrations:** remove if exists modifier for drop index
|
||||
([82013c9](https://code.castopod.org/adaures/castopod/commit/82013c9cde901c54fdb3a833890aa693e8542627)),
|
||||
closes [#382](https://code.castopod.org/adaures/castopod/issues/382)
|
||||
|
||||
## [1.6.1](https://code.castopod.org/adaures/castopod/compare/v1.6.0...v1.6.1) (2023-09-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **admin:** redirect root fediverse route to fediverse-blocked-actors
|
||||
([ba5324e](https://code.castopod.org/adaures/castopod/commit/ba5324ea1942a3939f186e974d29fb393c54b253))
|
||||
- **analytics:** show full referrer domain in web pages visits reports
|
||||
([6be38e9](https://code.castopod.org/adaures/castopod/commit/6be38e9fda3d1436d81686e1a3a5e5b173e390a0)),
|
||||
closes [#367](https://code.castopod.org/adaures/castopod/issues/367)
|
||||
- **auth:** overwrite Shield's PermissionFilter
|
||||
([c6e8000](https://code.castopod.org/adaures/castopod/commit/c6e8000bab54f4a32068578f750f4cf9d91bad89))
|
||||
- **auth:** update shield from v1.0.0-beta.3 to v1.0.0-beta.6
|
||||
([23842df](https://code.castopod.org/adaures/castopod/commit/23842df03ae28e416390e2436442b8e7c8340333))
|
||||
- **platforms:** add missing tiktok to social platforms seed
|
||||
([8dfdaf3](https://code.castopod.org/adaures/castopod/commit/8dfdaf321566050e9c53683e70864871eb55d618))
|
||||
- remove fediverse prefix to prevent migration error + load routes during
|
||||
podcast import
|
||||
([7ff1dbe](https://code.castopod.org/adaures/castopod/commit/7ff1dbe9030768074b2fe7c7f570bfb9e7336f62))
|
||||
- **routes:** overwrite RouteCollection to include all routes + update js and
|
||||
php dependencies
|
||||
([b4f1b91](https://code.castopod.org/adaures/castopod/commit/b4f1b916bfec53f071e8d0d900081c6d74486e53))
|
||||
- update Router to include latest CI changes with alternate-content logic
|
||||
([ae57601](https://code.castopod.org/adaures/castopod/commit/ae57601c838a7aa9469bae8038ac1c30d8c9a51e))
|
||||
- use podcast-activity named route instead of not existing actor route
|
||||
([3c35718](https://code.castopod.org/adaures/castopod/commit/3c357183ca51545787fcfc801b4a5829d9cd8ad6))
|
||||
|
||||
# [1.6.0](https://code.castopod.org/adaures/castopod/compare/v1.5.2...v1.6.0) (2023-08-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **home:** update where clause when getting all podcasts to prevent draft
|
||||
podcasts from showing up
|
||||
([7a1eea5](https://code.castopod.org/adaures/castopod/commit/7a1eea58d3cbc1982baaec21d87a36e218e1910a))
|
||||
- **media:** copy and delete temp file when saving instead of moving it for FS
|
||||
FileManager
|
||||
([9346e78](https://code.castopod.org/adaures/castopod/commit/9346e787bd2a2c815533092279f96ae1fe0d9aae)),
|
||||
closes [#338](https://code.castopod.org/adaures/castopod/issues/338)
|
||||
- **media:** get path using media_path_absolute when saving media file
|
||||
([754e7a6](https://code.castopod.org/adaures/castopod/commit/754e7a6b4b2c12cf50c1c8b166732dc3255f36fb))
|
||||
- **media:** init file properties in setAttributes' Model method + set defaults
|
||||
to pathinfo data
|
||||
([0775add](https://code.castopod.org/adaures/castopod/commit/0775add67860b94a35b68c01b133ec8ec969f539))
|
||||
- **premium-podcasts:** show premium flag only when podcast has published
|
||||
premium episodes
|
||||
([d10c4fd](https://code.castopod.org/adaures/castopod/commit/d10c4fd7538e6af8a5b0eb232a06522fe8c4bf8e))
|
||||
- **s3:** add a flag to serve media files by redirecting to a presigned url
|
||||
instead of default proxy
|
||||
([11aa358](https://code.castopod.org/adaures/castopod/commit/11aa3586a04c166404954600235634cee77219df))
|
||||
|
||||
### Features
|
||||
|
||||
- **episode:** add preview link in admin to view and share episode before
|
||||
publication
|
||||
([7d21b35](https://code.castopod.org/adaures/castopod/commit/7d21b3509ec5d1aa65420efa038f44bcd235e64f))
|
||||
|
||||
## [1.5.2](https://code.castopod.org/adaures/castopod/compare/v1.5.1...v1.5.2) (2023-07-31)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **credits:** remove undefined $podcast variable from page layout
|
||||
([73a5b68](https://code.castopod.org/adaures/castopod/commit/73a5b680875cc520fd15c529c01d44df728f9be2)),
|
||||
closes [#359](https://code.castopod.org/adaures/castopod/issues/359)
|
||||
- **platforms:** change twitter to X + add buymeacoffee and kofi as funding
|
||||
([d69b4e4](https://code.castopod.org/adaures/castopod/commit/d69b4e4857fcb1ac1c05ac59c78d130788f00400)),
|
||||
closes [#353](https://code.castopod.org/adaures/castopod/issues/353)
|
||||
[#361](https://code.castopod.org/adaures/castopod/issues/361)
|
||||
|
||||
## [1.5.1](https://code.castopod.org/adaures/castopod/compare/v1.5.0...v1.5.1) (2023-07-29)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **admin-ui:** remove button labels on smaller screens in podcast view
|
||||
([9cc5ffd](https://code.castopod.org/adaures/castopod/commit/9cc5ffd1439fdc86f46a03f4319cae32db95f84e))
|
||||
- **rss:** set srt transcripts' mimetype to application/x-subrip with
|
||||
rel="captions" attribute
|
||||
([16a3fdb](https://code.castopod.org/adaures/castopod/commit/16a3fdb56e3f07185e75d106216f29519ccb25f7)),
|
||||
closes [#360](https://code.castopod.org/adaures/castopod/issues/360)
|
||||
- **rss:** update podcast extension namespace
|
||||
([6833dd0](https://code.castopod.org/adaures/castopod/commit/6833dd05ab51bc530d34fd4174ad732f623226c0)),
|
||||
closes [#360](https://code.castopod.org/adaures/castopod/issues/360)
|
||||
|
||||
# [1.5.0](https://code.castopod.org/adaures/castopod/compare/v1.4.7...v1.5.0) (2023-07-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **admin-ui:** truncate header title + remove sticky podcast banner card on
|
||||
mobile
|
||||
([63c20da](https://code.castopod.org/adaures/castopod/commit/63c20da5ffd500265f06fa38f2b2c963e14602af))
|
||||
|
||||
### Features
|
||||
|
||||
- add podcast links page including social, podcasting and funding links
|
||||
([8ae2929](https://code.castopod.org/adaures/castopod/commit/8ae292933af15fa99856582ac24e985bfef37d5b))
|
||||
|
||||
## [1.4.7](https://code.castopod.org/adaures/castopod/compare/v1.4.6...v1.4.7) (2023-07-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **s3:** allow CORS for served static files
|
||||
([9b955c9](https://code.castopod.org/adaures/castopod/commit/9b955c9ce25a06a9102b67ebe77375dc45d28f0f))
|
||||
|
||||
## [1.4.6](https://code.castopod.org/adaures/castopod/compare/v1.4.5...v1.4.6) (2023-07-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **fediverse:** expand object before sending accept follow request
|
||||
([082cdc9](https://code.castopod.org/adaures/castopod/commit/082cdc9ee79d004c2ed748e3b8046e9141bf0242)),
|
||||
closes [#350](https://code.castopod.org/adaures/castopod/issues/350)
|
||||
- **podcast-import:** remove error log when no import in queue, exit with
|
||||
success instead
|
||||
([5e719f3](https://code.castopod.org/adaures/castopod/commit/5e719f3e9eb6cf48c3fd8ac97181638b24d03fc9))
|
||||
|
||||
## [1.4.5](https://code.castopod.org/adaures/castopod/compare/v1.4.4...v1.4.5) (2023-07-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **s3:** handle range requests to serve media files
|
||||
([41a5932](https://code.castopod.org/adaures/castopod/commit/41a59322332c835808a32987aaf8ec6cafbf5fca))
|
||||
|
||||
## [1.4.4](https://code.castopod.org/adaures/castopod/compare/v1.4.3...v1.4.4) (2023-07-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **audio-clipper:** init segment position on firstUpdate + improve UX by adding
|
||||
ghost handle
|
||||
([aa68386](https://code.castopod.org/adaures/castopod/commit/aa683866671d14c0b9a11b09c74eb132673e5547)),
|
||||
closes [#351](https://code.castopod.org/adaures/castopod/issues/351)
|
||||
- set resized images to 72dpi for compatibility with Apple Podcasts
|
||||
([0b327cb](https://code.castopod.org/adaures/castopod/commit/0b327cb4d9c92d0ae227a0f08ede3b29390df172)),
|
||||
closes [#282](https://code.castopod.org/adaures/castopod/issues/282)
|
||||
|
||||
## [1.4.3](https://code.castopod.org/adaures/castopod/compare/v1.4.2...v1.4.3) (2023-06-29)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **video-clipper:** add -t option to ffmpeg command to stop generation after
|
||||
duration
|
||||
([60814b8](https://code.castopod.org/adaures/castopod/commit/60814b8d202419c2bdbf6abb7c2bde447537b7e9)),
|
||||
closes [#341](https://code.castopod.org/adaures/castopod/issues/341)
|
||||
|
||||
## [1.4.2](https://code.castopod.org/adaures/castopod/compare/v1.4.1...v1.4.2) (2023-06-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **fediverse:** check that actor's images mimetype is present or guess it
|
||||
otherwise
|
||||
([06c4f15](https://code.castopod.org/adaures/castopod/commit/06c4f15477a568407a3d3c1e5e489bc0241bc1e9)),
|
||||
closes [#348](https://code.castopod.org/adaures/castopod/issues/348)
|
||||
- **podcast-import:** show cancel or retry action depending on task status
|
||||
([e42258d](https://code.castopod.org/adaures/castopod/commit/e42258de1f331aac0cbb380b80cd8fc7f9d7dc18))
|
||||
|
||||
## [1.4.1](https://code.castopod.org/adaures/castopod/compare/v1.4.0...v1.4.1) (2023-06-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **podcast-import:** set default values for person group and role if not found
|
||||
in taxonomy
|
||||
([aa46dca](https://code.castopod.org/adaures/castopod/commit/aa46dca4e399bf2e544d62dcb4a9a0328e4e6c41))
|
||||
|
||||
# [1.4.0](https://code.castopod.org/adaures/castopod/compare/v1.3.5...v1.4.0) (2023-06-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **charts:** set duration charts label to HHhMM for listening time analytics
|
||||
([3fc1d8e](https://code.castopod.org/adaures/castopod/commit/3fc1d8e18dc8119251c72dcaa7e5121246c2b194))
|
||||
- **embed:** set height of player iframe from config
|
||||
([4665741](https://code.castopod.org/adaures/castopod/commit/4665741425532f253a46a42ba05602047798dba2))
|
||||
- **s3:** serve files without cache if dummy cache handler + add http referer
|
||||
header to redirect
|
||||
([30db9f0](https://code.castopod.org/adaures/castopod/commit/30db9f0667bf7f7a5f186ea667a524d1e3b502db))
|
||||
- **s3:** use presigned request uri to serve static files
|
||||
([cb92dc7](https://code.castopod.org/adaures/castopod/commit/cb92dc73f17543d32d1cdc24db72403a5c561a74))
|
||||
- **webmanifest:** import misc helper to get site_icon_url
|
||||
([548a11d](https://code.castopod.org/adaures/castopod/commit/548a11d501749fa61ef894fd8818abae5668554f))
|
||||
|
||||
### Features
|
||||
|
||||
- **import:** run podcast imports' processes asynchronously using tasks
|
||||
([d8e1d40](https://code.castopod.org/adaures/castopod/commit/d8e1d4031d86de9a3889b74ae2a6d9c90af8a1da))
|
||||
- **rest-api:** add endpoints for episodes and full text search for podcasts and
|
||||
episodes
|
||||
([85505d4](https://code.castopod.org/adaures/castopod/commit/85505d4b3181c96bc91619e3ab9b0601f8e1c120)),
|
||||
closes [#296](https://code.castopod.org/adaures/castopod/issues/296)
|
||||
|
||||
## [1.3.5](https://code.castopod.org/adaures/castopod/compare/v1.3.4...v1.3.5) (2023-05-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- replace essence with embera to create preview cards
|
||||
([c682f03](https://code.castopod.org/adaures/castopod/commit/c682f03a67c6c0ebbcc6ff45d9a037f6f9823bde))
|
||||
|
||||
## [1.3.4](https://code.castopod.org/adaures/castopod/compare/v1.3.3...v1.3.4) (2023-05-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **import-update:** insert episodes incrementally into database
|
||||
([108fdf8](https://code.castopod.org/adaures/castopod/commit/108fdf84b8dd458fc71a06a77d14069287ab8e42))
|
||||
|
||||
## [1.3.3](https://code.castopod.org/adaures/castopod/compare/v1.3.2...v1.3.3) (2023-04-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- unnescape podcast title special characters in "find us on" section
|
||||
([f727276](https://code.castopod.org/adaures/castopod/commit/f727276f820a8ef2c47947f40a37a4a157b509ef)),
|
||||
closes [#323](https://code.castopod.org/adaures/castopod/issues/323)
|
||||
- **websub:** add missing misc helper import
|
||||
([855aacc](https://code.castopod.org/adaures/castopod/commit/855aacce0bf3841a876cd593e668e116149080aa))
|
||||
|
||||
## [1.3.2](https://code.castopod.org/adaures/castopod/compare/v1.3.1...v1.3.2) (2023-04-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- remove path key when getting default avatar path
|
||||
([c5a1359](https://code.castopod.org/adaures/castopod/commit/c5a1359218d61c0f78006f2bd5785e317f32bade))
|
||||
- **s3:** serve files using media base url to allow for CDN setup
|
||||
([502f53c](https://code.castopod.org/adaures/castopod/commit/502f53c9701da3b8da2caef1eb54df25b7d2d86a))
|
||||
|
||||
## [1.3.1](https://code.castopod.org/adaures/castopod/compare/v1.3.0...v1.3.1) (2023-04-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **s3:** add proxy to serve images from s3 to client
|
||||
([a76724a](https://code.castopod.org/adaures/castopod/commit/a76724a8cfee700f6874f86b35616d61facc664e)),
|
||||
closes [#321](https://code.castopod.org/adaures/castopod/issues/321)
|
||||
|
||||
# [1.3.0](https://code.castopod.org/adaures/castopod/compare/v1.2.4...v1.3.0) (2023-04-03)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- delete files using file_manager when deleting episode and podcast
|
||||
([41d8efe](https://code.castopod.org/adaures/castopod/commit/41d8efe6e71566eba44bfdfd00d1708ac4338366))
|
||||
|
||||
### Features
|
||||
|
||||
- **media:** set media storage directory as configurable
|
||||
([7e1a470](https://code.castopod.org/adaures/castopod/commit/7e1a470ba42172eb4c3864ab3652e9f8b55d1ba8))
|
||||
|
||||
## [1.2.4](https://code.castopod.org/adaures/castopod/compare/v1.2.3...v1.2.4) (2023-03-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- allow images to have .jpeg extension consistently
|
||||
([ae5e12b](https://code.castopod.org/adaures/castopod/commit/ae5e12be3b15fe50cb2311abcbbc19ac23b592f6))
|
||||
- **s3:** delete persons image sizes from bucket + add keyPrefix to config
|
||||
([208c271](https://code.castopod.org/adaures/castopod/commit/208c2715f900371987c3b75a749fe937a3db1991))
|
||||
- **s3:** do not create bucket if not exists, check if healthy instead
|
||||
([da7076f](https://code.castopod.org/adaures/castopod/commit/da7076fc2d49d07708d5adaa99733487b7f52e20))
|
||||
|
||||
### Reverts
|
||||
|
||||
- **homepage:** remove redirect to install if database is not setup
|
||||
([d4954e0](https://code.castopod.org/adaures/castopod/commit/d4954e026d5e0d48c5f15ed69d1ce71abb34d1a1))
|
||||
|
||||
## [1.2.3](https://code.castopod.org/adaures/castopod/compare/v1.2.2...v1.2.3) (2023-03-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **notifications:** set mark-all-as-read parameter to be podcast_id instead of
|
||||
actor_id
|
||||
([2748f23](https://code.castopod.org/adaures/castopod/commit/2748f2313797e50d8a2a7b87df09c0bc6e64360a))
|
||||
|
||||
## [1.2.2](https://code.castopod.org/adaures/castopod/compare/v1.2.1...v1.2.2) (2023-03-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **migration:** change old media file_key to file_path
|
||||
([a414142](https://code.castopod.org/adaures/castopod/commit/a4141421aa1d6e89742b390b042382f729f965a9)),
|
||||
closes [#314](https://code.castopod.org/adaures/castopod/issues/314)
|
||||
|
||||
## [1.2.1](https://code.castopod.org/adaures/castopod/compare/v1.2.0...v1.2.1) (2023-03-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- change app.mediaBaseURL to media.baseURL in install, docker entrypoints and
|
||||
docs
|
||||
([b3c6e05](https://code.castopod.org/adaures/castopod/commit/b3c6e05e6fcd8a518eeedeefde28b61f879ba71d))
|
||||
|
||||
# [1.2.0](https://code.castopod.org/adaures/castopod/compare/v1.1.2...v1.2.0) (2023-03-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **analytics:** check the x_forwarded_for client header
|
||||
([1111177](https://code.castopod.org/adaures/castopod/commit/1111177eb7fea4eba6d119b17acdf3bf416492ef))
|
||||
- **auth:** update podcast editors' permissions
|
||||
([a9b6308](https://code.castopod.org/adaures/castopod/commit/a9b630884bc318499ea7f03862d5752dd5f178e1))
|
||||
- **contributors:** add dash to prevent deleting permissions from other podcast
|
||||
([5d2a2d4](https://code.castopod.org/adaures/castopod/commit/5d2a2d49c489cd98f9c9ecbca35fd5d21a9cadfb)),
|
||||
closes [#310](https://code.castopod.org/adaures/castopod/issues/310)
|
||||
- display bandwidth limit on dashboard when set in .env
|
||||
([a2a87ab](https://code.castopod.org/adaures/castopod/commit/a2a87abf7caea3c87bcf2d0988610cc07782de9e))
|
||||
- **docker:** update nginx configuration
|
||||
([8884598](https://code.castopod.org/adaures/castopod/commit/8884598a56d0e2550776ef4cee5e53558c20e009))
|
||||
- **platforms:** update 'submit_url' for Antennapod
|
||||
([9fc49a7](https://code.castopod.org/adaures/castopod/commit/9fc49a7430406f50e68318c5fd7c577ae1ebd9df))
|
||||
|
||||
### Features
|
||||
|
||||
- add downloads count to episode list
|
||||
([b63c1dc](https://code.castopod.org/adaures/castopod/commit/b63c1dc9b1ed41626b99ba852a9a00ed417059ba))
|
||||
- add health route to check if db, cache and file manager are ok
|
||||
([1dde11f](https://code.castopod.org/adaures/castopod/commit/1dde11f8e42b66684a956068f5347e9289f4918b))
|
||||
- **media:** add s3 to manage media files
|
||||
([d93fc98](https://code.castopod.org/adaures/castopod/commit/d93fc98469ffe93913b65e539dec396891708c70))
|
||||
|
||||
### Reverts
|
||||
|
||||
- **install:** reset condition to look for instance owner before continuing
|
||||
install
|
||||
([fc009f3](https://code.castopod.org/adaures/castopod/commit/fc009f3d0058028bbbb6418603cf820c0f7cea80))
|
||||
|
||||
## [1.1.2](https://code.castopod.org/adaures/castopod/compare/v1.1.1...v1.1.2) (2022-12-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **analytics:** set EpisodeAudioController to init user session data
|
||||
([77ccb30](https://code.castopod.org/adaures/castopod/commit/77ccb306009eb093147c56789535e754f3d85570))
|
||||
|
||||
## [1.1.1](https://code.castopod.org/adaures/castopod/compare/v1.1.0...v1.1.1) (2022-12-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **op3:** remove scheme when wraping audio URI
|
||||
([0ad22e4](https://code.castopod.org/adaures/castopod/commit/0ad22e49bc488e96df5a41495f5b242559b64a45))
|
||||
- **rss:** add file extension to enclosure url
|
||||
([964cbba](https://code.castopod.org/adaures/castopod/commit/964cbba54f16556408bf8280c544a52e6be5c9fc))
|
||||
|
||||
# [1.1.0](https://code.castopod.org/adaures/castopod/compare/v1.0.5...v1.1.0) (2022-12-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **notifications:** remove cache inconsistencies when marking notification as
|
||||
read
|
||||
([46d7054](https://code.castopod.org/adaures/castopod/commit/46d70541d313c836ab0c078ba6121fe5fe956e62))
|
||||
- **notifications:** retrieve activity from database instead of getting cache
|
||||
([7fbbd08](https://code.castopod.org/adaures/castopod/commit/7fbbd08da6a37d08608900ad318e72815fe4b0c4))
|
||||
- **podcast:soundbite:** rename start time attribute to follow spec
|
||||
([689831c](https://code.castopod.org/adaures/castopod/commit/689831c26c756d454de432900d23bc09a37f890b))
|
||||
|
||||
### Features
|
||||
|
||||
- **analytics:** add OP3 analytics service option + update episode audio url
|
||||
([16527ed](https://code.castopod.org/adaures/castopod/commit/16527ed529265f2925e205856c684e34175a8933))
|
||||
|
||||
## [1.0.5](https://code.castopod.org/adaures/castopod/compare/v1.0.4...v1.0.5) (2022-11-25)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **router:** revert to CI4 v4.2.7 to include all routes
|
||||
([c13cfa0](https://code.castopod.org/adaures/castopod/commit/c13cfa0ea0679751521ca4157b953043ecc7974a))
|
||||
|
||||
## [1.0.4](https://code.castopod.org/adaures/castopod/compare/v1.0.3...v1.0.4) (2022-11-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- update actorUsername regex to get url_to actor
|
||||
([1d6b177](https://code.castopod.org/adaures/castopod/commit/1d6b177a55111ede01fba1c08499036d474533bc))
|
||||
|
||||
## [1.0.3](https://code.castopod.org/adaures/castopod/compare/v1.0.2...v1.0.3) (2022-11-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **dashboard-ui:** fill the blank gaps between cards on smaller screen sizes
|
||||
([00836cc](https://code.castopod.org/adaures/castopod/commit/00836cc368c75ae2e23fa5dc4a53a5bb6eb2ce24))
|
||||
|
||||
## [1.0.2](https://code.castopod.org/adaures/castopod/compare/v1.0.1...v1.0.2) (2022-11-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **auth:** disallow registration by default
|
||||
([379b9be](https://code.castopod.org/adaures/castopod/commit/379b9be2b99574fe4af4009b01128dba2c75f037))
|
||||
- **contributors:** add prefix to podcast group to delete contributor
|
||||
([9f785db](https://code.castopod.org/adaures/castopod/commit/9f785db7ba674638a6f456aa3626f3f8100911f1))
|
||||
- extract podcast ids from user groups using a regex
|
||||
([e26215a](https://code.castopod.org/adaures/castopod/commit/e26215a11fc23aa0ad5ccff8ee97d6c6e8a09c1a))
|
||||
- **notifications:** add manage-notifications permission to podcast
|
||||
([ed7c247](https://code.castopod.org/adaures/castopod/commit/ed7c247bcbbb450e5ff96418930d3b37ce912cc4))
|
||||
- **platforms:** convert special characters to htmlentities to validate url
|
||||
([82310a2](https://code.castopod.org/adaures/castopod/commit/82310a2e0b426e84501090bdd9c0cf592d1c0d53))
|
||||
|
||||
## [1.0.1](https://code.castopod.org/adaures/castopod/compare/v1.0.0...v1.0.1) (2022-11-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **platforms:** trim platform url before validation and storage
|
||||
([259fe5f](https://code.castopod.org/adaures/castopod/commit/259fe5f697a833e268cde88e959bc19bd662edf6))
|
||||
|
||||
# 1.0.0 (2022-10-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **a11y:** replace active tab color to contrast with background on podcast and
|
||||
episode pages
|
||||
([f3785e1](https://code.castopod.org/adaures/castopod/commit/f3785e140147d085a2fb6a62ded87cdfe360f442))
|
||||
- **activity-pub:** cache issues when navigating to activity stream urls
|
||||
([7bcbfb3](https://code.castopod.org/adaures/castopod/commit/7bcbfb32f7cca08d111be46c7f1640e372d4a4b0))
|
||||
- **activity-pub:** get database records using new model instances
|
||||
([92536dd](https://code.castopod.org/adaures/castopod/commit/92536ddb3812214a9c5682b92e547e5c1998a5d7))
|
||||
- **activitypub:** add conditions for possibly missing actor properties + add
|
||||
user-agent to requests
|
||||
([8fbf948](https://code.castopod.org/adaures/castopod/commit/8fbf948fbba22ffd33966a1b2ccd42e8f7c1f8a2))
|
||||
- **activitypub:** add target actor id to like / announce activities to send
|
||||
directly to note's actor
|
||||
([962dd30](https://code.castopod.org/adaures/castopod/commit/962dd305f5d3f6eadc68f400e0e8f953827fe20d))
|
||||
- **activitypub:** add target_actor_id for create activity to broadcast post
|
||||
reply
|
||||
([0128a21](https://code.castopod.org/adaures/castopod/commit/0128a21ec55dcc0a2fbf4081dadb4c4737735ba1))
|
||||
- **activitypub:** allow cors on get requests for routes exposing acitivitypub
|
||||
objects
|
||||
([2f24809](https://code.castopod.org/adaures/castopod/commit/2f2480998f9abb34f02ab186c65d462a74b4e640))
|
||||
- **activitypub:** set created_by to null for reblog if no user + update episode
|
||||
oembed data
|
||||
([209dfbd](https://code.castopod.org/adaures/castopod/commit/209dfbd134e1a2cc02e7c24c158d786fa4dda61d))
|
||||
- add admin-audio-player to vite config to have admin player show up
|
||||
([93cb9b2](https://code.castopod.org/adaures/castopod/commit/93cb9b24701c09b92820204a67c1fc1b3c044708))
|
||||
- add application/octet-stream mimetype to mp3 and m4a extensions to prevent
|
||||
ext_in error
|
||||
([339bef8](https://code.castopod.org/adaures/castopod/commit/339bef878e54983d86e91e6ff7a931a843d321b3)),
|
||||
closes [#145](https://code.castopod.org/adaures/castopod/issues/145)
|
||||
- add category_label component to include parent category in about podcast page
|
||||
([74e7d68](https://code.castopod.org/adaures/castopod/commit/74e7d68ac834885c4b89ee6e7d60db2157165799))
|
||||
- add explicit int conversion when formatting episode duration
|
||||
([1253096](https://code.castopod.org/adaures/castopod/commit/1253096197a0d30692bdafa7152f250cd9a71acf))
|
||||
- add head request to analytics_hit route
|
||||
([f0a2f0b](https://code.castopod.org/adaures/castopod/commit/f0a2f0bea491ca91976b351bb79837e95c9d094b))
|
||||
- add href to castopod website on login page
|
||||
([cc54257](https://code.castopod.org/adaures/castopod/commit/cc5425735184ad738aa0f38540f18e8971f8f56e))
|
||||
- add missing explicit badge for podcasts and episodes
|
||||
([cdf9f9d](https://code.castopod.org/adaures/castopod/commit/cdf9f9d53f2597f19455cb65c51da4677bb99327))
|
||||
- add open graph size for podcast images to replace the inadequate large format
|
||||
([33aae1f](https://code.castopod.org/adaures/castopod/commit/33aae1f7934e4962116e94e477dbf48e24971f5f))
|
||||
- add public/media folder to castopod bundle
|
||||
([8053d35](https://code.castopod.org/adaures/castopod/commit/8053d3521b481872711dabaaf265d08b9bfbaa87)),
|
||||
closes [#52](https://code.castopod.org/adaures/castopod/issues/52)
|
||||
- add translation key for audio-clipper trim labels
|
||||
([db191ac](https://code.castopod.org/adaures/castopod/commit/db191ac31bd16bad2a72afdb8b25c685adf86a6e))
|
||||
- add underline and semibold font weight for prose links to have them stand out
|
||||
([d4d8671](https://code.castopod.org/adaures/castopod/commit/d4d867121c50bded4176a53d7154cf1bb347e306))
|
||||
- add where condition to get episode count without deleted episodes
|
||||
([7661734](https://code.castopod.org/adaures/castopod/commit/7661734ed296654630f3668132671117519145dd)),
|
||||
closes [#67](https://code.castopod.org/adaures/castopod/issues/67)
|
||||
- **admin:** save block and lock switches
|
||||
([b66c0af](https://code.castopod.org/adaures/castopod/commit/b66c0afc8fab2e338402a9a4f8105e5f5459e208))
|
||||
- **analytics:** redirect to mp3 file even when referer was not set
|
||||
([9fc388d](https://code.castopod.org/adaures/castopod/commit/9fc388d154f29c335dedcd624abe8c1751762c07))
|
||||
- **analytics:** remove charts empty values + remove useless language cache
|
||||
([1678794](https://code.castopod.org/adaures/castopod/commit/16787941539ba4014281a366789ea896a9cd2afc))
|
||||
- **analytics:** set duration field to precise decimal as episode's audio file
|
||||
duration
|
||||
([d772685](https://code.castopod.org/adaures/castopod/commit/d77268540569b2be9d91d5e09aefb3ff5ac2b071))
|
||||
- **analytics:** set initial value for duration and bandwidth
|
||||
([ee50539](https://code.castopod.org/adaures/castopod/commit/ee5053959154b1a2e5fbe4b43162968425206a26))
|
||||
- **analytics:** update migrations to set decimal precision for latitude and
|
||||
longitude
|
||||
([714d6b5](https://code.castopod.org/adaures/castopod/commit/714d6b5d4950e52cf1c3170bb59954f98ffd48bd))
|
||||
- **analytics:** update service management so that it works with new OPAWG slug
|
||||
values
|
||||
([7fe9d42](https://code.castopod.org/adaures/castopod/commit/7fe9d42500ade2c6fa3ff4365b4affc475af0e51))
|
||||
- **audio-clipper:** add mouse position offset when stretching clip to prevent
|
||||
content from jumping
|
||||
([602654b](https://code.castopod.org/adaures/castopod/commit/602654b99b33ee8c29da080058a0aaea976cd484))
|
||||
- **audio-clipper:** show audio playing progress + put waveform behind audio
|
||||
clipper
|
||||
([01a09dc](https://code.castopod.org/adaures/castopod/commit/01a09dc447b81c5412ceb45d6706a867939fd4dd))
|
||||
- **avatar:** use default avatar when no avatar url has been set
|
||||
([9d23c7e](https://code.castopod.org/adaures/castopod/commit/9d23c7e7e142c6cf1a1418e37e41d711064593c4)),
|
||||
closes [#111](https://code.castopod.org/adaures/castopod/issues/111)
|
||||
- **bundle:** include modules and themes when copying files with rsync
|
||||
([cd5bb88](https://code.castopod.org/adaures/castopod/commit/cd5bb8835c6e259408a8c13a2196a347e161da83))
|
||||
- **bundle:** update vite input files path + add `set -e` in bash scripts to
|
||||
fail if command fails
|
||||
([0ee53c7](https://code.castopod.org/adaures/castopod/commit/0ee53c71ffadb8a6ddb1febd9f912bc99f5f7a0b))
|
||||
- **cache:** add locale for podcast and episode pages + clear some persisting
|
||||
cache in models
|
||||
([9cec8a8](https://code.castopod.org/adaures/castopod/commit/9cec8a81ccbb7239402fe6633dbc31979272302a)),
|
||||
closes [#42](https://code.castopod.org/adaures/castopod/issues/42)
|
||||
[#61](https://code.castopod.org/adaures/castopod/issues/61)
|
||||
- **cache:** delete posts and comments pages cache when updating platform links
|
||||
([f7c3e5b](https://code.castopod.org/adaures/castopod/commit/f7c3e5bf4ad43389bf8d58d2c4aaf16b81cbce00)),
|
||||
closes [#169](https://code.castopod.org/adaures/castopod/issues/169)
|
||||
- **cache:** return a non cached view when connected
|
||||
([e2e7358](https://code.castopod.org/adaures/castopod/commit/e2e735815d805a48eed2ea3288d060d0ddb253a3))
|
||||
- **cache:** suffix cache names with authenticated for credits, map and pages
|
||||
([418a70b](https://code.castopod.org/adaures/castopod/commit/418a70b2a670d8ba0ab6c15fa5faa41f6be55e53))
|
||||
- cast actor_id to pass as int to set_interact_as_actor() function
|
||||
([56a8e5d](https://code.castopod.org/adaures/castopod/commit/56a8e5d7dd615322aeb007e730801c65d0b02e5c))
|
||||
- **category:** remove uncategorized option to enforce users in choosing a
|
||||
category
|
||||
([8c64f25](https://code.castopod.org/adaures/castopod/commit/8c64f25a0e72fec03d25544797d32623b2276fce))
|
||||
- change image size requirement hints
|
||||
([ea20206](https://code.castopod.org/adaures/castopod/commit/ea20206ee674eb54dd3ea188d2a2e2d41425df65))
|
||||
- change message upon cancellation of episode publication
|
||||
([9859c74](https://code.castopod.org/adaures/castopod/commit/9859c7434c2a3478ce035f7a4de20f594d63f5b0))
|
||||
- check for database connection and podcasts table existence before redirecting
|
||||
to install
|
||||
([eb74e81](https://code.castopod.org/adaures/castopod/commit/eb74e81c3d93581e310b391cd029e62a0d690a8a))
|
||||
- check that additional files are valid when creating episode
|
||||
([eac5bc8](https://code.castopod.org/adaures/castopod/commit/eac5bc876de125e1fe08d1b89f767a04fc0fbfb6))
|
||||
- check that note has a preview_card_id before displaying it
|
||||
([acb8b3a](https://code.castopod.org/adaures/castopod/commit/acb8b3a40172ccb184ffe544760601d756692e6c)),
|
||||
closes [#114](https://code.castopod.org/adaures/castopod/issues/114)
|
||||
- clear cache when deleting podcast banner
|
||||
([99bb40b](https://code.castopod.org/adaures/castopod/commit/99bb40b8bc17b8ee2cd8468a82e46ea280c92cb6))
|
||||
- comment all cache clean after page update to prevent analytics cache deletion
|
||||
([e6197a4](https://code.castopod.org/adaures/castopod/commit/e6197a4972a3cce3d67dd7972bb54f8720b8e5b7))
|
||||
- **comments:** add comment view partials for public pages
|
||||
([fcecbe1](https://code.castopod.org/adaures/castopod/commit/fcecbe1c68b0d28d19454fba65caf3ab769fbc75))
|
||||
- correct chart data
|
||||
([4d3e9c8](https://code.castopod.org/adaures/castopod/commit/4d3e9c8c02cdc882e9fe1c29625695b6f83c820a))
|
||||
- correct percona compatibility issue
|
||||
([e53f819](https://code.castopod.org/adaures/castopod/commit/e53f819264b2d6902996f11ffcbb7c99295a90ef))
|
||||
- correct php-fpm issues
|
||||
([1ef55d7](https://code.castopod.org/adaures/castopod/commit/1ef55d7315bb44abe05f02ec8a84b6b6a557a9a0))
|
||||
- correct referrer bug
|
||||
([ed69b2f](https://code.castopod.org/adaures/castopod/commit/ed69b2f5004ed1cd18bac824c08a0df01f5d2637))
|
||||
- correction for servers with low int precision
|
||||
([31b7828](https://code.castopod.org/adaures/castopod/commit/31b7828e77519ef43e9bcfcbdf6c21712f97a571))
|
||||
- **cors:** add preflight option routes for episode, podcast and status objects
|
||||
([a281abf](https://code.castopod.org/adaures/castopod/commit/a281abfda475388a07943c169dab460cc2d4f944))
|
||||
- declare typed properties in PHPDoc for php<7.4
|
||||
([14dd44d](https://code.castopod.org/adaures/castopod/commit/14dd44d03d6db0d9ae4198db8e65c92a0e45cb31)),
|
||||
closes [#23](https://code.castopod.org/adaures/castopod/issues/23)
|
||||
- define podcast_id and platform_slug as foreign keys in podcasts_plaforms table
|
||||
([6e9451a](https://code.castopod.org/adaures/castopod/commit/6e9451a1103b43750fa70ad576de36af25ca29cb))
|
||||
- define podcastNamespaceLink value
|
||||
([0d744d2](https://code.castopod.org/adaures/castopod/commit/0d744d212df0d070ceea185068eaf2746e1ccd48))
|
||||
- **email:** set the correct url in the activation and forgot emails
|
||||
([10fc6f1](https://code.castopod.org/adaures/castopod/commit/10fc6f17c6838a58348f32ccfd0cf05f9d3e172c)),
|
||||
closes [#204](https://code.castopod.org/adaures/castopod/issues/204)
|
||||
- **embeddable-player:** enable any ancestor when X-Frame-Options is set on
|
||||
server
|
||||
([44a4962](https://code.castopod.org/adaures/castopod/commit/44a4962e0b7e3ed87e9914b4e7792a0d52330ff8))
|
||||
- **embed:** open embedded player's links in new tab
|
||||
([4aa73d7](https://code.castopod.org/adaures/castopod/commit/4aa73d71e3b8c0a6c3f75f4d1d45c4d693aba64c))
|
||||
- **episode-form:** show warning to set `memory_limit`, `upload_max_filesize` &
|
||||
`post_max_size`
|
||||
([3b3c218](https://code.castopod.org/adaures/castopod/commit/3b3c218b9c868e9f12c54d7670e69d84c9ee79c0)),
|
||||
closes [#5](https://code.castopod.org/adaures/castopod/issues/5)
|
||||
[#86](https://code.castopod.org/adaures/castopod/issues/86)
|
||||
- **episode-unpublish:** set consistent posts_counts' increments/decrements for
|
||||
actors and episodes
|
||||
([8acdafd](https://code.castopod.org/adaures/castopod/commit/8acdafd26044e50a4d6ee451bf24ad66003c5bb3)),
|
||||
closes [#233](https://code.castopod.org/adaures/castopod/issues/233)
|
||||
- **episodeCount:** add missing brackets to French language file
|
||||
([c1b4112](https://code.castopod.org/adaures/castopod/commit/c1b411265ad9b06e95a8b097ecf73445b88dcb45))
|
||||
- **episode:** replace guid's empty string value to null
|
||||
([441052a](https://code.castopod.org/adaures/castopod/commit/441052af8d99e6e317edefd1e58ad71799357088))
|
||||
- **episodes-page:** handle defaultQuery being null when no podcast episodes
|
||||
([15183b7](https://code.castopod.org/adaures/castopod/commit/15183b7eab57dac007bcdfa8c3651239de1ae05a)),
|
||||
closes [#100](https://code.castopod.org/adaures/castopod/issues/100)
|
||||
- **episodes-table:** set descriptions to be not null
|
||||
([6774ec1](https://code.castopod.org/adaures/castopod/commit/6774ec10fa78527be6b7548ca1dc34ad0ada090c))
|
||||
- **episodes:** add publication status + set publication date to null when none
|
||||
has been set
|
||||
([d882981](https://code.castopod.org/adaures/castopod/commit/d882981b3a86c81921ce6b07d4cf61fc13983689)),
|
||||
closes [#70](https://code.castopod.org/adaures/castopod/issues/70)
|
||||
- escape characters for `min` in format_duration_symbol
|
||||
([3b6722a](https://code.castopod.org/adaures/castopod/commit/3b6722a42b9e4330e5235d4ceed41c777159f4dc))
|
||||
- escape generated feed tag values and remove new lines from public pages meta
|
||||
description
|
||||
([6238a43](https://code.castopod.org/adaures/castopod/commit/6238a43863210afe8988ad7cf251e6bfc6c8557c)),
|
||||
closes [#57](https://code.castopod.org/adaures/castopod/issues/57)
|
||||
[#46](https://code.castopod.org/adaures/castopod/issues/46)
|
||||
- expire default query cache upon scheduled episode publication
|
||||
([b72e7c8](https://code.castopod.org/adaures/castopod/commit/b72e7c8691c887e41107baea0a4d50a39eaf8c8b)),
|
||||
closes [#81](https://code.castopod.org/adaures/castopod/issues/81)
|
||||
- explicitly cast seconds to int in iso8601_duration helper function
|
||||
([779653f](https://code.castopod.org/adaures/castopod/commit/779653f75b140942f731cbb238bc0667cc461307))
|
||||
- **fediverse:** set default castopod avatar url when actor avatar is not
|
||||
present
|
||||
([460f52f](https://code.castopod.org/adaures/castopod/commit/460f52f70e493d619c28632db6c698e88f0ebb5f))
|
||||
- **fediverse:** set model instances as non shared to prevent overlapping
|
||||
([91128fa](https://code.castopod.org/adaures/castopod/commit/91128fad7a68e1f4e5acacba90b6899288699e61))
|
||||
- fix layout bugs in admin and update translation files
|
||||
([a834171](https://code.castopod.org/adaures/castopod/commit/a83417180cf61cdfadc5509b0aaa2fdb66592be3)),
|
||||
closes [#40](https://code.castopod.org/adaures/castopod/issues/40)
|
||||
- **follow:** add missing helpers to Actor controller
|
||||
([ee53a73](https://code.castopod.org/adaures/castopod/commit/ee53a732dc12ebbf5706e14969749a12cfd9d559))
|
||||
- **get_browser_language:** return defaultLocale if browser doesn't send user
|
||||
preferred language
|
||||
([9cc2996](https://code.castopod.org/adaures/castopod/commit/9cc299626181048b85b629bbe7f5806a1f5d21ff))
|
||||
- handle HEAD requests on podcast_feed route
|
||||
([74b2640](https://code.castopod.org/adaures/castopod/commit/74b2640f2a25c4cd6fd8835fc492c2a6893d4950)),
|
||||
closes [#79](https://code.castopod.org/adaures/castopod/issues/79)
|
||||
- **home:** remove hardcoded prefix in getAllPodcasts query
|
||||
([92d5cc5](https://code.castopod.org/adaures/castopod/commit/92d5cc50a3e533875cd894dccc417918102d4b7f))
|
||||
- **housekeeping:** replace the use of GLOB_BRACE with looping over file
|
||||
extensions
|
||||
([42d92d0](https://code.castopod.org/adaures/castopod/commit/42d92d0c8dfe0c567c28f5bfdda129890fa4c2ec)),
|
||||
closes [#154](https://code.castopod.org/adaures/castopod/issues/154)
|
||||
- **housekeeping:** set default sizes value + ignore illegal IFD size error to
|
||||
proceed with script
|
||||
([f21ca57](https://code.castopod.org/adaures/castopod/commit/f21ca57603cfa503699b7e09a155e18d876d65fe))
|
||||
- **housekeeping:** use EpisodeModel's builder to reset comments count
|
||||
([65e9c0b](https://code.castopod.org/adaures/castopod/commit/65e9c0b05ea4992884149cb4a4b071bf31a20a1a))
|
||||
- **htaccess:** add ? after index.php in RewriteRule
|
||||
([d9d139e](https://code.castopod.org/adaures/castopod/commit/d9d139eefa03c28d1a064b3b32c9036193497e57)),
|
||||
closes [#152](https://code.castopod.org/adaures/castopod/issues/152)
|
||||
- **http-signature:** update SIGNATURE_PATTERN allowing signature keys to be
|
||||
sent in any order
|
||||
([b7f285e](https://code.castopod.org/adaures/castopod/commit/b7f285e4e24247fedb94f030356fa6f291f525cc))
|
||||
- **images:** set default mimetype if none is specified when getting size info
|
||||
([6e4acc6](https://code.castopod.org/adaures/castopod/commit/6e4acc64ad256178cee7905402b48bafcd49f84c))
|
||||
- **import-with-escaped-characters:** remove \CodeIgniter\HTTP\URI in
|
||||
download_file, closes
|
||||
[#103](https://code.castopod.org/adaures/castopod/issues/103)
|
||||
([35b5be0](https://code.castopod.org/adaures/castopod/commit/35b5be095ff54d27acec1610a846ec0cdbdf1d65))
|
||||
- **import:** add extension when downloading file without + truncate slug if too
|
||||
long
|
||||
([c5f18bb](https://code.castopod.org/adaures/castopod/commit/c5f18bb6dc08a758ff735454bbe9cfa45a68c09b))
|
||||
- **import:** add validation for handle field to prevent
|
||||
Router.invalidParameterType error
|
||||
([5bf7200](https://code.castopod.org/adaures/castopod/commit/5bf7200fb390f2447b29f24b495f24483cf7b205)),
|
||||
closes [#119](https://code.castopod.org/adaures/castopod/issues/119)
|
||||
- **import:** cast description's SimpleXMLElement to string
|
||||
([02d17be](https://code.castopod.org/adaures/castopod/commit/02d17be4ffe229fc6657207d31eba0543b5f1a4c))
|
||||
- **import:** remove query string from files url
|
||||
([109c4aa](https://code.castopod.org/adaures/castopod/commit/109c4aa1afb72dd8b99c0302d74a7fef5a38638e))
|
||||
- **import:** save media files during podcast import + set missing media fields
|
||||
([a9989d8](https://code.castopod.org/adaures/castopod/commit/a9989d841a634f8cf6c04df25f40bb1e7d4fcdcc))
|
||||
- **import:** set default episode type if not set
|
||||
([d7250ab](https://code.castopod.org/adaures/castopod/commit/d7250ab03f9b032830c575ad58b51c8d60b7a49a))
|
||||
- **import:** set episode and season numbers to null when not present in item
|
||||
tag
|
||||
([3211398](https://code.castopod.org/adaures/castopod/commit/3211398c78b1b28b76a46427ee07874bbf84a85d))
|
||||
- **import:** use <image><url> tag when no <itunes:image> is present
|
||||
([20e607a](https://code.castopod.org/adaures/castopod/commit/20e607afb755bc75056041738fa7cbf6723d754c))
|
||||
- include missing variables on public ui's episode page and remote_actions
|
||||
([193b373](https://code.castopod.org/adaures/castopod/commit/193b373bc94a5270acae99b637aa84b6cb2dedfe))
|
||||
- **input-component:** unset required attribute to prevent rendering it when
|
||||
false
|
||||
([db9ac13](https://code.castopod.org/adaures/castopod/commit/db9ac13860bce58235a5da275910bea605a00626))
|
||||
- **install:** add password validation when creating super admin
|
||||
([5a2ca0c](https://code.castopod.org/adaures/castopod/commit/5a2ca0cc4ae85cc15960201c86f131cb822f714f))
|
||||
- **install:** redirect manually to install wizard on first visit
|
||||
([2ceaaca](https://code.castopod.org/adaures/castopod/commit/2ceaaca44f1b82fc64d961e2fb4f4aaeade7e736))
|
||||
- **install:** redirect to host_url install route on instanceConfig validation
|
||||
error
|
||||
([99250b1](https://code.castopod.org/adaures/castopod/commit/99250b1868657c249a447399c7ebc69e00d43d1a))
|
||||
- **install:** redirect to input baseUrl after instance config
|
||||
([2426af7](https://code.castopod.org/adaures/castopod/commit/2426af7de8c9d426aaf534ff17b67f71c2e9f374)),
|
||||
closes [#53](https://code.castopod.org/adaures/castopod/issues/53)
|
||||
- **install:** set message block on forms to show error messages
|
||||
([3a0a20d](https://code.castopod.org/adaures/castopod/commit/3a0a20d59cdae7f166325efb750eaa6e9800ba6e)),
|
||||
closes [#157](https://code.castopod.org/adaures/castopod/issues/157)
|
||||
- **interact-as:** set actor_id instead of podcast id upon login event
|
||||
([5dfade7](https://code.castopod.org/adaures/castopod/commit/5dfade7cf37f339c56d2e577c679b88a1b1d9336)),
|
||||
closes [#104](https://code.castopod.org/adaures/castopod/issues/104)
|
||||
- **json-ld:** add missing properties to PodcastSeries object
|
||||
([e97266c](https://code.castopod.org/adaures/castopod/commit/e97266c5d4883a10f68b3685ecc0d1942f54d658))
|
||||
- keep subtitle line breaks when parsing srt file to json
|
||||
([cfb3da6](https://code.castopod.org/adaures/castopod/commit/cfb3da6592f2de23cb1a7ac420f19fc77fa338aa))
|
||||
- **layouts:** replace holy-grail layout with tailwind config + widen public
|
||||
podcast layout
|
||||
([be5a287](https://code.castopod.org/adaures/castopod/commit/be5a28787fdb180b64d9bf570120eff7072ab9aa))
|
||||
- **map:** update episode markers query to discard unpublished episodes
|
||||
([b3caac4](https://code.castopod.org/adaures/castopod/commit/b3caac45b12a23e4289d00133d2ad7915d084c44))
|
||||
- **markdown-editor:** remove unnecessary buttons for podcast and episode
|
||||
editors + add extensions
|
||||
([9c4f60e](https://code.castopod.org/adaures/castopod/commit/9c4f60e00bcbd4f784f12d2a6fed357ad402ee2e))
|
||||
- **md-editor:** build new markdown editor with lit +
|
||||
github/markdown-toolbar-element
|
||||
([9ec1cb9](https://code.castopod.org/adaures/castopod/commit/9ec1cb93da6f41124c48b8cf14ee6942e865bede)),
|
||||
closes [#93](https://code.castopod.org/adaures/castopod/issues/93)
|
||||
[#94](https://code.castopod.org/adaures/castopod/issues/94)
|
||||
[#120](https://code.castopod.org/adaures/castopod/issues/120)
|
||||
- **migrations:** ignore invalid utf8 chars for media files metadata + update
|
||||
transcript parser
|
||||
([45e8f99](https://code.castopod.org/adaures/castopod/commit/45e8f99e753cc02ec105e6f4d7fe026a205724f8))
|
||||
- minor corrections
|
||||
([13be386](https://code.castopod.org/adaures/castopod/commit/13be386842e94d9def1f7de4720931d8f6935171))
|
||||
- move analytics to helper
|
||||
([d311917](https://code.castopod.org/adaures/castopod/commit/d31191732e41aa106234b5ebe6e54ee02f0ce603))
|
||||
- move html escaping on credits page
|
||||
([fbffdbd](https://code.castopod.org/adaures/castopod/commit/fbffdbde78544c83138ee6234c62d43056f407b6))
|
||||
- **multiselect:** add missing class names in choices options for purge to work
|
||||
properly
|
||||
([719538d](https://code.castopod.org/adaures/castopod/commit/719538d0ccb28af3c3c5e1a4b6468d4b772fe819))
|
||||
- **notifications:** add trigger after activities update + update insert trigger
|
||||
([e5d16e8](https://code.castopod.org/adaures/castopod/commit/e5d16e87119021fa5a43470d67ddfe5128e57f74))
|
||||
- **notifications:** notify actors after activities insert / update using model
|
||||
callback methods
|
||||
([e08555a](https://code.castopod.org/adaures/castopod/commit/e08555a4e9a6c15eeba18273c63403f82eddae35))
|
||||
- **open-graph:** replace non existant episode description to podcast
|
||||
description in podcast page
|
||||
([b02584e](https://code.castopod.org/adaures/castopod/commit/b02584ee609af1ad1b5680cc28208d113eb0410b))
|
||||
- overwrite common lang function to escape returned string
|
||||
([4c490c1](https://code.castopod.org/adaures/castopod/commit/4c490c15bb6642ad0b2aaddf08d8af25de99b4b0)),
|
||||
closes [#196](https://code.castopod.org/adaures/castopod/issues/196)
|
||||
[#198](https://code.castopod.org/adaures/castopod/issues/198)
|
||||
- overwrite getActorById to return app's Actor entity
|
||||
([f2bc2f7](https://code.castopod.org/adaures/castopod/commit/f2bc2f7e01aa166faa627df6fe4d5ed4887c16e5))
|
||||
- **package.json:** update destination of postcss generation scripts
|
||||
([21413f8](https://code.castopod.org/adaures/castopod/commit/21413f8af3b8a0ac01d8c6f15bcd7a63e524e964))
|
||||
- **pages:** add locale to page cache
|
||||
([8f999ce](https://code.castopod.org/adaures/castopod/commit/8f999ce2f7ee1416c30cf58c84f67b3d11b3f142))
|
||||
- **partner:** set correct image URL
|
||||
([61554be](https://code.castopod.org/adaures/castopod/commit/61554be12a64d59ab99fab810b1b05632b408f3a))
|
||||
- pass timezone to relative time component to show the localized time in the UI
|
||||
([b9db936](https://code.castopod.org/adaures/castopod/commit/b9db936461d4cb914958bb3256bb910bbd7ba815))
|
||||
- **persons:** prevent overflow of persons list by adding horizontal scroll
|
||||
([9e8995d](https://code.castopod.org/adaures/castopod/commit/9e8995dc6e039032cc65f87895cf770f99e8b244))
|
||||
- **persons:** set person picture as optional for better ux
|
||||
([7fdea63](https://code.castopod.org/adaures/castopod/commit/7fdea63de7e572810082c84fff3013af580df58b)),
|
||||
closes [#125](https://code.castopod.org/adaures/castopod/issues/125)
|
||||
- **platforms:** display platform link only when visible is toggled on
|
||||
([6e503c8](https://code.castopod.org/adaures/castopod/commit/6e503c8d6182987e48892370623183f871bbd1c1)),
|
||||
closes [#39](https://code.castopod.org/adaures/castopod/issues/39)
|
||||
- **player-styling:** revert vite to 2.8 to reference the player css
|
||||
([e07d3af](https://code.castopod.org/adaures/castopod/commit/e07d3afea9af85b8361227e000fb64b502781668))
|
||||
- **podcast-activity:** check if transcript and chapters are set before
|
||||
including them in audio
|
||||
([5855a25](https://code.castopod.org/adaures/castopod/commit/5855a250936f91641efef77650890a18d8e9917f))
|
||||
- **podcast-import:** move guid attribute declaration for Episode entity to
|
||||
include slug data
|
||||
([5d02ae3](https://code.castopod.org/adaures/castopod/commit/5d02ae39908a9d743627135b372bf981134c4328))
|
||||
- **podcast:** use markdown description value for editor + set prose class to
|
||||
about description
|
||||
([f304d97](https://code.castopod.org/adaures/castopod/commit/f304d97b14e0ef383509cb3bba50beb55bf701ba)),
|
||||
closes [#156](https://code.castopod.org/adaures/castopod/issues/156)
|
||||
- prefill description footer input when creating a new episode
|
||||
([9ea5ca3](https://code.castopod.org/adaures/castopod/commit/9ea5ca31697c70d176294f8aea37bd57d471fcf7))
|
||||
- **premium-podcasts:** display unlock button in embed when premium episode
|
||||
([ca109ba](https://code.castopod.org/adaures/castopod/commit/ca109ba3a8a08e661fd2484454b1983c3418f15d))
|
||||
- **premium-podcasts:** remove cache in unlock form + redirect to podcast if
|
||||
podcast is not premium
|
||||
([242352c](https://code.castopod.org/adaures/castopod/commit/242352c4d9cd936de14e8e8a5d78ebf1287b1f95))
|
||||
- **premium-podcasts:** return different cached page when podcast is unlocked
|
||||
([b1303c5](https://code.castopod.org/adaures/castopod/commit/b1303c525517498b0edfb9885ff36e08c72628b5))
|
||||
- **pwa:** add scope to webmanifests to allow installing an app per podcast
|
||||
([74c683e](https://code.castopod.org/adaures/castopod/commit/74c683eb44398a84443ec17903c3e002bb5ea9b9))
|
||||
- **pwa:** set app display as standalone in the webmanifests
|
||||
([7aa37d2](https://code.castopod.org/adaures/castopod/commit/7aa37d24ac13a1ee160c01a56b43621d7efcfbbc))
|
||||
- re-order graph values
|
||||
([35f633b](https://code.castopod.org/adaures/castopod/commit/35f633b4c71c087d1ddc9bba9e9bbe18de09204f))
|
||||
- redirect to non cached views when authenticated in public views
|
||||
([482b47b](https://code.castopod.org/adaures/castopod/commit/482b47ba6bdab7f27fc5704a559567228e07cd14))
|
||||
- **release:** add missing version number to castopod-host package
|
||||
([8f3e9d9](https://code.castopod.org/adaures/castopod/commit/8f3e9d90c14545d3f84d4469b26a53db4554b4dc))
|
||||
- remove cache from remote follow form to display error messages
|
||||
([90e4443](https://code.castopod.org/adaures/castopod/commit/90e44437bdf37d8024ef609b2f7336dbdfc3b974))
|
||||
- remove defer from js script declaration as it is a module
|
||||
([18ae557](https://code.castopod.org/adaures/castopod/commit/18ae557e97f1cef775cd1e75fb1fedee7f1c0cc9))
|
||||
- remove fixed size from podcast sidebar + rearrange account info + space out
|
||||
import radio inputs
|
||||
([776eec6](https://code.castopod.org/adaures/castopod/commit/776eec6f0d533d6c92ebec16f7a9dbfcde1f41f4))
|
||||
- remove heavy image cover data from audio file metadata
|
||||
([f74403b](https://code.castopod.org/adaures/castopod/commit/f74403bd7a5089b760603abe36264e7615be0e78))
|
||||
- remove required for other_categories field and add podcast_id to latest
|
||||
podcasts query
|
||||
([5417be0](https://code.castopod.org/adaures/castopod/commit/5417be0049288489a19c7b575aa77bd1e2bc0243))
|
||||
- remove required property to persons picture
|
||||
([c546be3](https://code.castopod.org/adaures/castopod/commit/c546be385b243014243ae93356006cd126d2f00d)),
|
||||
closes [#125](https://code.castopod.org/adaures/castopod/issues/125)
|
||||
- remove value escaping for form inputs and textareas
|
||||
([bc6dea2](https://code.castopod.org/adaures/castopod/commit/bc6dea2f8ad1cf0aee0eaa93151332fbac7fb771))
|
||||
- rename field status to task_status to get scheduled activities
|
||||
([4ff82a5](https://code.castopod.org/adaures/castopod/commit/4ff82a5f0a38dbbc9e272fca7df70ea5a190e334))
|
||||
- rename issue_templates labels
|
||||
([9f00305](https://code.castopod.org/adaures/castopod/commit/9f00305844e5a168e89d727fe29892b4ad5e48d6))
|
||||
- rename MyAccount controller file
|
||||
([e109df3](https://code.castopod.org/adaures/castopod/commit/e109df3004a3a98d72de39532e062fff9917f50f)),
|
||||
closes [#60](https://code.castopod.org/adaures/castopod/issues/60)
|
||||
- rename podcast name to podcast handle to clarify field usage
|
||||
([9dd4c77](https://code.castopod.org/adaures/castopod/commit/9dd4c7741eb1b7cb5fc214ff674697f3aa986df0)),
|
||||
closes [#126](https://code.castopod.org/adaures/castopod/issues/126)
|
||||
- reorder fields as composite primary keys for analytics tables
|
||||
([9660aa9](https://code.castopod.org/adaures/castopod/commit/9660aa97c8ffd4fe61f3a388d52b9ac5dd8e1d63))
|
||||
- replace deletedField with published_at for episodes
|
||||
([14d7d07](https://code.castopod.org/adaures/castopod/commit/14d7d078225cdc8980759273a5dc4163d9f84b06))
|
||||
- replace getWebEnclosureUrl with getEnclosureWebUrl
|
||||
([8122cea](https://code.castopod.org/adaures/castopod/commit/8122ceaf8a70050f14b3078f28b024e7d7cdb9ac))
|
||||
- replace hardcoded style links with vite service + set default value for remote
|
||||
transcript url
|
||||
([3f2e056](https://code.castopod.org/adaures/castopod/commit/3f2e05608e43d47bbb518a9acfaf56ec3eefafb4)),
|
||||
closes [#149](https://code.castopod.org/adaures/castopod/issues/149)
|
||||
[#150](https://code.castopod.org/adaures/castopod/issues/150)
|
||||
- replace website key for webpages in breadcrumb translate file
|
||||
([50e32ff](https://code.castopod.org/adaures/castopod/commit/50e32ff75636c1d4c5d945a267e884cb26ad7191))
|
||||
- restore default podcast icon on public website
|
||||
([342778b](https://code.castopod.org/adaures/castopod/commit/342778bac3c684328d72633961df1a2ebdc1330e))
|
||||
- revert to beta.1's codeigniter4 version
|
||||
([e831411](https://code.castopod.org/adaures/castopod/commit/e83141127080ccde44987195db46ba97fd6cc2ca))
|
||||
- rewrite regenerate image function to use saveSizes method from Image entity
|
||||
([3889912](https://code.castopod.org/adaures/castopod/commit/38899124ec27e94a8c798bc2db528f9f785eec20))
|
||||
- **router:** check if Accept header is set before getting value
|
||||
([10a2ae0](https://code.castopod.org/adaures/castopod/commit/10a2ae02484672d6a0fbc6e7b943519c5ec16cb6)),
|
||||
closes [#228](https://code.castopod.org/adaures/castopod/issues/228)
|
||||
- **router:** trim URI slash to match same routes for URIs with and without
|
||||
trailing slash
|
||||
([9e9375f](https://code.castopod.org/adaures/castopod/commit/9e9375f9a2cd6102f827b36ec521f4c86a557c00))
|
||||
- **rss-import:** add Castopod user-agent, handle redirects for downloaded
|
||||
files, add Content namespace
|
||||
([214243b](https://code.castopod.org/adaures/castopod/commit/214243b3fec4937e45ef1ceaba1149004cdf3b44))
|
||||
- **rss:** cast number type values to string in rss_helper
|
||||
([7180ae9](https://code.castopod.org/adaures/castopod/commit/7180ae9ec700930b69c04ed91f8eceea16ad77ce)),
|
||||
closes [#148](https://code.castopod.org/adaures/castopod/issues/148)
|
||||
- **rss:** do not escape podcast and episode titles in the xml
|
||||
([0dd3b7e](https://code.castopod.org/adaures/castopod/commit/0dd3b7e0bf00d5a9eb80c93cba1efcada59ec3c1)),
|
||||
closes [#138](https://code.castopod.org/adaures/castopod/issues/138)
|
||||
[#71](https://code.castopod.org/adaures/castopod/issues/71)
|
||||
- **rss:** remove escaping for publisher and owner name
|
||||
([6fc6347](https://code.castopod.org/adaures/castopod/commit/6fc6347846c126618cb7ff50164181650308d0c0))
|
||||
- **rss:** round episode durations and soundbites
|
||||
([c9fb987](https://code.castopod.org/adaures/castopod/commit/c9fb987fcfbe17069ec68fdbc823777079ce574b)),
|
||||
closes [#214](https://code.castopod.org/adaures/castopod/issues/214)
|
||||
- **rss:** set ❬itunes:author❭ tag to owner_name if publisher not specified
|
||||
([2271c14](https://code.castopod.org/adaures/castopod/commit/2271c1445b1ded12bc53b5d23b5e59d12b17c71a)),
|
||||
closes [#96](https://code.castopod.org/adaures/castopod/issues/96)
|
||||
- **rss:** use originalPath instead of originalMediaPath in Image library
|
||||
([b4012b7](https://code.castopod.org/adaures/castopod/commit/b4012b7d2ed6b34b69ad767570dd33f0dc7db920))
|
||||
- save transcript and chapters files to podcasts folder
|
||||
([63f49c7](https://code.castopod.org/adaures/castopod/commit/63f49c719f672b615c5a8893d3868dffcd332e47))
|
||||
- **search-episodes:** add fallback sql query using LIKE for search query with
|
||||
less than 4 characters
|
||||
([e66bf44](https://code.castopod.org/adaures/castopod/commit/e66bf44341175bc5a10fbf7dfa00b351e76136c2)),
|
||||
closes [#236](https://code.castopod.org/adaures/castopod/issues/236)
|
||||
- **security:** add csrf filter + prevent xss attacks by escaping user input
|
||||
([cd2e1e1](https://code.castopod.org/adaures/castopod/commit/cd2e1e1dc37c53d32d00971c451c4800b8fd6107))
|
||||
- set cache expiration to next note publish to show note on publication date
|
||||
([0a66de3](https://code.castopod.org/adaures/castopod/commit/0a66de3e6c17d4ac94ee8e13bd00ceaf64b1303e))
|
||||
- set episode description footer to null when empty value
|
||||
([3a7d97d](https://code.castopod.org/adaures/castopod/commit/3a7d97d660046d80698611311ff3708110d2af82))
|
||||
- set episode duration translation to hardcoded english
|
||||
([c39efc9](https://code.castopod.org/adaures/castopod/commit/c39efc9489180662edcebd142d4476c0617ea97f)),
|
||||
closes [#64](https://code.castopod.org/adaures/castopod/issues/64)
|
||||
- set episode guid upon episode creation
|
||||
([ad8b153](https://code.castopod.org/adaures/castopod/commit/ad8b153f2a3b1a3b1751bf63785c4950e1516e6b)),
|
||||
closes [#48](https://code.castopod.org/adaures/castopod/issues/48)
|
||||
- set episode numbers during import + remove all custom form_helpers + minor ui
|
||||
issues
|
||||
([99a3b8d](https://code.castopod.org/adaures/castopod/commit/99a3b8d33e00482da50dd62bdaa9215a351a56e4))
|
||||
- set interact_as_actor for user upon password reset
|
||||
([ad8f5f5](https://code.castopod.org/adaures/castopod/commit/ad8f5f5a0fac7b0b9cc10a0b86200f014aca7553)),
|
||||
closes [#178](https://code.castopod.org/adaures/castopod/issues/178)
|
||||
- set localized slug_field key as string in french language
|
||||
([17fb29b](https://code.castopod.org/adaures/castopod/commit/17fb29b20993b7deee4e252e0e3a4a2459ee0d98))
|
||||
- set location to null when getting empty string
|
||||
([71b1b5f](https://code.castopod.org/adaures/castopod/commit/71b1b5f775af475b1dc78328330e277f565e41b6))
|
||||
- set storage limit as disk_total_space instead of free space
|
||||
([7512e2e](https://code.castopod.org/adaures/castopod/commit/7512e2ed1ff5656cd63a4fc2524296dbb8b4164a))
|
||||
- **settings:** add .jpg extension to site-icon file input to display all jpeg
|
||||
images
|
||||
([f611a16](https://code.castopod.org/adaures/castopod/commit/f611a16cd0c1a389e1c5a287eaec9d2a927a4bb6))
|
||||
- **socialinteract:** move social interact uri into uri attribute + update
|
||||
social data upon import
|
||||
([12b2200](https://code.castopod.org/adaures/castopod/commit/12b22008a237185cb736fc29352fab22421dad16))
|
||||
- sort episodes by published_at with unpublished episodes at the begining
|
||||
([1686f84](https://code.castopod.org/adaures/castopod/commit/1686f840d16f2bd3d71d7f222a59b8e6a838fd6e)),
|
||||
closes [#249](https://code.castopod.org/adaures/castopod/issues/249)
|
||||
- sort episodic podcasts by season
|
||||
([d7b6794](https://code.castopod.org/adaures/castopod/commit/d7b6794f68f9a01fd606a407c6eb4c12d15dee74))
|
||||
- **themes:** update themes stylesheet route and remove css extension
|
||||
([e4e7e00](https://code.castopod.org/adaures/castopod/commit/e4e7e0005e931967dd6162588f1c5913dbf4603e))
|
||||
- **types:** update fake seeders types + fix bugs
|
||||
([76a4bf3](https://code.castopod.org/adaures/castopod/commit/76a4bf344160df679db29e236e7df7822970fb60))
|
||||
- **ui:** remove empty tooltip when hovering on sponsor button
|
||||
([40aa661](https://code.castopod.org/adaures/castopod/commit/40aa661289e1d1517fffcea5d257183bc9c458e4))
|
||||
- unpublish episode before deleting it + add validation step before deletion
|
||||
([f75bd76](https://code.castopod.org/adaures/castopod/commit/f75bd76458eeb01a2d37912695e33f77d03b7a69)),
|
||||
closes [#112](https://code.castopod.org/adaures/castopod/issues/112)
|
||||
[#55](https://code.castopod.org/adaures/castopod/issues/55)
|
||||
- update .htaccess for shared hosting config
|
||||
([2379826](https://code.castopod.org/adaures/castopod/commit/2379826352e2f4b5060910bf9f29268610102f2e))
|
||||
- update broken contributor dropdown fields
|
||||
([e5b7515](https://code.castopod.org/adaures/castopod/commit/e5b75150234bd7f19e01def93425d3bda7379dd3))
|
||||
- update condition in AnalyticsTrait
|
||||
([fbc0967](https://code.castopod.org/adaures/castopod/commit/fbc0967caa81630d514ddb1b93b0834ebb4d913b))
|
||||
- update condition in home controller to redirect to install page
|
||||
([33f1b91](https://code.castopod.org/adaures/castopod/commit/33f1b91d55dd0652c979d50fc85879dbf88a4a42))
|
||||
- update conditions when checking for empty max_episodes and season_number
|
||||
([fbad0b5](https://code.castopod.org/adaures/castopod/commit/fbad0b59f68c65eba2fdcd5a8d3b312b622e9a45))
|
||||
- update form_textarea to prevent escaping value
|
||||
([78548b5](https://code.castopod.org/adaures/castopod/commit/78548b5cd75ea7d6688d1945ff5449ea4f6bec68))
|
||||
- update iso-369 language table seeder
|
||||
([0c90db4](https://code.castopod.org/adaures/castopod/commit/0c90db44c40de5af5b0b32b54489bda9424d9ef6))
|
||||
- update ivoox podcasting icon
|
||||
([f2b69a4](https://code.castopod.org/adaures/castopod/commit/f2b69a47339c887f57883ec612f3d200e512ac1c))
|
||||
- update MarkdownEditor component + restyle Button and other components
|
||||
([b05d177](https://code.castopod.org/adaures/castopod/commit/b05d177f1b7f44fef043ac5eb41f07133a2cf52d))
|
||||
- update purgecss content path for php helper files
|
||||
([eb70bb4](https://code.castopod.org/adaures/castopod/commit/eb70bb4f7078ff347aeb8f5dcc7896311d289466)),
|
||||
closes [#59](https://code.castopod.org/adaures/castopod/issues/59)
|
||||
- update translations for settings' tasks to include what they should be used
|
||||
for
|
||||
([06b1a8b](https://code.castopod.org/adaures/castopod/commit/06b1a8b29b6ce5d81c5570d250bdac4e0c9ee5ca))
|
||||
- use slash instead of backslash to call layout
|
||||
([a80adb2](https://code.castopod.org/adaures/castopod/commit/a80adb22958fc0a38374cbce2d950a0042e699eb))
|
||||
- use UTC_TIMESTAMP() to get current utc date instead of NOW() in sql queries
|
||||
([4e22a0d](https://code.castopod.org/adaures/castopod/commit/4e22a0d5e4b60941d41071f059aac80cbaf38fbf))
|
||||
- **users:** remove required roles input when editing user + prevent owner's
|
||||
roles from being edited
|
||||
([1c8af75](https://code.castopod.org/adaures/castopod/commit/1c8af7550ba27d8c8473ae96acd21ad7731fd863)),
|
||||
closes [#239](https://code.castopod.org/adaures/castopod/issues/239)
|
||||
- **ux:** allow for empty message upon episode publication and warn user on
|
||||
submit
|
||||
([33d01b8](https://code.castopod.org/adaures/castopod/commit/33d01b8d4fd6ebf24e9f011aa705c456c846956c)),
|
||||
closes [#129](https://code.castopod.org/adaures/castopod/issues/129)
|
||||
- **ux:** have podcast dashboard card link to podcast dashboard if only one
|
||||
podcast in instance
|
||||
([7dabee5](https://code.castopod.org/adaures/castopod/commit/7dabee58a187abe92358d962da506a836e29cda3))
|
||||
- **ux:** redirect user to install page on database error in home page
|
||||
([9017e30](https://code.castopod.org/adaures/castopod/commit/9017e30bf41bed8c2be65091bbc5fb1e63aef87a))
|
||||
- validate slug length when submitting episode form + clean permalink edit
|
||||
prefix
|
||||
([b07ac09](https://code.castopod.org/adaures/castopod/commit/b07ac093b2cae646f9a897bc9dfeeaef6eda6561))
|
||||
- **video-clips:** check if created video exists before recreating it and
|
||||
failing
|
||||
([dff1208](https://code.castopod.org/adaures/castopod/commit/dff12087251b2b89e195604202094b5ddd9a0936))
|
||||
- **video-clips:** clear video clip cache after process has finished
|
||||
([3ae6232](https://code.castopod.org/adaures/castopod/commit/3ae62325856f6ff331a5d9ed901b9fa097ca7055))
|
||||
- **video-clips:** create unique temporary files for resources to be deleted
|
||||
after generation
|
||||
([7f7c878](https://code.castopod.org/adaures/castopod/commit/7f7c878cb6ecf7b4a967b2af87da82bc6593081e))
|
||||
- **video-clips:** set audio codec to aac, fixing audio issue on twitter
|
||||
([3c22c68](https://code.castopod.org/adaures/castopod/commit/3c22c68ee81f77bd7fcf7e2739ee6af016407843))
|
||||
- **video-clips:** set longer podcast and episode lengths for squared format
|
||||
([c030113](https://code.castopod.org/adaures/castopod/commit/c0301134c2048dc29eb2b995e4d5c22c49444100))
|
||||
- **video-clips:** tweak portrait parameters to have subtitles display without
|
||||
overflowing
|
||||
([2385b1a](https://code.castopod.org/adaures/castopod/commit/2385b1a2926d1344569836e18cb30adb4c604664))
|
||||
- **video-clips:** update condition to check if ffmpeg is installed
|
||||
([b57f0b6](https://code.castopod.org/adaures/castopod/commit/b57f0b6eb65dccf22cb4d55f93d18ca36857d7fc)),
|
||||
closes [#163](https://code.castopod.org/adaures/castopod/issues/163)
|
||||
- **xml-editor:** escape xml editor's content + restyle form sections to prevent
|
||||
overflowing
|
||||
([588590b](https://code.castopod.org/adaures/castopod/commit/588590bd2c0346e2465ff8f1930580d76a3bf068))
|
||||
- **xml-editor:** prettify xml even without root node
|
||||
([ca55c24](https://code.castopod.org/adaures/castopod/commit/ca55c248d0562a8529071c1f10be12f40ef50dda))
|
||||
|
||||
### Features
|
||||
|
||||
- **activitypub:** add Podcast actor and PodcastEpisode object with comments
|
||||
([9e1e5d2](https://code.castopod.org/adaures/castopod/commit/9e1e5d2e862d6a3345d11ca7f96b955c76bfa013))
|
||||
- add about page in admin with instance info + database update button
|
||||
([d0836f3](https://code.castopod.org/adaures/castopod/commit/d0836f3ee360a836f815c59ea755f288501dc517))
|
||||
- add alternate rss feed link tag to podcast page head
|
||||
([a973c09](https://code.castopod.org/adaures/castopod/commit/a973c097d54a3d0186c4079b9d4d3e81aae38505)),
|
||||
closes [#35](https://code.castopod.org/adaures/castopod/issues/35)
|
||||
- add analytics and unknown useragents
|
||||
([ec92e65](https://code.castopod.org/adaures/castopod/commit/ec92e65aa42e09b1df04600b52a0c679dfc494bb))
|
||||
- add audio-clipper toolbar + add video-clip-previewer
|
||||
([0255753](https://code.castopod.org/adaures/castopod/commit/02557539e6eb48fc23ee2ee3b0c75aee3310965b))
|
||||
- add audio-clipper webcomponent (wip)
|
||||
([21d4251](https://code.castopod.org/adaures/castopod/commit/21d4251b9bcd5acb0f8a1761bc4edc34a3dbc228))
|
||||
- add autofocus to input field "Email or username" on login page
|
||||
([19caed4](https://code.castopod.org/adaures/castopod/commit/19caed4bce0daab9ccf6ab9645f44b60eb87de88))
|
||||
- add basic stats on podcast about page
|
||||
([1670558](https://code.castopod.org/adaures/castopod/commit/1670558473dba47219d470ff21d6224db6ab42ba))
|
||||
- add breadcrumb in admin area
|
||||
([7fb1de2](https://code.castopod.org/adaures/castopod/commit/7fb1de2cf3c97c4cd7afe3bd71bbe66041786ecd)),
|
||||
closes [#17](https://code.castopod.org/adaures/castopod/issues/17)
|
||||
- add cache to ActivityPub sql queries + cache activity and note pages
|
||||
([2d297f4](https://code.castopod.org/adaures/castopod/commit/2d297f45b3d7ef6e8711875a0b9b908e878115fa))
|
||||
- add CDN url
|
||||
([972bcbf](https://code.castopod.org/adaures/castopod/commit/972bcbf65ee119b8641ca3c4e5c0e8cf9ca8dd4f)),
|
||||
closes [#37](https://code.castopod.org/adaures/castopod/issues/37)
|
||||
- add codemirror to display xml editor for custom rss field
|
||||
([f15f262](https://code.castopod.org/adaures/castopod/commit/f15f26240cd5311fa9d07779f364b6639a501dec))
|
||||
- add cumulative listening time charts
|
||||
([588b4d2](https://code.castopod.org/adaures/castopod/commit/588b4d28da00bc12d02126e23181690f54d81716))
|
||||
- add default icons to Alert component
|
||||
([0d98001](https://code.castopod.org/adaures/castopod/commit/0d9800123b135e4fa1a2acd14a5e039c12174333))
|
||||
- add DropdownMenu component + remove global audio player in admin
|
||||
([abb7fba](https://code.castopod.org/adaures/castopod/commit/abb7fbac276d77b7d31a0aeba75d464f3ba3ad46))
|
||||
- add episode_numbering() component helper to display episode and season numbers
|
||||
([3f4a6bd](https://code.castopod.org/adaures/castopod/commit/3f4a6bd0b9f870f16107a41b102b6bf734868198))
|
||||
- add french translation
|
||||
([196920d](https://code.castopod.org/adaures/castopod/commit/196920d62f1810b4c35f800d17d7f93627319091))
|
||||
- add heading component + update ecs rules to fix views
|
||||
([23bdc6f](https://code.castopod.org/adaures/castopod/commit/23bdc6f8e36b7e8dfbe32755a54dea59ad913432))
|
||||
- add housekeeping task to run after migrations
|
||||
([89dee41](https://code.castopod.org/adaures/castopod/commit/89dee41d583e57251ea9315402a757f03571d7ad))
|
||||
- add install wizard form to bootstrap database and create the first superadmin
|
||||
user
|
||||
([cba871c](https://code.castopod.org/adaures/castopod/commit/cba871c5df9f7120c44d9952456ebbd0d220669e)),
|
||||
closes [#2](https://code.castopod.org/adaures/castopod/issues/2)
|
||||
- add instructions on production error page to ease Castopod debugging process
|
||||
([9eab54e](https://code.castopod.org/adaures/castopod/commit/9eab54e0853ccb8300d9f9b743cd84aefbf06549)),
|
||||
closes [#224](https://code.castopod.org/adaures/castopod/issues/224)
|
||||
- add ISO 3166 country codes
|
||||
([97cd94b](https://code.castopod.org/adaures/castopod/commit/97cd94b47494b66faf43fbbe0748872da80020a4))
|
||||
- add js audio player on podcast, admin and embeddable player pages + fix admon
|
||||
episodes ux
|
||||
([0e14eb4](https://code.castopod.org/adaures/castopod/commit/0e14eb4d3f526b0fd256a6144f3fbfc3fe52a357)),
|
||||
closes [#131](https://code.castopod.org/adaures/castopod/issues/131)
|
||||
- add label to sponsor button on podcast page
|
||||
([c29c018](https://code.castopod.org/adaures/castopod/commit/c29c018c7a543fc9398b5d7d11f086123e2b33f2)),
|
||||
closes [#162](https://code.castopod.org/adaures/castopod/issues/162)
|
||||
- add legalNoticeURL to app config for setting an external url to legal notice
|
||||
([711843a](https://code.castopod.org/adaures/castopod/commit/711843a0c81e1e2ec7a015431786df4ef32d5092))
|
||||
- add lock podcast according to the Podcastindex podcast-namespace to prevent
|
||||
unauthozized import
|
||||
([72b3012](https://code.castopod.org/adaures/castopod/commit/72b301272e0b70ded3e2b237391909e3f152ad0b))
|
||||
- add map analytics, add episodes analytics, clean analytics page layout,
|
||||
translate countries
|
||||
([07eae83](https://code.castopod.org/adaures/castopod/commit/07eae83a00d860e149359fae67d549488403d88b))
|
||||
- add media entity and link documents, images and audio files to it
|
||||
([6ecf286](https://code.castopod.org/adaures/castopod/commit/6ecf2866cfcde31a0840f15c3340808ce14b44cf))
|
||||
- add notifications inbox for actors
|
||||
([999999e](https://code.castopod.org/adaures/castopod/commit/999999e3efab7b1aad7568e4fd114dc7bac04f38)),
|
||||
closes [#215](https://code.castopod.org/adaures/castopod/issues/215)
|
||||
- add Noto Sans Mono font to use for durations + button to access new video clip
|
||||
form in list
|
||||
([7609bb6](https://code.castopod.org/adaures/castopod/commit/7609bb60330539aa91bfdafbb35c2d585624218a))
|
||||
- add npm for js dependencies + move src/ files to root folder
|
||||
([cbb83a6](https://code.castopod.org/adaures/castopod/commit/cbb83a6f308ac9357e9fb0cca5edae9d3fee5b48))
|
||||
- add Open Graph and Twitter meta tags
|
||||
([af970b8](https://code.castopod.org/adaures/castopod/commit/af970b8bac949e4c63047e04aca1b7403a4e8deb)),
|
||||
closes [#41](https://code.castopod.org/adaures/castopod/issues/41)
|
||||
- add pages table to store custom instance pages (eg. legal-notice, cookie
|
||||
policy, etc.)
|
||||
([9c224a8](https://code.castopod.org/adaures/castopod/commit/9c224a8ac6dd95f3c6c087a300fc8bac48e8090f)),
|
||||
closes [#24](https://code.castopod.org/adaures/castopod/issues/24)
|
||||
- add permanent delete feature for podcasts 🎉
|
||||
([dbb4030](https://code.castopod.org/adaures/castopod/commit/dbb4030da49f9ea1f61759fb7c66d71fc29ea4a1)),
|
||||
closes [#89](https://code.castopod.org/adaures/castopod/issues/89)
|
||||
- add platform models
|
||||
([a333d29](https://code.castopod.org/adaures/castopod/commit/a333d291966229a909c0851fd8b890ed97c48ceb))
|
||||
- add platforms form in podcast settings
|
||||
([043f49c](https://code.castopod.org/adaures/castopod/commit/043f49c784bc007ca0fa756ca4ed2d3b08843ad9))
|
||||
- add platforms tables
|
||||
([ce59344](https://code.castopod.org/adaures/castopod/commit/ce5934419a516c9926dd3fd0ace3c11a95b60722))
|
||||
- add podcast banner field for each podcast + refactor images configuration
|
||||
([4a8147b](https://code.castopod.org/adaures/castopod/commit/4a8147bfbbd98d9badfc57a0f2a18bdd5812e802))
|
||||
- add premium podcasts to manage subscriptions for premium episodes
|
||||
([3234500](https://code.castopod.org/adaures/castopod/commit/3234500e2d967438ad140f65da801a543f43775d)),
|
||||
closes [#193](https://code.castopod.org/adaures/castopod/issues/193)
|
||||
- add publish feature for podcasts and set draft by default
|
||||
([3d363f2](https://code.castopod.org/adaures/castopod/commit/3d363f2efe99836ac05c305a2fa683e342f06561)),
|
||||
closes [#128](https://code.castopod.org/adaures/castopod/issues/128)
|
||||
[#220](https://code.castopod.org/adaures/castopod/issues/220)
|
||||
- add remote_url alternative for transcript and chapters files
|
||||
([3143c9a](https://code.castopod.org/adaures/castopod/commit/3143c9ad36e4cf1364205cf2be39c0c96f80fdd2))
|
||||
- add replied to post or comment to reply element
|
||||
([d0f9c60](https://code.castopod.org/adaures/castopod/commit/d0f9c6018f1af527099f3e26b5d824710fa11caf))
|
||||
- add schema.org json-ld objects to podcasts, episodes, posts and comments pages
|
||||
([902f959](https://code.castopod.org/adaures/castopod/commit/902f959b30a10839684f093eb86edebc5d826a0b))
|
||||
- add task to housekeeping setting for resetting all instance counts
|
||||
([9303e51](https://code.castopod.org/adaures/castopod/commit/9303e51bc50d730a8026f58984e83b840360ee88))
|
||||
- add unique listeners analytics
|
||||
([3a49258](https://code.castopod.org/adaures/castopod/commit/3a4925816f3268230640525ad7af507aab8eecb9))
|
||||
- add update rss feed feature for podcasts to import their latest episodes
|
||||
([5eb9dc1](https://code.castopod.org/adaures/castopod/commit/5eb9dc168eb9af04767829b76242c9120f55d46d)),
|
||||
closes [#183](https://code.castopod.org/adaures/castopod/issues/183)
|
||||
- add user permissions and basic groups to handle authorizations
|
||||
([d58e518](https://code.castopod.org/adaures/castopod/commit/d58e51874a4722921b75b0049117015c2380406e)),
|
||||
closes [#3](https://code.castopod.org/adaures/castopod/issues/3)
|
||||
[#18](https://code.castopod.org/adaures/castopod/issues/18)
|
||||
- add WebSub module for pushing feed updates to open hubs
|
||||
([10d3f73](https://code.castopod.org/adaures/castopod/commit/10d3f73786ba141e27a822b2585c4a244ee92c14))
|
||||
- **admin:** add instance wide dashboard with storage and bandwidth usage
|
||||
([b1a6c02](https://code.castopod.org/adaures/castopod/commit/b1a6c02e56fdc01a7ff69fa7e7dd8ea71380b7ba)),
|
||||
closes [#216](https://code.castopod.org/adaures/castopod/issues/216)
|
||||
- **admin:** add search form in podcast episodes list
|
||||
([6be5d12](https://code.castopod.org/adaures/castopod/commit/6be5d12877342a7c56e25ea8dd15a975c6ce45ac)),
|
||||
closes [#26](https://code.castopod.org/adaures/castopod/issues/26)
|
||||
- **admin:** make header stick on scroll and show title + action buttons using
|
||||
css only
|
||||
([d60498c](https://code.castopod.org/adaures/castopod/commit/d60498c1beb970a14eeb3bbe02d1b1d8116624b0))
|
||||
- **admin:** update admin layout for better ux + update brand pine colors
|
||||
([d86142e](https://code.castopod.org/adaures/castopod/commit/d86142ebe7cd7582835f180b79fbeaaaba703528))
|
||||
- allow cross origin requests on episode comments
|
||||
([e12f95a](https://code.castopod.org/adaures/castopod/commit/e12f95aca13c6d54489a9cfd99d4cd2490fe83ab))
|
||||
- **analytics-gdpr:** update cached personal data to expire at midnight
|
||||
([0188b67](https://code.castopod.org/adaures/castopod/commit/0188b67354a756f0c926edd7b46623ab5b20c12b))
|
||||
- **analytics:** add 'other' group to pie charts in order to display more
|
||||
accurate data
|
||||
([73acef9](https://code.castopod.org/adaures/castopod/commit/73acef933ff3485987afc5157de022910876fc12))
|
||||
- **analytics:** add charts and data export
|
||||
([78625c4](https://code.castopod.org/adaures/castopod/commit/78625c471b4f03a09bd42f72b82217e1f2d01cef))
|
||||
- **analytics:** add current date and secret salt to analytics hash for improved
|
||||
privacy
|
||||
([6f2e7c0](https://code.castopod.org/adaures/castopod/commit/6f2e7c009c24830d4f08633bfbde3b75f40bf215))
|
||||
- **analytics:** add service name from rss user-agent
|
||||
([7202b98](https://code.castopod.org/adaures/castopod/commit/7202b9867bd59aafa8c338a4230fb5e5c55b24c6))
|
||||
- **analytics:** add weekday and hour bar charts
|
||||
([8ab3132](https://code.castopod.org/adaures/castopod/commit/8ab313296bb4a254ab05e90b17d896039839b784))
|
||||
- **api:** add rest api with podcasts read endpoints
|
||||
([e64001d](https://code.castopod.org/adaures/castopod/commit/e64001d00604bcf587ec5e9a631282f212df450d)),
|
||||
closes [#210](https://code.castopod.org/adaures/castopod/issues/210)
|
||||
- apply colour theme to embed player
|
||||
([9548337](https://code.castopod.org/adaures/castopod/commit/9548337a7c49879e8b58c2dfece46e3cfc9517eb)),
|
||||
closes [#201](https://code.castopod.org/adaures/castopod/issues/201)
|
||||
- **auth:** add auth.enable2FA config to enable two-factor authentication
|
||||
([7213ed2](https://code.castopod.org/adaures/castopod/commit/7213ed290c977ce8723f6d92addadc03913576ee))
|
||||
- build hashed static files to renew browser cache
|
||||
([37c54d2](https://code.castopod.org/adaures/castopod/commit/37c54d247749bdf8f528babd4a78f24d48051063)),
|
||||
closes [#107](https://code.castopod.org/adaures/castopod/issues/107)
|
||||
- **cache:** add podcast and episode pages to cache + clear them after insert or
|
||||
update
|
||||
([da0f047](https://code.castopod.org/adaures/castopod/commit/da0f0472819007e02e5da37399f2377772c618b9))
|
||||
- **categories:** create model, entity, migrations and seeds
|
||||
([f73b042](https://code.castopod.org/adaures/castopod/commit/f73b042cc091be82abdbbca8992080875d526972))
|
||||
- **clips:** setup clip entities and model + save video clip to have it
|
||||
generated in the background
|
||||
([2f6fdf9](https://code.castopod.org/adaures/castopod/commit/2f6fdf9091d52ca49709fc82621ba1c6dd0e817d))
|
||||
- **comments:** add comments to episodes + update naming of status to post
|
||||
([bb4752c](https://code.castopod.org/adaures/castopod/commit/bb4752c35e086664f5fd75fdc0d56546a1e356f6))
|
||||
- **comments:** add like / undo like to comment + add comment page
|
||||
([0c187ef](https://code.castopod.org/adaures/castopod/commit/0c187ef7a9278a60bcc6e5ee4d69d948b51e5c54))
|
||||
- **components:** add custom view renderer with ComponentRenderer adapted from
|
||||
bonfire2
|
||||
([a95de8b](https://code.castopod.org/adaures/castopod/commit/a95de8bab010f6b01c598da72191abe97e473687))
|
||||
- create optimized & resized images upon upload
|
||||
([02e4441](https://code.castopod.org/adaures/castopod/commit/02e4441f98f27e9534e5b9b63279153d14632ccd)),
|
||||
closes [#6](https://code.castopod.org/adaures/castopod/issues/6)
|
||||
- **custom-rss:** add custom xml tag injection in rss feed for ❬channel❭ and
|
||||
❬item❭
|
||||
([6ecdaad](https://code.castopod.org/adaures/castopod/commit/6ecdaad911d06b7f7a2b7d24710968c7eb9118f6))
|
||||
- **datetime-picker:** set material_green theme to flatpickr
|
||||
([3ce6541](https://code.castopod.org/adaures/castopod/commit/3ce6541003260677e722a916ad6bc83ef47c4371))
|
||||
- **devcontainer:** add devcontainer settings for dev environment
|
||||
([69e7266](https://code.castopod.org/adaures/castopod/commit/69e72667365247b63430dee88194e8f0d7c28edc))
|
||||
- display castopod version in admin footer
|
||||
([9f2574e](https://code.castopod.org/adaures/castopod/commit/9f2574e6fbb61dac4e1a4252dff30017685da5f0)),
|
||||
closes [#68](https://code.castopod.org/adaures/castopod/issues/68)
|
||||
- display legal disclaimer and warning on podcast import page
|
||||
([2f07992](https://code.castopod.org/adaures/castopod/commit/2f07992e5508b34b91f194eebfac80c51e80e90a)),
|
||||
closes [#34](https://code.castopod.org/adaures/castopod/issues/34)
|
||||
- edit + delete podcast and episode
|
||||
([ac5f0c7](https://code.castopod.org/adaures/castopod/commit/ac5f0c732806e955c01e05b7867801bc938c6bd5))
|
||||
- **embeddable-player:** add embeddable player widget
|
||||
([141788f](https://code.castopod.org/adaures/castopod/commit/141788fa089f9dedc8956c64ca515a4a4625f904))
|
||||
- enhance admin ui with responsive design and ux improvements
|
||||
([2d44b45](https://code.castopod.org/adaures/castopod/commit/2d44b457a02205d2e7da258d7029b8bc5da39533)),
|
||||
closes [#31](https://code.castopod.org/adaures/castopod/issues/31)
|
||||
[#9](https://code.castopod.org/adaures/castopod/issues/9)
|
||||
- enhance ui using javascript in admin area
|
||||
([c0e66d5](https://code.castopod.org/adaures/castopod/commit/c0e66d5f7012026e145d106f4d6bd3ba792a1b77))
|
||||
- **episode-unpublish:** remove episode comments upon unpublish
|
||||
([78acd7f](https://code.castopod.org/adaures/castopod/commit/78acd7f5c057c82507d801c424040296dbaba586))
|
||||
- **episode:** add form to allow editing episode's publication date to a past
|
||||
date
|
||||
([d783d16](https://code.castopod.org/adaures/castopod/commit/d783d16eb73d3f896a3dea39a766b4e963e53abf)),
|
||||
closes [#97](https://code.castopod.org/adaures/castopod/issues/97)
|
||||
- **episodes:** add create form and view pages for episode
|
||||
([f3b2c8b](https://code.castopod.org/adaures/castopod/commit/f3b2c8b84f3d93bef734e34dbe8ed729535e45e9)),
|
||||
closes [#1](https://code.castopod.org/adaures/castopod/issues/1)
|
||||
- **episodes:** add migrations, model and entity for episodes table
|
||||
([0444821](https://code.castopod.org/adaures/castopod/commit/044482174ede555ce19a2d8c6f48771cc8e7d27b))
|
||||
- **episodes:** replace all audio file URL parameters with base64 encoded data
|
||||
([e1f65cd](https://code.castopod.org/adaures/castopod/commit/e1f65cd3b53353a30d4ab6eb5312393cf04a1676))
|
||||
- **episodes:** replace soft delete with permanent delete
|
||||
([eb9ff52](https://code.castopod.org/adaures/castopod/commit/eb9ff522c25af8ceb2ed08614b581757ee791d42))
|
||||
- **episodes:** schedule episode with future publication_date by using cache
|
||||
expiration time
|
||||
([4f1e773](https://code.castopod.org/adaures/castopod/commit/4f1e773c0f9e4c2597f6c1b0a4773dfb34b2f203)),
|
||||
closes [#47](https://code.castopod.org/adaures/castopod/issues/47)
|
||||
- **fediverse:** implement activitypub protocols + update user interface
|
||||
([2f525c0](https://code.castopod.org/adaures/castopod/commit/2f525c0f6e44d320bff16e22c223481923ba683e)),
|
||||
closes [#69](https://code.castopod.org/adaures/castopod/issues/69)
|
||||
[#65](https://code.castopod.org/adaures/castopod/issues/65)
|
||||
[#85](https://code.castopod.org/adaures/castopod/issues/85)
|
||||
[#51](https://code.castopod.org/adaures/castopod/issues/51)
|
||||
[#91](https://code.castopod.org/adaures/castopod/issues/91)
|
||||
[#92](https://code.castopod.org/adaures/castopod/issues/92)
|
||||
[#88](https://code.castopod.org/adaures/castopod/issues/88)
|
||||
- **fonts:** replace Montserrat with Inter for better readablity
|
||||
([bfa11d0](https://code.castopod.org/adaures/castopod/commit/bfa11d007d04b8ac714c8cf3b8050a6aaf177a26))
|
||||
- **GDPR:** add GDPR.yml file to public/.well-known/
|
||||
([86bccc3](https://code.castopod.org/adaures/castopod/commit/86bccc3d5cc9562b89196f1766ac91cdc8ad786d))
|
||||
- **gdpr:** add purpose for granting access to premium content
|
||||
([47d6d81](https://code.castopod.org/adaures/castopod/commit/47d6d81b798ec3ed467e0f4339c98c8a6b80cecd))
|
||||
- **home:** sort podcasts by recent activity + add dropdown menu to choose
|
||||
between sorting options
|
||||
([7b89da6](https://code.castopod.org/adaures/castopod/commit/7b89da6106c150708782d39ed2742fe416c41e89)),
|
||||
closes [#164](https://code.castopod.org/adaures/castopod/issues/164)
|
||||
- **housekeeping:** add clear_cache option to flush redis or files cache
|
||||
([99bfac0](https://code.castopod.org/adaures/castopod/commit/99bfac0b428a4bc6fe8bfd10a355dfd93f42ba5c))
|
||||
- **i18n:** add 7 new languages + update german translations
|
||||
([d021abb](https://code.castopod.org/adaures/castopod/commit/d021abb52f5525d93810e25df2b453c918d7bc8b))
|
||||
- **i18n:** add german language as supported locale + create Language files from
|
||||
english source
|
||||
([c220b31](https://code.castopod.org/adaures/castopod/commit/c220b310ed59cad188af044b1fed0c39efc7da5b))
|
||||
- **i18n:** add Norwegian Nynorsk to supported locales
|
||||
([ced61fc](https://code.castopod.org/adaures/castopod/commit/ced61fc2364f954c1f6e0208b572faf5741498a8))
|
||||
- **i18n:** add Polish translation
|
||||
([2d83b44](https://code.castopod.org/adaures/castopod/commit/2d83b44add9e4e00766a1f326377ed892f48ad73))
|
||||
- **i18n:** add Spanish to supported locales
|
||||
([e340b54](https://code.castopod.org/adaures/castopod/commit/e340b54a84d7dcdf9ba910fe7ff39c453fac0968))
|
||||
- **i18n:** add support for German and Brazilian Portuguese languages
|
||||
([c9b9fe4](https://code.castopod.org/adaures/castopod/commit/c9b9fe4ee893de9a1df7f8269c39d08a90d205d6))
|
||||
- **i18n:** add support for Simplified Chinese (zh-Hans) and Catalan (ca)
|
||||
locales
|
||||
([48d1443](https://code.castopod.org/adaures/castopod/commit/48d14434727c3310a391160c7af02c56b7e20425))
|
||||
- **icons:** add default icons for podcasting, social and funding platforms +
|
||||
remove complex icons
|
||||
([5bcdfeb](https://code.castopod.org/adaures/castopod/commit/5bcdfebe6489b5d6b90f3c828b014ec4e9a7e7e1)),
|
||||
closes [#166](https://code.castopod.org/adaures/castopod/issues/166)
|
||||
[#167](https://code.castopod.org/adaures/castopod/issues/167)
|
||||
[#170](https://code.castopod.org/adaures/castopod/issues/170)
|
||||
- **icons:** add podnews icon to podcasting platforms
|
||||
([5f42355](https://code.castopod.org/adaures/castopod/commit/5f423557c2b78fd7c38c5e0caab6c6c80d21e36e)),
|
||||
closes [#190](https://code.castopod.org/adaures/castopod/issues/190)
|
||||
- import podcast from an rss feed url
|
||||
([9a5d5a1](https://code.castopod.org/adaures/castopod/commit/9a5d5a15b4945eb319da9e999c4ca60a0a4f6d2d)),
|
||||
closes [#21](https://code.castopod.org/adaures/castopod/issues/21)
|
||||
- integrate stylized form components and update podcast edit page
|
||||
([6536729](https://code.castopod.org/adaures/castopod/commit/653672954606a23796e8a7bda3c34fd6b92f84e0))
|
||||
- make displayed publication time as relative time using @github/time-elements
|
||||
([230e139](https://code.castopod.org/adaures/castopod/commit/230e139e43324b9ebef06ca8f6e13b3d9a7bdc70))
|
||||
- make episode description more visible on episode pages
|
||||
([90533be](https://code.castopod.org/adaures/castopod/commit/90533be0298249e5527870c01329fce5f94ec2dc)),
|
||||
closes [#171](https://code.castopod.org/adaures/castopod/issues/171)
|
||||
- **map:** display geolocated episodes on a map page
|
||||
([4357cc2](https://code.castopod.org/adaures/castopod/commit/4357cc25ccc585ce398035c1c25d566b6a9df775))
|
||||
- **media:** clean media api + create an entity per media type
|
||||
([fafaa7e](https://code.castopod.org/adaures/castopod/commit/fafaa7e689b17f09a2b056081fa1f4fc53bf716b))
|
||||
- **media:** save audio, images, transcripts and chapters to media for episode
|
||||
and persons
|
||||
([58e2a00](https://code.castopod.org/adaures/castopod/commit/58e2a00a87fa7d5b188e13cc521d94f0cfddba50))
|
||||
- **meta-tags:** add activitypub alternate links to podcast, episode, comment
|
||||
and post pages
|
||||
([bd61752](https://code.castopod.org/adaures/castopod/commit/bd61752be2f574323b05d1d0aee0df55adf9a74e))
|
||||
- minor corrections to some tables
|
||||
([3bf9420](https://code.castopod.org/adaures/castopod/commit/3bf9420b5956a501b3b24405d243a71a928d6086))
|
||||
- **monetization:** add Web Monetization support
|
||||
([96a6026](https://code.castopod.org/adaures/castopod/commit/96a6026f1db452085360f5fe248de82a2ec06468))
|
||||
- **nodeinfo2:** add .well-known route for nodeinfo2 containing metadata about
|
||||
the castopod instance
|
||||
([88fddc8](https://code.castopod.org/adaures/castopod/commit/88fddc81d730978f2a4d8a671936b54041e3fe45))
|
||||
- **partner:** add link and image in episode description
|
||||
([ad07bb9](https://code.castopod.org/adaures/castopod/commit/ad07bb9330dc9493813368e969e1f3a3def44614))
|
||||
- **person:** add podcastindex.org namespace person tag
|
||||
([8acd011](https://code.castopod.org/adaures/castopod/commit/8acd011f13e99492ef4b44b327685bb006fe5f8f))
|
||||
- **platforms:** add AntennaPod
|
||||
([53e9cfd](https://code.castopod.org/adaures/castopod/commit/53e9cfd61c794b1539e9d4691d3c4e73c4b7aaa7))
|
||||
- **platforms:** add Fediverse and some funding platforms, add link on logo
|
||||
([afc3d50](https://code.castopod.org/adaures/castopod/commit/afc3d50289bb4173e0697d109ffe72f6814b93d1))
|
||||
- **platforms:** add helloasso
|
||||
([16cb993](https://code.castopod.org/adaures/castopod/commit/16cb993ee6e28987a840fc27a9c2c73794c67697))
|
||||
- **platforms:** add missing newpodcastapps.com's platforms
|
||||
([92dd370](https://code.castopod.org/adaures/castopod/commit/92dd370e2f9a464edd26cddcde96d0e16f91548d))
|
||||
- **platforms:** add pod.link
|
||||
([3d7a232](https://code.castopod.org/adaures/castopod/commit/3d7a2320ddd116e4a311605421126aff57243219))
|
||||
- **platforms:** add Podcast Index
|
||||
([ad52b1c](https://code.castopod.org/adaures/castopod/commit/ad52b1cc2b7d0bc844970214d205961a7196b4a9))
|
||||
- **platforms:** add podfriend
|
||||
([9fdc8d3](https://code.castopod.org/adaures/castopod/commit/9fdc8d32930234c7ffd2be6892be57febcef1086))
|
||||
- **podcast-form:** add new_feed_url field to set an url when changing domain or
|
||||
host
|
||||
([e7eec48](https://code.castopod.org/adaures/castopod/commit/e7eec48e7bc06a9aa907db01ed3e5b536e7dd8be))
|
||||
- **podcast-form:** update routes and redirect to podcast page
|
||||
([12ce905](https://code.castopod.org/adaures/castopod/commit/12ce905799002dc9c07e6de092342d30ba9fd7d8))
|
||||
- **podcast:** create a podcast using form
|
||||
([1202ba3](https://code.castopod.org/adaures/castopod/commit/1202ba3545f521097c60a6a2af95e70527cd1d34))
|
||||
- **podcasting 2.0:** update podcast:social tag to adhere to latest spec
|
||||
([a597cf4](https://code.castopod.org/adaures/castopod/commit/a597cf4ecfa6807a3413177d99c816056a7e7c45))
|
||||
- prefill season and episode numbers + set episode number as mandatory for
|
||||
serial podcasts
|
||||
([07d740b](https://code.castopod.org/adaures/castopod/commit/07d740b79f9283e389e723954f680f909ce5de4a)),
|
||||
closes [#134](https://code.castopod.org/adaures/castopod/issues/134)
|
||||
[#136](https://code.castopod.org/adaures/castopod/issues/136)
|
||||
- **public-ui:** adapt public podcast and episode pages to wireframes
|
||||
([40a0535](https://code.castopod.org/adaures/castopod/commit/40a0535fc1bc12a24994b651f5e00b35995cbdda)),
|
||||
closes [#30](https://code.castopod.org/adaures/castopod/issues/30)
|
||||
[#13](https://code.castopod.org/adaures/castopod/issues/13)
|
||||
- **pwa:** add service-worker + webmanifest for each podcasts to have them
|
||||
install on devices
|
||||
([fee2c1c](https://code.castopod.org/adaures/castopod/commit/fee2c1c0d0d03c4ff0a6a207b0a5e0c22bb7b13a))
|
||||
- redesign public podcast and episode pages + remove any information clutter for
|
||||
better ux
|
||||
([9321400](https://code.castopod.org/adaures/castopod/commit/932140077c671f0486a2cd08ceb6126c7ecde87f))
|
||||
- replace form helper functions with components in admin template
|
||||
([e64548b](https://code.castopod.org/adaures/castopod/commit/e64548b982ba47ff35f2272e2e30dd85eeba950b))
|
||||
- replace slug field with interactive permalink component
|
||||
([578022b](https://code.castopod.org/adaures/castopod/commit/578022b8c5163ffaf8db5870ed5ec9d5d9536477))
|
||||
- restyle episode and person cards + add focus style to interactive elements for
|
||||
a11y
|
||||
([a505a1d](https://code.castopod.org/adaures/castopod/commit/a505a1de56e8e3056379bd60d0595f432e294728))
|
||||
- **rss:** add ˂podcast:guid˃ tag for channel
|
||||
([1fab10e](https://code.castopod.org/adaures/castopod/commit/1fab10eb0d63bb7c3edf34ffe691e2aec2c2e43c))
|
||||
- **rss:** add podcast-namespace tags for platforms + previousUrl tag
|
||||
([dbba8dc](https://code.castopod.org/adaures/castopod/commit/dbba8dc58133967c778514268cbfed8098ed1dbc)),
|
||||
closes [#73](https://code.castopod.org/adaures/castopod/issues/73)
|
||||
[#75](https://code.castopod.org/adaures/castopod/issues/75)
|
||||
[#76](https://code.castopod.org/adaures/castopod/issues/76)
|
||||
[#80](https://code.castopod.org/adaures/castopod/issues/80)
|
||||
- **rss:** add podcast:comments tag to link to episode comments
|
||||
([32e8c7c](https://code.castopod.org/adaures/castopod/commit/32e8c7c16a61ffe08e2f3bfbdeda556811a0358c))
|
||||
- **rss:** add podcast:location tag
|
||||
([c0a2282](https://code.castopod.org/adaures/castopod/commit/c0a22829bd87d48535a86e60c6cd7280e44683a2))
|
||||
- **rss:** add rss feed route without the `.xml` extension
|
||||
([94c0b7c](https://code.castopod.org/adaures/castopod/commit/94c0b7c15920dae9ade5cdc79c7996dbfe82ba05)),
|
||||
closes [#247](https://code.castopod.org/adaures/castopod/issues/247)
|
||||
- **rss:** add soundbites according to the podcastindex specs
|
||||
([6b34617](https://code.castopod.org/adaures/castopod/commit/6b34617d07c70522cb941e96d91d9987493413eb)),
|
||||
closes [#83](https://code.castopod.org/adaures/castopod/issues/83)
|
||||
- **rss:** add transcript and chapters support
|
||||
([e769d83](https://code.castopod.org/adaures/castopod/commit/e769d83a932c169e52a630a17cd4dd8ac5cebaf6)),
|
||||
closes [#72](https://code.castopod.org/adaures/castopod/issues/72)
|
||||
[#82](https://code.castopod.org/adaures/castopod/issues/82)
|
||||
- **rss:** generate rss feed from podcast entity
|
||||
([c815ecd](https://code.castopod.org/adaures/castopod/commit/c815ecd6640931fee0895f80908a3ddfac482666))
|
||||
- **rss:** update monetization tag so that it meets PodcastIndex requirements
|
||||
([4c7ecbe](https://code.castopod.org/adaures/castopod/commit/4c7ecbee83950e5f9f2482cedaab18a1ac9bfc9e))
|
||||
- **select:** enhance select input with choices.js
|
||||
([910d457](https://code.castopod.org/adaures/castopod/commit/910d457cf843e0fc334b3505a4727d51633395ac))
|
||||
- set app parameter forceGlobalSecureRequests = true forcing requests to go
|
||||
through https
|
||||
([d9dff1b](https://code.castopod.org/adaures/castopod/commit/d9dff1b8bf89c8b526ad6cb89f98a1f160d49117))
|
||||
- set podcast / episode description in the pages description meta tag
|
||||
([1c4a504](https://code.castopod.org/adaures/castopod/commit/1c4a50442bea2d3449efce9c5ff1c80743152f55)),
|
||||
closes [#44](https://code.castopod.org/adaures/castopod/issues/44)
|
||||
- **settings:** add general config for instance (site name, description and
|
||||
icon)
|
||||
([5c56f3e](https://code.castopod.org/adaures/castopod/commit/5c56f3e6f00a61af2ccf50811c155c325f2b10fa))
|
||||
- **settings:** add theme settings to set an accent color for all public pages
|
||||
([5c529a8](https://code.castopod.org/adaures/castopod/commit/5c529a83aa6d6147d94e5aee996e6b0ab02f0ce4))
|
||||
- simplify podcast page's layout for better ux
|
||||
([2c0efc6](https://code.castopod.org/adaures/castopod/commit/2c0efc6563604dd067be88cfc9ddd88a01745e64))
|
||||
- **soundbites:** add soundbite list and creation forms with audio-clipper
|
||||
component
|
||||
([de19317](https://code.castopod.org/adaures/castopod/commit/de19317138a2106deb825c1eed7dda036ed7dac3))
|
||||
- style file inputs using tailwind's file class
|
||||
([8208ab6](https://code.castopod.org/adaures/castopod/commit/8208ab6785aae8c49f78eb9ac8cd53d77ec8e5e5))
|
||||
- **themes:** add ViewThemes library to set views in root themes folder
|
||||
([7a27676](https://code.castopod.org/adaures/castopod/commit/7a276764e6a1ee3619d9d3488f6163215db75338))
|
||||
- **themes:** set different default banner per theme
|
||||
([11c916f](https://code.castopod.org/adaures/castopod/commit/11c916fe433eb749ac32230c48e256057564cbb0))
|
||||
- **themes:** set generic css variables for colors to enable instance themes
|
||||
([a746a78](https://code.castopod.org/adaures/castopod/commit/a746a781b4bfc78209cf8302c6d7bb3cb452e446))
|
||||
- toggle podcast sidebar on smaller screens
|
||||
([f0205ec](https://code.castopod.org/adaures/castopod/commit/f0205ec274414e881cba40d6776126f05eaee583))
|
||||
- **transcript:** parse srt subtitles into json file + add max file size info
|
||||
below audio file input
|
||||
([0098761](https://code.castopod.org/adaures/castopod/commit/00987610a068c8d6cdd4421ea16585fa037eb61a))
|
||||
- **ui:** create ViewComponents library to enable building class and view files
|
||||
components
|
||||
([94872f2](https://code.castopod.org/adaures/castopod/commit/94872f2338e6025c2f3770be256160838dae9003))
|
||||
- update analytics so to meet IABv2 requirements
|
||||
([03e23a2](https://code.castopod.org/adaures/castopod/commit/03e23a28bf9b1b73fba55352c36a8cd6cc8ae729)),
|
||||
closes [#10](https://code.castopod.org/adaures/castopod/issues/10)
|
||||
- update pine colors + create charts components
|
||||
([a50abc1](https://code.castopod.org/adaures/castopod/commit/a50abc138d4997b564e3065b37504cda5ce62da6))
|
||||
- **users:** add myth-auth to handle users crud + add admin gateway only
|
||||
accessible by login
|
||||
([c63a077](https://code.castopod.org/adaures/castopod/commit/c63a077618c61b4cde7f25ffc650a4b0e1495f44)),
|
||||
closes [#11](https://code.castopod.org/adaures/castopod/issues/11)
|
||||
- **ux:** remove admin dashboard and redirect directly to podcast list
|
||||
([27c48b8](https://code.castopod.org/adaures/castopod/commit/27c48b8fa930b33e5e15f0c8685e468e857ca9cd))
|
||||
- **video-clip:** add video-clip page with video preview + logs
|
||||
([42538dd](https://code.castopod.org/adaures/castopod/commit/42538dd7577be0ffe59b4fdfadbd76cc89e5ef30))
|
||||
- **video-clip:** generate video clips in the bg using a cron job + add video
|
||||
clip page + tidy up UI
|
||||
([db0e427](https://code.castopod.org/adaures/castopod/commit/db0e4272bd6d307c562e1f961d2747cb62de0f35))
|
||||
- **video-clips:** add dimensions for portrait and squared formats
|
||||
([3af404d](https://code.castopod.org/adaures/castopod/commit/3af404da3dd1901c78cc7e1778fc225f6716207d))
|
||||
- **video-clips:** add new themes + add castopod logo as a watermark
|
||||
([1d1490b](https://code.castopod.org/adaures/castopod/commit/1d1490b06a1f5ecb10b3b98a72efc55d09c10944))
|
||||
- **video-clips:** add route for scheduled video clips + list video clips with
|
||||
status
|
||||
([2065ebb](https://code.castopod.org/adaures/castopod/commit/2065ebbee5e3d0f890ac90b55ca984f1d62a184c))
|
||||
- **video-clips:** allow episodeNumbering text to stand in the indent of
|
||||
episodeTitle paragraph
|
||||
([71a063d](https://code.castopod.org/adaures/castopod/commit/71a063dac311cb21639801fbae6af7c5106c2699))
|
||||
- **video-clips:** generate a 16:9 video using ffmpeg
|
||||
([35aa7ea](https://code.castopod.org/adaures/castopod/commit/35aa7ea5d9a339b3e6f745137282268d69fe2231))
|
||||
- **video-clips:** generate subtitles clip using transcript json to have
|
||||
subtitles accross video
|
||||
([3ce07e4](https://code.castopod.org/adaures/castopod/commit/3ce07e455d171e29be30d8ad45055510eb8d363c))
|
||||
- **video-clips:** replace hardcoded colors with config's theme colors
|
||||
([e462abf](https://code.castopod.org/adaures/castopod/commit/e462abf6d660e41d2170c52caf45704008de58e9))
|
||||
- **vite:** add vite config to decouple it from CI_ENVIRONMENT
|
||||
([8721719](https://code.castopod.org/adaures/castopod/commit/8721719cd7cf32e94823541eafaba1e9309355a8))
|
||||
- write id3v2 tags to episode's audio file
|
||||
([4651d01](https://code.castopod.org/adaures/castopod/commit/4651d01a84ff3ea8433a8ae26cfd750a1ec9e88d))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
- **cache:** update CI4 to use cache's deleteMatching method
|
||||
([54b84f9](https://code.castopod.org/adaures/castopod/commit/54b84f96843af13f579fea49102c8c2ef81b0a54))
|
||||
- **cache:** use deleteMatching method to prevent forgetting cached elements in
|
||||
models
|
||||
([76afc0c](https://code.castopod.org/adaures/castopod/commit/76afc0cfa2feb087697bae4bc138e4956873dd62))
|
||||
- defer javascript + lazy load images for faster page loads
|
||||
([f0685e4](https://code.castopod.org/adaures/castopod/commit/f0685e44799dfb494592ff97841c0ae035381db8))
|
||||
- **docker:** add redis caching service for development
|
||||
([05ace8c](https://code.castopod.org/adaures/castopod/commit/05ace8cff2ef02d19abd40097ac5546dca6a54ca))
|
||||
|
||||
### Reverts
|
||||
|
||||
- **install:** redirect to install in homepage if no database was set
|
||||
([73f094d](https://code.castopod.org/adaures/castopod/commit/73f094daf26a8cf75e39ebff1eeb7f9039276312))
|
||||
- set deprecated config options back in App config
|
||||
([433745f](https://code.castopod.org/adaures/castopod/commit/433745f194c73407999b207090478563283876a5))
|
||||
- **soundbites:** remove soundbite table from episode's public page
|
||||
([5dc0f19](https://code.castopod.org/adaures/castopod/commit/5dc0f19656de0d764f627d6ae78a9e306c901835))
|
||||
- use basic input file for episodes audio files instead of button for better UX
|
||||
([d5f22fb](https://code.castopod.org/adaures/castopod/commit/d5f22fbb38c43d9b37df401eff655958a57cb40a))
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
- **analytics:** analytics_podcasts_by_player table and analytics_podcasts
|
||||
procedure were updated
|
||||
|
||||
# [1.0.0-beta.24](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.23...v1.0.0-beta.24) (2022-10-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
|
|
|||
|
|
@ -1,128 +1,162 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
# Contributor Covenant 3.0 Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
We pledge to make our community welcoming, safe, and equitable for all.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
We are committed to fostering an environment that respects and promotes the
|
||||
dignity, rights, and contributions of all individuals, regardless of
|
||||
characteristics including race, ethnicity, caste, color, age, physical
|
||||
characteristics, neurodiversity, disability, sex or gender, gender identity or
|
||||
expression, sexual orientation, language, philosophy or religion, national or
|
||||
social origin, socio-economic position, level of education, or other status. The
|
||||
same privileges of participation are extended to everyone who participates in
|
||||
good faith and in accordance with this Covenant.
|
||||
|
||||
## Our Standards
|
||||
## Encouraged Behaviors
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
While acknowledging differences in social norms, we all strive to meet our
|
||||
community's expectations for positive behavior. We also understand that our
|
||||
words and actions may be interpreted differently than we intend based on
|
||||
culture, background, or native language.
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
With these considerations in mind, we agree to behave mindfully toward each
|
||||
other and act in ways that center our shared values, including:
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
1. Respecting the **purpose of our community**, our activities, and our ways of
|
||||
gathering.
|
||||
2. Engaging **kindly and honestly** with others.
|
||||
3. Respecting **different viewpoints** and experiences.
|
||||
4. **Taking responsibility** for our actions and contributions.
|
||||
5. Gracefully giving and accepting **constructive feedback**.
|
||||
6. Committing to **repairing harm** when it occurs.
|
||||
7. Behaving in other ways that promote and sustain the **well-being of our
|
||||
community**.
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
## Restricted Behaviors
|
||||
|
||||
## Enforcement Responsibilities
|
||||
We agree to restrict the following behaviors in our community. Instances,
|
||||
threats, and promotion of these behaviors are violations of this Code of
|
||||
Conduct.
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
1. **Harassment.** Violating explicitly expressed boundaries or engaging in
|
||||
unnecessary personal attention after any clear request to stop.
|
||||
2. **Character attacks.** Making insulting, demeaning, or pejorative comments
|
||||
directed at a community member or group of people.
|
||||
3. **Stereotyping or discrimination.** Characterizing anyone’s personality or
|
||||
behavior on the basis of immutable identities or traits.
|
||||
4. **Sexualization.** Behaving in a way that would generally be considered
|
||||
inappropriately intimate in the context or purpose of the community.
|
||||
5. **Violating confidentiality**. Sharing or acting on someone's personal or
|
||||
private information without their permission.
|
||||
6. **Endangerment.** Causing, encouraging, or threatening violence or other harm
|
||||
toward any person or group.
|
||||
7. Behaving in other ways that **threaten the well-being** of our community.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
### Other Restrictions
|
||||
|
||||
1. **Misleading identity.** Impersonating someone else for any reason, or
|
||||
pretending to be someone else to evade enforcement actions.
|
||||
2. **Failing to credit sources.** Not properly crediting the sources of content
|
||||
you contribute.
|
||||
3. **Promotional materials**. Sharing marketing or other commercial content in a
|
||||
way that is outside the norms of the community.
|
||||
4. **Irresponsible communication.** Failing to responsibly present content which
|
||||
includes, links or describes any other restricted behaviors.
|
||||
|
||||
## Reporting an Issue
|
||||
|
||||
Tensions can occur between community members even when they are trying their
|
||||
best to collaborate. Not every conflict represents a code of conduct violation,
|
||||
and this Code of Conduct reinforces encouraged behaviors and norms that can help
|
||||
avoid conflicts and minimize harm.
|
||||
|
||||
When an incident does occur, it is important to report it promptly. To report a
|
||||
possible violation, email us at [abuse@castopod.org](mailto:abuse@castopod.org).
|
||||
|
||||
Community Moderators take reports of violations seriously and will make every
|
||||
effort to respond in a timely manner. They will investigate all reports of code
|
||||
of conduct violations, reviewing messages, logs, and recordings, or interviewing
|
||||
witnesses and other participants. Community Moderators will keep investigation
|
||||
and enforcement actions as transparent as possible while prioritizing safety and
|
||||
confidentiality. In order to honor these values, enforcement actions are carried
|
||||
out in private with the involved parties, but communicating to the whole
|
||||
community may be part of a mutually agreed upon resolution.
|
||||
|
||||
## Addressing and Repairing Harm
|
||||
|
||||
If an investigation by the Community Moderators finds that this Code of Conduct
|
||||
has been violated, the following enforcement ladder may be used to determine how
|
||||
best to repair harm, based on the incident's impact on the individuals involved
|
||||
and the community as a whole. Depending on the severity of a violation, lower
|
||||
rungs on the ladder may be skipped.
|
||||
|
||||
1. Warning
|
||||
1. Event: A violation involving a single incident or series of incidents.
|
||||
2. Consequence: A private, written warning from the Community Moderators.
|
||||
3. Repair: Examples of repair include a private written apology,
|
||||
acknowledgement of responsibility, and seeking clarification on
|
||||
expectations.
|
||||
2. Temporarily Limited Activities
|
||||
1. Event: A repeated incidence of a violation that previously resulted in a
|
||||
warning, or the first incidence of a more serious violation.
|
||||
2. Consequence: A private, written warning with a time-limited cooldown
|
||||
period designed to underscore the seriousness of the situation and give
|
||||
the community members involved time to process the incident. The cooldown
|
||||
period may be limited to particular communication channels or interactions
|
||||
with particular community members.
|
||||
3. Repair: Examples of repair may include making an apology, using the
|
||||
cooldown period to reflect on actions and impact, and being thoughtful
|
||||
about re-entering community spaces after the period is over.
|
||||
3. Temporary Suspension
|
||||
1. Event: A pattern of repeated violation which the Community Moderators have
|
||||
tried to address with warnings, or a single serious violation.
|
||||
2. Consequence: A private written warning with conditions for return from
|
||||
suspension. In general, temporary suspensions give the person being
|
||||
suspended time to reflect upon their behavior and possible corrective
|
||||
actions.
|
||||
3. Repair: Examples of repair include respecting the spirit of the
|
||||
suspension, meeting the specified conditions for return, and being
|
||||
thoughtful about how to reintegrate with the community when the suspension
|
||||
is lifted.
|
||||
4. Permanent Ban
|
||||
1. Event: A pattern of repeated code of conduct violations that other steps
|
||||
on the ladder have failed to resolve, or a violation so serious that the
|
||||
Community Moderators determine there is no way to keep the community safe
|
||||
with this person as a member.
|
||||
2. Consequence: Access to all community spaces, tools, and communication
|
||||
channels is removed. In general, permanent bans should be rarely used,
|
||||
should have strong reasoning behind them, and should only be resorted to
|
||||
if working through other remedies has failed to change the behavior.
|
||||
3. Repair: There is no possible repair in cases of this severity.
|
||||
|
||||
This enforcement ladder is intended as a guideline. It does not limit the
|
||||
ability of Community Managers to use their discretion and judgment, in keeping
|
||||
with the best interests of our community.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
an individual is officially representing the community in public or other
|
||||
spaces. Examples of representing our community include using an official email
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
[abuse@castopod.org](mailto:abuse@castopod.org). All complaints will be reviewed
|
||||
and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
This Code of Conduct is adapted from the Contributor Covenant, version 3.0,
|
||||
permanently available at
|
||||
[https://www.contributor-covenant.org/version/3/0/](https://www.contributor-covenant.org/version/3/0/).
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
||||
Contributor Covenant is stewarded by the Organization for Ethical Source and
|
||||
licensed under CC BY-SA 4.0. To view a copy of this license, visit
|
||||
[https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/)
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
For answers to common questions about Contributor Covenant, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq).
|
||||
Translations are provided at
|
||||
[https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations).
|
||||
Additional enforcement and community guideline resources can be found at
|
||||
[https://www.contributor-covenant.org/resources](https://www.contributor-covenant.org/resources).
|
||||
The enforcement ladder was inspired by the work of
|
||||
[Mozilla’s code of conduct team](https://github.com/mozilla/inclusion).
|
||||
|
|
|
|||
|
|
@ -1,8 +1,3 @@
|
|||
---
|
||||
title: Development setup
|
||||
sidebarDepth: 3
|
||||
---
|
||||
|
||||
# Setup your development environment
|
||||
|
||||
## Introduction
|
||||
|
|
@ -10,7 +5,7 @@ sidebarDepth: 3
|
|||
Castopod is a web app based on the `php` framework
|
||||
[CodeIgniter 4](https://codeigniter.com).
|
||||
|
||||
We use [Docker](https://www.docker.com/) quickly setup a dev environment. A
|
||||
We use [Docker](https://www.docker.com/) to quickly setup a dev environment. A
|
||||
`docker-compose.yml` and `Dockerfile` are included in the project's root folder
|
||||
to help you kickstart your contribution.
|
||||
|
||||
|
|
@ -21,9 +16,9 @@ to help you kickstart your contribution.
|
|||
|
||||
### 1. Pre-requisites
|
||||
|
||||
0. Install [docker](https://docs.docker.com/get-docker).
|
||||
0. Install [Docker](https://docs.docker.com/get-docker).
|
||||
|
||||
1. Clone Castopod project by running:
|
||||
1. Clone the Castopod repository by running:
|
||||
|
||||
```bash
|
||||
git clone https://code.castopod.org/adaures/castopod.git
|
||||
|
|
@ -34,7 +29,7 @@ to help you kickstart your contribution.
|
|||
|
||||
```ini
|
||||
CI_ENVIRONMENT="development"
|
||||
# If set to development, you must run `npm run dev` to start the static assets server
|
||||
# If set to development, you must run `pnpm run dev` to start the static assets server
|
||||
vite.environment="development"
|
||||
|
||||
# By default, this is set to true in the app config.
|
||||
|
|
@ -43,7 +38,6 @@ to help you kickstart your contribution.
|
|||
app.forceGlobalSecureRequests=false
|
||||
|
||||
app.baseURL="http://localhost:8080/"
|
||||
app.mediaBaseURL="http://localhost:8080/"
|
||||
|
||||
admin.gateway="cp-admin"
|
||||
auth.gateway="cp-auth"
|
||||
|
|
@ -52,25 +46,43 @@ to help you kickstart your contribution.
|
|||
database.default.database="castopod"
|
||||
database.default.username="castopod"
|
||||
database.default.password="castopod"
|
||||
database.default.DBPrefix="dev_"
|
||||
|
||||
analytics.salt="DEV_ANALYTICS_SALT"
|
||||
|
||||
cache.handler="redis"
|
||||
cache.redis.host = "redis"
|
||||
cache.redis.host="redis"
|
||||
|
||||
# You may not want to use redis as your cache handler
|
||||
# Comment/remove the two lines above and uncomment
|
||||
# the next line for file caching.
|
||||
# -----------------------
|
||||
#cache.handler="file"
|
||||
|
||||
######################################
|
||||
# Media config
|
||||
######################################
|
||||
media.baseURL="http://localhost:8080/"
|
||||
|
||||
# S3
|
||||
# Uncomment to store s3 objects using adobe/s3mock service
|
||||
# -----------------------
|
||||
#media.fileManager="s3"
|
||||
#media.s3.bucket="castopod"
|
||||
#media.s3.endpoint="http://172.31.0.6:9090/"
|
||||
#media.s3.pathStyleEndpoint=true
|
||||
```
|
||||
|
||||
> _NB._ You can tweak your environment by setting more environment variables
|
||||
> in your custom `.env` file. See the `env` for examples or the
|
||||
> [!NOTE]
|
||||
> You can tweak your environment by setting more environment variables in
|
||||
> your custom `.env` file. See the `env` for examples or the
|
||||
> [CodeIgniter4 User Guide](https://codeigniter.com/user_guide/index.html)
|
||||
> for more info.
|
||||
|
||||
3. (for docker desktop) Add the repository you've cloned to docker desktop's
|
||||
3. (for Docker desktop) Add the repository you've cloned to Docker desktop's
|
||||
`Settings` > `Resources` > `File Sharing`
|
||||
|
||||
### 2. (recommended) Develop inside the app Container with VSCode
|
||||
### 2. (recommended) Develop inside the app container with VSCode
|
||||
|
||||
If you're working in VSCode, you can take advantage of the `.devcontainer/`
|
||||
folder. It defines a development environment (dev container) with preinstalled
|
||||
|
|
@ -84,16 +96,16 @@ required services will be loaded automagically! 🪄
|
|||
> The VSCode window will reload inside the dev container. Expect several
|
||||
> minutes during first load as it is building all necessary services.
|
||||
|
||||
**Note**: The dev container will start by running Castopod's php server.
|
||||
**Note**: The dev container will start by running Castopod's PHP server.
|
||||
During development, you will have to start [Vite](https://vitejs.dev)'s dev
|
||||
server for compiling the typescript code and styles:
|
||||
|
||||
```bash
|
||||
# run Vite dev server
|
||||
npm run dev
|
||||
pnpm run dev
|
||||
```
|
||||
|
||||
If there is any issue with the php server not running, you can restart them
|
||||
If there is any issue with the PHP server not running, you can restart them
|
||||
using the following commands:
|
||||
|
||||
```bash
|
||||
|
|
@ -113,15 +125,15 @@ required services will be loaded automagically! 🪄
|
|||
# Composer is installed
|
||||
composer -V
|
||||
|
||||
# npm is installed
|
||||
npm -v
|
||||
# pnpm is installed
|
||||
pnpm -v
|
||||
|
||||
# git is installed
|
||||
git version
|
||||
```
|
||||
|
||||
For more info, see
|
||||
[VSCode Remote Containers](https://code.visualstudio.com/docs/remote/containers)
|
||||
[Developing inside a Container](https://code.visualstudio.com/docs/devcontainers/containers)
|
||||
|
||||
### 3. Start hacking
|
||||
|
||||
|
|
@ -132,9 +144,12 @@ more insights.
|
|||
|
||||
To see your changes, go to:
|
||||
|
||||
- `http://localhost:8080/` for the Castopod app
|
||||
- `http://localhost:8888/` for the phpmyadmin interface:
|
||||
- `http://localhost:8080/` for the Castopod website
|
||||
- `http://localhost:8080/cp-admin` for the Castopod admin:
|
||||
- email: **admin@castopod.local**
|
||||
- password: **castopod**
|
||||
|
||||
- `http://localhost:8888/` for the phpmyadmin interface:
|
||||
- username: **castopod**
|
||||
- password: **castopod**
|
||||
|
||||
|
|
@ -142,9 +157,9 @@ To see your changes, go to:
|
|||
|
||||
You do not wish to use the VSCode devcontainer? No problem!
|
||||
|
||||
1. Start docker containers manually:
|
||||
1. Start the Docker containers manually:
|
||||
|
||||
Go to project's root folder and run:
|
||||
Go to the project's root folder and run:
|
||||
|
||||
```bash
|
||||
# starts all services declared in docker-compose.yml file
|
||||
|
|
@ -159,7 +174,7 @@ You do not wish to use the VSCode devcontainer? No problem!
|
|||
|
||||
```
|
||||
|
||||
> The `docker-compose up -d` command will boot 4 containers in the
|
||||
> The `docker-compose up -d` command will boot 5 containers in the
|
||||
> background:
|
||||
>
|
||||
> - `castopod_app`: a php based container with Castopod requirements
|
||||
|
|
@ -170,6 +185,7 @@ You do not wish to use the VSCode devcontainer? No problem!
|
|||
> persistent data
|
||||
> - `castopod_phpmyadmin`: a phpmyadmin server to visualize the mariadb
|
||||
> database.
|
||||
> - `castopod_s3`: a mock s3 server to work on the s3 fileManager
|
||||
|
||||
2. Run any command inside the containers by prefixing them with
|
||||
`docker-compose run --rm app`:
|
||||
|
|
@ -181,8 +197,8 @@ You do not wish to use the VSCode devcontainer? No problem!
|
|||
# use Composer
|
||||
docker-compose run --rm app composer -V
|
||||
|
||||
# use npm
|
||||
docker-compose run --rm app npm -v
|
||||
# use pnpm
|
||||
docker-compose run --rm app pnpm -v
|
||||
|
||||
# use git
|
||||
docker-compose run --rm app git version
|
||||
|
|
@ -200,57 +216,46 @@ You do not wish to use the VSCode devcontainer? No problem!
|
|||
composer install
|
||||
```
|
||||
|
||||
::: info Note
|
||||
> [!NOTE]
|
||||
> The php dependencies aren't included in the repository. Composer will check
|
||||
> the `composer.json` and `composer.lock` files to download the packages with
|
||||
> the right versions. The dependencies will live under the `vendor/` folder.
|
||||
> For more info, check out the
|
||||
> [Composer documentation](https://getcomposer.org/doc/).
|
||||
|
||||
The php dependencies aren't included in the repository. Composer will check
|
||||
the `composer.json` and `composer.lock` files to download the packages with
|
||||
the right versions. The dependencies will live under the `vendor/` folder.
|
||||
For more info, check out the
|
||||
[Composer documentation](https://getcomposer.org/doc/).
|
||||
|
||||
:::
|
||||
|
||||
2. Install javascript dependencies with [npm](https://www.npmjs.com/)
|
||||
2. Install JavaScript dependencies with [pnpm](https://pnpm.io/)
|
||||
|
||||
```bash
|
||||
npm install
|
||||
pnpm install
|
||||
```
|
||||
|
||||
::: info Note
|
||||
|
||||
The javascript dependencies aren't included in the repository. Npm will check
|
||||
the `package.json` and `package.lock` files to download the packages with the
|
||||
right versions. The dependencies will live under the `node_module` folder.
|
||||
For more info, check out the [NPM documentation](https://docs.npmjs.com/).
|
||||
|
||||
:::
|
||||
> [!NOTE]
|
||||
> The JavaScript dependencies aren't included in the repository. Pnpm will
|
||||
> check the `package.json` and `pnpm-lock.yaml` files to download the
|
||||
> packages with the right versions. The dependencies will live under the
|
||||
> `node_module` folder. For more info, check out the
|
||||
> [PNPM documentation](https://pnpm.io/motivation).
|
||||
|
||||
3. Generate static assets:
|
||||
|
||||
```bash
|
||||
# build all static assets at once
|
||||
npm run build:static
|
||||
pnpm run build:static
|
||||
|
||||
# build specific assets
|
||||
npm run build:icons
|
||||
npm run build:svg
|
||||
pnpm run build:icons
|
||||
pnpm run build:svg
|
||||
```
|
||||
|
||||
::: info Note
|
||||
|
||||
The static assets generated live under the `public/assets` folder, it
|
||||
includes javascript, styles, images, fonts, icons and svg files.
|
||||
|
||||
:::
|
||||
> [!NOTE]
|
||||
> The static assets generated live under the `public/assets` folder, it
|
||||
> includes JavaScript, styles, images, fonts, icons and svg files.
|
||||
|
||||
### Initialize and populate database
|
||||
|
||||
::: tip Tip
|
||||
|
||||
You may skip this section if you go through the install wizard (go to
|
||||
`/cp-install`).
|
||||
|
||||
:::
|
||||
> [!TIP]
|
||||
> You may skip this section if you go through the install wizard (go to
|
||||
> `/cp-install`).
|
||||
|
||||
1. Build the database with the migrate command:
|
||||
|
||||
|
|
@ -270,7 +275,7 @@ You may skip this section if you go through the install wizard (go to
|
|||
|
||||
```bash
|
||||
# Populates all required data
|
||||
php spark db:seed AppSeeder
|
||||
php spark db:seed DevSeeder
|
||||
```
|
||||
|
||||
You may choose to add data separately:
|
||||
|
|
@ -282,21 +287,11 @@ You may skip this section if you go through the install wizard (go to
|
|||
# Populates all Languages
|
||||
php spark db:seed LanguageSeeder
|
||||
|
||||
# Populates all podcasts platforms
|
||||
php spark db:seed PlatformSeeder
|
||||
|
||||
# Populates all Authentication data (roles definition…)
|
||||
php spark db:seed AuthSeeder
|
||||
```
|
||||
|
||||
3. (optionnal) Populate the database with test data:
|
||||
|
||||
- Populate test data (login: admin / password: AGUehL3P)
|
||||
|
||||
```bash
|
||||
php spark db:seed TestSeeder
|
||||
# Adds a superadmin with [admin@castopod.local / castopod] credentials
|
||||
php spark db:seed DevSuperadminSeeder
|
||||
```
|
||||
|
||||
3. (optional) Populate the database with test data:
|
||||
- Populate with fake podcast analytics:
|
||||
|
||||
```bash
|
||||
|
|
@ -309,11 +304,6 @@ You may skip this section if you go through the install wizard (go to
|
|||
php spark db:seed FakeWebsiteAnalyticsSeeder
|
||||
```
|
||||
|
||||
TestSeeder will add an active superadmin user with the following credentials:
|
||||
|
||||
- username: **admin**
|
||||
- password: **AGUehL3P**
|
||||
|
||||
### Useful docker / docker-compose commands
|
||||
|
||||
- Monitor the app container:
|
||||
|
|
@ -322,13 +312,13 @@ You may skip this section if you go through the install wizard (go to
|
|||
docker-compose logs --tail 50 --follow --timestamps app
|
||||
```
|
||||
|
||||
- Interact with redis server using included redis-cli command:
|
||||
- Interact with the Redis server using included redis-cli command:
|
||||
|
||||
```bash
|
||||
docker exec -it castopod_redis redis-cli
|
||||
```
|
||||
|
||||
- Monitor the redis container:
|
||||
- Monitor the Redis container:
|
||||
|
||||
```bash
|
||||
docker-compose logs --tail 50 --follow --timestamps redis
|
||||
|
|
@ -364,28 +354,52 @@ docker-compose down
|
|||
docker-compose build app
|
||||
```
|
||||
|
||||
Check [docker](https://docs.docker.com/engine/reference/commandline/docker/) and
|
||||
Check [Docker](https://docs.docker.com/engine/reference/commandline/docker/) and
|
||||
[docker-compose](https://docs.docker.com/compose/reference/) documentations for
|
||||
more insights.
|
||||
|
||||
### Updating Documentation
|
||||
|
||||
Castopod's documentation is written in Markdown and uses the Astro Starlight
|
||||
framework. To update Castopod's documentation, including the Getting Started
|
||||
guide and User Guide:
|
||||
|
||||
1. Change directories to the `docs` directory and install the dependencies:
|
||||
|
||||
```bash
|
||||
cd docs/
|
||||
pnpm i
|
||||
```
|
||||
|
||||
2. Start the documentation development server:
|
||||
|
||||
```bash
|
||||
pnpm run dev --host
|
||||
```
|
||||
|
||||
3. The documentation development server runs on port 4321. In your browser visit
|
||||
`http://localhost:4321/docs`. If the page displays a 404 Not Found error,
|
||||
click on the Castopod logo in the upper left hand corner of the page and the
|
||||
documentation should load.
|
||||
|
||||
4. Edit the Markdown files with your documentation updates. The Astro Starlight
|
||||
development server will automatically update each time you save a change.
|
||||
|
||||
## Known issues
|
||||
|
||||
### Allocation failed - JavaScript heap out of memory
|
||||
|
||||
This happens when running `npm install`.
|
||||
This happens when running `pnpm install`.
|
||||
|
||||
👉 By default, docker might not have access to enough RAM. Allocate more memory
|
||||
and run `npm install` again.
|
||||
and run `pnpm install` again.
|
||||
|
||||
### (Linux) Files created inside container are attributed to root locally
|
||||
|
||||
You may use Linux user namespaces to fix this on your machine:
|
||||
|
||||
::: info Note
|
||||
|
||||
Replace "username" with your local username
|
||||
|
||||
:::
|
||||
> [!NOTE]
|
||||
> Replace "username" with your local username
|
||||
|
||||
1. Go to `/etc/docker/daemon.json` and add:
|
||||
|
||||
|
|
@ -409,7 +423,7 @@ Replace "username" with your local username
|
|||
username:100000:65536
|
||||
```
|
||||
|
||||
3. Restart docker:
|
||||
3. Restart Docker:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart docker
|
||||
169
CONTRIBUTING.md
169
CONTRIBUTING.md
|
|
@ -1,4 +1,167 @@
|
|||
# Contributing guidelines
|
||||
# Contributing to Castopod
|
||||
|
||||
You may find the contributing guidelines in the
|
||||
[Castopod documentation website](https://docs.castopod.org/contributing/guidelines.html).
|
||||
Love Castopod and want to help? Thanks so much, there's something to do for
|
||||
everybody!
|
||||
|
||||
> [!NOTE]
|
||||
> Castopod follows the [all contributors](https://allcontributors.org/)
|
||||
> specification in an effort to **recognize any kind of contribution**, not just
|
||||
> code!
|
||||
> If you've made a contribution and do not appear in the
|
||||
> [contributors](../index.md#contributors-✨) list, please
|
||||
> [let us know](../index.md#contact) so we can correct our mistake! 🙂
|
||||
|
||||
Please take a moment to review this document in order to make the contribution
|
||||
process easy and effective for everyone involved.
|
||||
|
||||
Following these guidelines helps to communicate that you respect the time of the
|
||||
developers managing and developing this open source project. In return, they
|
||||
should reciprocate that respect in addressing your issue or assessing patches
|
||||
and features.
|
||||
|
||||
## Translating Castopod
|
||||
|
||||
We use [Crowdin](https://translate.castopod.org/) to manage translation files
|
||||
for [Castopod](https://code.castopod.org/), the
|
||||
[documentation](https://docs.castopod.org/) and the
|
||||
[landing](https://castopod.org/) websites.
|
||||
|
||||
Whether you'd like to correct a translation error, validate new translations or
|
||||
include your language to Castopod, head into the
|
||||
[crowdin project](https://translate.castopod.org/) to get started.
|
||||
|
||||
> [!NOTE]
|
||||
> To prevent degrading user experience, new languages are included to Castopod
|
||||
> when they reach a certain threshold (~90%).
|
||||
|
||||
## Using the issue tracker
|
||||
|
||||
The [issue tracker](https://code.castopod.org/adaures/castopod/-/issues) is the
|
||||
preferred channel for [bug reports](#bug-reports),
|
||||
[features requests](#feature-requests) and
|
||||
[submitting pull requests](#pull-requests).
|
||||
|
||||
## ⚠️ Security issues and vulnerabilities
|
||||
|
||||
If you encounter any security issue or vulnerability in the Castopod source,
|
||||
please contact us directly by email at
|
||||
[security@castopod.org](mailto:security@castopod.org)
|
||||
|
||||
## Bug reports
|
||||
|
||||
A bug is a _demonstrable problem_ that is caused by the code in the repository.
|
||||
Good bug reports are extremely helpful - thank you!
|
||||
|
||||
Guidelines for bug reports:
|
||||
|
||||
1. **Use the issue search** — check if the issue has already been
|
||||
reported.
|
||||
|
||||
2. **Check if the issue has been fixed** — try to reproduce it using the
|
||||
latest `main` branch in the repository.
|
||||
|
||||
3. **Isolate the problem** — ideally create a
|
||||
[reduced test case](https://css-tricks.com/reduced-test-cases/) and a live
|
||||
example.
|
||||
|
||||
A good bug report shouldn't leave others needing to chase you up for more
|
||||
information. Please try to be as detailed as possible in your report. What is
|
||||
your environment? What steps will reproduce the issue? What browser(s) and OS
|
||||
experience the problem? What would you expect to be the outcome? All these
|
||||
details will help people to fix any potential bugs.
|
||||
|
||||
> [!NOTE]
|
||||
> [Issue templates](https://docs.gitlab.com/ee/user/project/description_templates.html#using-the-templates) have
|
||||
> been created for this project. You may use them to help you follow those
|
||||
> guidelines.
|
||||
|
||||
## Feature requests
|
||||
|
||||
Feature requests are welcome. But take a moment to find out whether your idea
|
||||
fits with the scope and aims of the project. It's up to _you_ to make a strong
|
||||
case to convince the project's developers of the merits of this feature. Please
|
||||
provide as much detail and context as possible.
|
||||
|
||||
## Pull requests
|
||||
|
||||
Good pull requests - patches, improvements, new features - are a fantastic help.
|
||||
They should remain focused in scope and avoid containing unrelated commits.
|
||||
|
||||
**Please ask first** before embarking on any significant pull request (e.g.
|
||||
implementing features, refactoring code, porting to a different language),
|
||||
otherwise you risk spending a lot of time working on something that the
|
||||
project's developers might not want to merge into the project.
|
||||
|
||||
Please adhere to the coding conventions used throughout a project (indentation,
|
||||
accurate comments, etc.) and any other requirements (such as test coverage).
|
||||
|
||||
Adhering to the following process is the best way to get your work included in
|
||||
the project:
|
||||
|
||||
1. [Fork](https://docs.gitlab.com/ee/user/project/repository/forking_workflow.html)
|
||||
the project, clone your fork, and configure the remotes:
|
||||
|
||||
```bash
|
||||
# Clone your fork of the repo into the current directory
|
||||
git clone https://code.castopod.org/<your-username>/castopod.git
|
||||
|
||||
# Navigate to the newly cloned directory
|
||||
cd castopod
|
||||
|
||||
# Assign the original repo to a remote called "upstream"
|
||||
git remote add upstream https://code.castopod.org/adaures/castopod.git
|
||||
```
|
||||
|
||||
2. If you cloned a while ago, get the latest changes from upstream:
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull upstream main
|
||||
```
|
||||
|
||||
3. Create a new topic branch (off the `main` branch) to contain your feature,
|
||||
change, or fix:
|
||||
|
||||
```bash
|
||||
git checkout -b <topic-branch-name>
|
||||
```
|
||||
|
||||
4. Commit your changes in logical chunks. Please adhere to these
|
||||
[git commit message guidelines](https://conventionalcommits.org/) or your
|
||||
code is unlikely be merged into the main project. Use Git's
|
||||
[interactive rebase](https://help.github.com/articles/about-git-rebase/)
|
||||
feature to tidy up your commits before making them public.
|
||||
|
||||
5. Locally merge (or rebase) the upstream dev branch into your topic branch:
|
||||
|
||||
```bash
|
||||
git pull [--rebase] upstream main
|
||||
```
|
||||
|
||||
6. Push your topic branch up to your fork:
|
||||
|
||||
```bash
|
||||
git push origin <topic-branch-name>
|
||||
```
|
||||
|
||||
7. [Open a Pull Request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html#new-merge-request-from-a-fork)
|
||||
with a clear title and description.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> By submitting a patch, you agree to allow the project owners to license your
|
||||
> work under the terms of the
|
||||
> [GNU AGPLv3](https://code.castopod.org/adaures/castopod/-/blob/develop/LICENSE.md).
|
||||
|
||||
## Collaborating guidelines
|
||||
|
||||
There are few basic rules to ensure high quality of the project:
|
||||
|
||||
- Before merging, a PR requires at least two approvals from the collaborators
|
||||
unless it's an architectural change, a large feature, etc. If it is, then at
|
||||
least 50% of the core team have to agree to merge it, with every team member
|
||||
having a full veto right. (i.e. every single one can block any PR)
|
||||
- A PR should remain open for at least two days before merging (does not apply
|
||||
for trivial contributions like fixing a typo). This way everyone has enough
|
||||
time to look into it.
|
||||
|
||||
You are always welcome to discuss and propose improvements to this guideline.
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ Javascript dependencies can be found in the [package.json](./package.json) file.
|
|||
([Open Font License](https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL))
|
||||
- [RemixIcon](https://remixicon.com/)
|
||||
([Apache License 2.0](https://github.com/Remix-Design/RemixIcon/blob/master/License))
|
||||
- [OPAWG/User agent list](https://github.com/opawg/user-agents)
|
||||
- [OPAWG/User agent list](https://github.com/opawg/user-agents-v2)
|
||||
([by Open Podcast Analytics Working Group](https://github.com/opawg))
|
||||
([MIT license](https://github.com/opawg/user-agents/blob/master/LICENSE))
|
||||
([MIT license](https://github.com/opawg/user-agents-v2/blob/master/LICENSE))
|
||||
- [OPAWG/podcast-rss-useragents](https://github.com/opawg/podcast-rss-useragents)
|
||||
([by Open Podcast Analytics Working Group](https://github.com/opawg))
|
||||
([MIT license](https://github.com/opawg/podcast-rss-useragents/blob/master/LICENSE))
|
||||
|
|
|
|||
153
README.md
153
README.md
|
|
@ -1,7 +1,7 @@
|
|||
<div align="center">
|
||||
<h1>
|
||||
<a href="https://castopod.org/">
|
||||
<img src="https://docs.castopod.org/images/castopod-logo-inline.svg" alt="Castopod" height="64px" />
|
||||
<img src="./docs/src/assets/castopod-logo-inline.svg" alt="Castopod" height="64px" />
|
||||
</a>
|
||||
</h1>
|
||||
</div>
|
||||
|
|
@ -15,16 +15,11 @@
|
|||
Castopod is a free and open-source podcast hosting solution made for podcasters
|
||||
who want engage and interact with their audience.
|
||||
|
||||
> **Status**
|
||||
>
|
||||
> Castopod is currently in **beta** but already quite stable and used by
|
||||
> podcasters around the world!
|
||||
|
||||
## Getting started
|
||||
|
||||
To get started with Castopod, you may
|
||||
[check out the documentation](https://docs.castopod.org/), everything should be
|
||||
there!
|
||||
Castopod comes pre-packaged with all the required static assets and
|
||||
dependencies, you may download and install it by checking out the
|
||||
[getting started page](https://castopod.org/getting-started/)!
|
||||
|
||||
## Security issues and vulnerabilities
|
||||
|
||||
|
|
@ -36,12 +31,9 @@ please contact us directly by email at
|
|||
|
||||
Contributions are always welcome!
|
||||
|
||||
See the
|
||||
[contribution guidelines](https://docs.castopod.org/contributing/guidelines) for
|
||||
ways to get started.
|
||||
See the [contribution guidelines](./CONTRIBUTING.md) for ways to get started.
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> [!Important]
|
||||
> **Any** contribution made on a repository other than
|
||||
> [the original repository](https://code.castopod.org/adaures/castopod) will not
|
||||
> be accepted.
|
||||
|
|
@ -55,63 +47,76 @@ Thanks goes to these wonderful people
|
|||
<!-- prettier-ignore-start -->
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/yassinedoghri"><img src="https://code.castopod.org/uploads/-/system/user/avatar/3/avatar.png?s=100" width="100px;" alt=""/><br /><sub><b>Yassine Doghri</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/commits/master" title="Code">💻</a> <a href="https://code.castopod.org/adaures/castopod/issues?author_username=yassinedoghri" title="Bug reports">🐛</a> <a href="https://code.castopod.org/adaures/castopod/commits/master" title="Documentation">📖</a> <a href="https://code.castopod.org/adaures/castopod/merge_requests?scope=all&state=all&approver_usernames[]=yassinedoghri" title="Reviewed Pull Requests">👀</a> <a href="#maintenance-yassinedoghri" title="Maintenance">🚧</a> <a href="#content-yassinedoghri" title="Content">🖋</a> <a href="#design-yassinedoghri" title="Design">🎨</a> <a href="#a11y-yassinedoghri" title="Accessibility">️️️️♿️</a> <a href="https://translate.castopod.org" title="Translation">🌍</a> <a href="#question-yassinedoghri" title="Answering Questions">💬</a> <a href="#mentoring-yassinedoghri" title="Mentoring">🧑🏫</a> <a href="#infra-yassinedoghri" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#ideas-yassinedoghri" title="Ideas, Planning, & Feedback">🤔</a> <a href="#projectManagement-yassinedoghri" title="Project Management">📆</a> <a href="https://blog.castopod.org/author/yassinedoghri/" title="Blogposts">📝</a></td>
|
||||
<td align="center"><a href="https://code.castopod.org/benjamin"><img src="https://code.castopod.org/uploads/-/system/user/avatar/2/avatar.png?s=100" width="100px;" alt=""/><br /><sub><b>Benjamin Bellamy</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/commits/master" title="Code">💻</a> <a href="https://code.castopod.org/adaures/castopod/issues?author_username=benjamin" title="Bug reports">🐛</a> <a href="https://code.castopod.org/adaures/castopod/merge_requests?scope=all&state=all&approver_usernames[]=benjamin" title="Reviewed Pull Requests">👀</a> <a href="#content-benjamin" title="Content">🖋</a> <a href="https://translate.castopod.org" title="Translation">🌍</a> <a href="#question-benjamin" title="Answering Questions">💬</a> <a href="#infra-benjamin" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#ideas-benjamin" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://blog.castopod.org/author/benjamin-bellamy/" title="Blogposts">📝</a> <a href="#projectManagement-benjamin" title="Project Management">📆</a> <a href="#talk-benjamin" title="Talks">📢</a></td>
|
||||
<td align="center"><a href="https://github.com/ola-hn"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt=""/><br /><sub><b>Ola Hneini</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/commits/master" title="Code">💻</a> <a href="https://code.castopod.org/adaures/castopod/merge_requests?scope=all&state=all&approver_usernames[]=ola" title="Reviewed Pull Requests">👀</a> <a href="https://code.castopod.org/adaures/castopod/commits/master" title="Documentation">📖</a> <a href="#maintenance-ola" title="Maintenance">🚧</a> <a href="#question-ola" title="Answering Questions">💬</a> <a href="#ideas-ola" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://mamot.fr/@rdelaage"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt=""/><br /><sub><b>Romain de Laage</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/commits/master" title="Code">💻</a> <a href="#infra-rdelaage" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://code.castopod.org/adaures/castopod/commits/master" title="Documentation">📖</a> <a href="https://translate.castopod.org" title="Translation">🌍</a> <a href="#ideas-rdelaage" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://twitter.com/lyonelbernard"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt=""/><br /><sub><b>Lyonel Bernard</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=Lyonel" title="Bug reports">🐛</a> <a href="#question-Lyonel" title="Answering Questions">💬</a> <a href="#audio-Lyonel" title="Audio">🔊</a> <a href="#ideas-Lyonel" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://www.crypticchameleon.com/"><img src="https://secure.gravatar.com/avatar/7c2a721b52d0763673a600e8f01bd745?s=80&d=identicon?s=100" width="100px;" alt=""/><br /><sub><b>Christopher Lagonick-Weitzel</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=ctlw83" title="Bug reports">🐛</a> <a href="#question-ctlw83" title="Answering Questions">💬</a> <a href="#audio-ctlw83" title="Audio">🔊</a> <a href="#ideas-ctlw83" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://ernestoacosta.me/"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt=""/><br /><sub><b>Ernesto Acosta</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=ernestoacostame" title="Bug reports">🐛</a> <a href="#audio-ernestoacostame" title="Audio">🔊</a> <a href="https://translate.castopod.org" title="Translation">🌍</a> <a href="#question-ernestoacostame" title="Answering Questions">💬</a> <a href="#ideas-ernestoacostame" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://code.castopod.org/Behel"><img src="https://secure.gravatar.com/avatar/ad63ee8ef8e3db8253d21e5012d2724f?s=80&d=identicon?s=100" width="100px;" alt=""/><br /><sub><b>Bastien Luneteau</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/commits/master" title="Code">💻</a> <a href="https://code.castopod.org/adaures/castopod/issues?author_username=Behel" title="Bug reports">🐛</a></td>
|
||||
<td align="center"><a href="https://www.cecillie.fr/"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt=""/><br /><sub><b>Cécile Ricordeau</b></sub></a><br /><a href="#design-cecillie" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="https://code.castopod.org/PatrykMis"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt=""/><br /><sub><b>Patryk Miś</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://code.castopod.org/mspanc"><img src="https://secure.gravatar.com/avatar/eed8337939641eac5ad0b570bd6acf96?s=80&d=identicon?s=100" width="100px;" alt=""/><br /><sub><b>Marcin Lewandowski</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=mspanc" title="Bug reports">🐛</a> <a href="#ideas-mspanc" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://code.castopod.org/SJanik"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt=""/><br /><sub><b>Sebastian Janik</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/commits/master" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://code.castopod.org/patryk"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt=""/><br /><sub><b>Patryk Karczmarczyk</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/commits/master" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://code.castopod.org/ddenis"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt=""/><br /><sub><b>denis d</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=ddenis" title="Bug reports">🐛</a> <a href="#ideas-ddenis" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://code.castopod.org/douglaskastle"><img src="https://secure.gravatar.com/avatar/b7e652ba4b6bcd440afa069e7f7bc9e6?s=80&d=identicon?s=100" width="100px;" alt=""/><br /><sub><b>Douglas Kastle</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=douglaskastle" title="Bug reports">🐛</a> <a href="#ideas-douglaskastle" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://code.castopod.org/cExplorer"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt=""/><br /><sub><b>cExplorer</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=cExplorer" title="Bug reports">🐛</a> <a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://code.castopod.org/imacrea"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt=""/><br /><sub><b>ImaCrea</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=imacrea" title="Bug reports">🐛</a> <a href="#ideas-imacrea" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://code.castopod.org/jonas"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt=""/><br /><sub><b>Jonas S</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/commits/master" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://code.castopod.org/yannL"><img src="https://secure.gravatar.com/avatar/9c46600ce566ec6d526370d8e104b1c8?s=80&d=identicon?s=100" width="100px;" alt=""/><br /><sub><b>LEFEBVRE Yann</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=yannL" title="Bug reports">🐛</a></td>
|
||||
<td align="center"><a href="https://code.castopod.org/spaetz"><img src="https://secure.gravatar.com/avatar/278e1af65e82993efd0ba7bbbacf6435?s=80&d=identicon?s=100" width="100px;" alt=""/><br /><sub><b>Sebastian Späth</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=spaetz" title="Bug reports">🐛</a> <a href="#ideas-spaetz" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://code.castopod.org/rocky"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt=""/><br /><sub><b>rocky III</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=rocky" title="Bug reports">🐛</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://code.castopod.org/Regenpfeifer"><img src="https://code.castopod.org/uploads/-/system/user/avatar/103/avatar.png?s=100" width="100px;" alt=""/><br /><sub><b>Hermann Josef Eckl</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=Regenpfeifer" title="Bug reports">🐛</a></td>
|
||||
<td align="center"><a href="https://code.castopod.org/cyrilledel"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt=""/><br /><sub><b>Delhaye Cyrille</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=cyrilledel" title="Bug reports">🐛</a> <a href="#ideas-cyrilledel" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://twitter.com/otetranome"><img src="https://code.castopod.org/uploads/-/system/user/avatar/113/avatar.png?s=100" width="100px;" alt=""/><br /><sub><b>João Leandro</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a> <a href="#ideas-otetranome" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://achouvardas.eu/"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt=""/><br /><sub><b>Angelos Chouvardas</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://mastodon.fjerland.no/@eivind"><img src="https://mastodon.fjerland.no/system/accounts/avatars/107/769/768/295/192/222/original/e5c985fea6487dcb.jpg?s=100" width="100px;" alt=""/><br /><sub><b>Eivind</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://mastodon.fedi.bzh/@ewen"><img src="https://mastodon.fedi.bzh/system/accounts/avatars/000/000/002/original/6f387690a504ae46.jpg?s=100" width="100px;" alt=""/><br /><sub><b>Ewen</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a> <a href="#ideas-3wen" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://crowdin.com/profile/forght"><img src="https://crowdin-static.downloads.crowdin.com/avatar/15073833/large/82d1e2e443a6df7edc43a7405dfeeb75_default.png?s=100" width="100px;" alt=""/><br /><sub><b>forght</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://crowdin.com/profile/glottis0q"><img src="https://crowdin-static.downloads.crowdin.com/avatar/15209934/large/8b17ef6a7399f0b82a8198f87c224195.png?s=100" width="100px;" alt=""/><br /><sub><b>glottis0q</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://mstdn.fr/@ButterflyOfFire"><img src="https://static.mstdn.fr/static/accounts/avatars/000/065/901/original/e18d44b28edd0ada.png?s=100" width="100px;" alt=""/><br /><sub><b>ButterflyOfFire</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://github.com/lil5"><img src="https://avatars.githubusercontent.com/u/17646836?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lucian I. Last</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://crowdin.com/profile/luuzviir"><img src="https://crowdin-static.downloads.crowdin.com/avatar/13166188/large/d03ab0abc7ce354b210d836955cd3805_default.png?s=100" width="100px;" alt=""/><br /><sub><b>LuuzViir</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://crowdin.com/profile/cthtc"><img src="https://crowdin-static.downloads.crowdin.com/avatar/15211502/large/ed0651060cb8474a9519b5168bd377c1_default.png?s=100" width="100px;" alt=""/><br /><sub><b>CTHTC</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://crowdin.com/profile/retrograde"><img src="https://crowdin-static.downloads.crowdin.com/avatar/15021651/large/b10c4057f85bf4de49c7fdf01354ecde.jpeg?s=100" width="100px;" alt=""/><br /><sub><b>Russian Retro</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://crowdin.com/profile/mareklach"><img src="https://crowdin-static.downloads.crowdin.com/avatar/13572324/large/3eeba8d569c247ace33862bf4ef4748f.jpeg?s=100" width="100px;" alt=""/><br /><sub><b>Marek L'ach</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://crowdin.com/profile/gunchleoc"><img src="https://crowdin-static.downloads.crowdin.com/avatar/13043878/large/3223f7b606296a8b1c92c5de39c459a2_default.png?s=100" width="100px;" alt=""/><br /><sub><b>GunChleoc</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://crowdin.com/profile/gabisnow"><img src="https://crowdin-static.downloads.crowdin.com/avatar/15214858/large/5b083bdf9c9e9de67cc6ee72a6c8db18_default.png?s=100" width="100px;" alt=""/><br /><sub><b>GabiSnow</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://crowdin.com/profile/bendaha"><img src="https://crowdin-static.downloads.crowdin.com/avatar/15331656/large/cd92450d2c20202299fb3a0075903e20_default.png?s=100" width="100px;" alt=""/><br /><sub><b>bendaha</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://crowdin.com/profile/samuelroland"><img src="https://crowdin-static.downloads.crowdin.com/avatar/14980053/large/3e154a37d03d6e98ae402ed3f930f4f5.png?s=100" width="100px;" alt=""/><br /><sub><b>Samuel Roland</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://dimitriregnier.net/"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt=""/><br /><sub><b>Dimitri Regnier</b></sub></a><br /><a href="#ideas-dimregnier" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://im.irithys.com/@thy"><img src="https://crowdin-static.downloads.crowdin.com/avatar/15405614/large/e46d7f8e9f7c05997827563c3a3cf942.jpeg?s=100" width="100px;" alt=""/><br /><sub><b>irithys</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center"><a href="https://twitter.com/caos30"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt=""/><br /><sub><b>Sergi</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://crowdin.com/profile/xosem"><img src="https://crowdin-static.downloads.crowdin.com/avatar/12617257/large/a201650da44fed28890b0e0d8477a663.jpg?s=100" width="100px;" alt=""/><br /><sub><b>ghose (XoseM)</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
</tr>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://yassinedoghri.com"><img src="https://avatars.githubusercontent.com/u/11021441?v=4?s=100" width="100px;" alt="Yassine Doghri"/><br /><sub><b>Yassine Doghri</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/commits/master" title="Code">💻</a> <a href="https://code.castopod.org/adaures/castopod/issues?author_username=yassinedoghri" title="Bug reports">🐛</a> <a href="https://code.castopod.org/adaures/castopod/commits/master" title="Documentation">📖</a> <a href="https://code.castopod.org/adaures/castopod/merge_requests?scope=all&state=all&approver_usernames[]=yassinedoghri" title="Reviewed Pull Requests">👀</a> <a href="#maintenance-yassinedoghri" title="Maintenance">🚧</a> <a href="#content-yassinedoghri" title="Content">🖋</a> <a href="#design-yassinedoghri" title="Design">🎨</a> <a href="#a11y-yassinedoghri" title="Accessibility">️️️️♿️</a> <a href="https://translate.castopod.org" title="Translation">🌍</a> <a href="#question-yassinedoghri" title="Answering Questions">💬</a> <a href="#mentoring-yassinedoghri" title="Mentoring">🧑🏫</a> <a href="#infra-yassinedoghri" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#ideas-yassinedoghri" title="Ideas, Planning, & Feedback">🤔</a> <a href="#projectManagement-yassinedoghri" title="Project Management">📆</a> <a href="https://blog.castopod.org/author/yassinedoghri/" title="Blogposts">📝</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://code.castopod.org/benjamin"><img src="https://code.castopod.org/uploads/-/system/user/avatar/2/avatar.png?s=100" width="100px;" alt="Benjamin Bellamy"/><br /><sub><b>Benjamin Bellamy</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/commits/master" title="Code">💻</a> <a href="https://code.castopod.org/adaures/castopod/issues?author_username=benjamin" title="Bug reports">🐛</a> <a href="https://code.castopod.org/adaures/castopod/merge_requests?scope=all&state=all&approver_usernames[]=benjamin" title="Reviewed Pull Requests">👀</a> <a href="#content-benjamin" title="Content">🖋</a> <a href="https://translate.castopod.org" title="Translation">🌍</a> <a href="#question-benjamin" title="Answering Questions">💬</a> <a href="#infra-benjamin" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#ideas-benjamin" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://blog.castopod.org/author/benjamin-bellamy/" title="Blogposts">📝</a> <a href="#projectManagement-benjamin" title="Project Management">📆</a> <a href="#talk-benjamin" title="Talks">📢</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ola-hn"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt="Ola Hneini"/><br /><sub><b>Ola Hneini</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/commits/master" title="Code">💻</a> <a href="https://code.castopod.org/adaures/castopod/merge_requests?scope=all&state=all&approver_usernames[]=ola" title="Reviewed Pull Requests">👀</a> <a href="https://code.castopod.org/adaures/castopod/commits/master" title="Documentation">📖</a> <a href="#maintenance-ola" title="Maintenance">🚧</a> <a href="#question-ola" title="Answering Questions">💬</a> <a href="#ideas-ola" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://mamot.fr/@rdelaage"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt="Romain de Laage"/><br /><sub><b>Romain de Laage</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/commits/master" title="Code">💻</a> <a href="#infra-rdelaage" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://code.castopod.org/adaures/castopod/commits/master" title="Documentation">📖</a> <a href="https://translate.castopod.org" title="Translation">🌍</a> <a href="#ideas-rdelaage" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://twitter.com/lyonelbernard"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt="Lyonel Bernard"/><br /><sub><b>Lyonel Bernard</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=Lyonel" title="Bug reports">🐛</a> <a href="#question-Lyonel" title="Answering Questions">💬</a> <a href="#audio-Lyonel" title="Audio">🔊</a> <a href="#ideas-Lyonel" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.crypticchameleon.com/"><img src="https://secure.gravatar.com/avatar/7c2a721b52d0763673a600e8f01bd745?s=80&d=identicon?s=100" width="100px;" alt="Christopher Lagonick-Weitzel"/><br /><sub><b>Christopher Lagonick-Weitzel</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=ctlw83" title="Bug reports">🐛</a> <a href="#question-ctlw83" title="Answering Questions">💬</a> <a href="#audio-ctlw83" title="Audio">🔊</a> <a href="#ideas-ctlw83" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://ernestoacosta.me/"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt="Ernesto Acosta"/><br /><sub><b>Ernesto Acosta</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=ernestoacostame" title="Bug reports">🐛</a> <a href="#audio-ernestoacostame" title="Audio">🔊</a> <a href="https://translate.castopod.org" title="Translation">🌍</a> <a href="#question-ernestoacostame" title="Answering Questions">💬</a> <a href="#ideas-ernestoacostame" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://mastodon.fedi.bzh/@ewen"><img src="https://mastodon.fedi.bzh/system/accounts/avatars/000/000/002/original/6f387690a504ae46.jpg?s=100" width="100px;" alt="Ewen"/><br /><sub><b>Ewen</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a> <a href="#ideas-3wen" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://code.castopod.org/adaures/castopod/commits/master" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://code.castopod.org/Behel"><img src="https://secure.gravatar.com/avatar/ad63ee8ef8e3db8253d21e5012d2724f?s=80&d=identicon?s=100" width="100px;" alt="Bastien Luneteau"/><br /><sub><b>Bastien Luneteau</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/commits/master" title="Code">💻</a> <a href="https://code.castopod.org/adaures/castopod/issues?author_username=Behel" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.cecillie.fr/"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt="Cécile Ricordeau"/><br /><sub><b>Cécile Ricordeau</b></sub></a><br /><a href="#design-cecillie" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://code.castopod.org/PatrykMis"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt="Patryk Miś"/><br /><sub><b>Patryk Miś</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://code.castopod.org/mspanc"><img src="https://secure.gravatar.com/avatar/eed8337939641eac5ad0b570bd6acf96?s=80&d=identicon?s=100" width="100px;" alt="Marcin Lewandowski"/><br /><sub><b>Marcin Lewandowski</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=mspanc" title="Bug reports">🐛</a> <a href="#ideas-mspanc" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://code.castopod.org/SJanik"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt="Sebastian Janik"/><br /><sub><b>Sebastian Janik</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/commits/master" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://code.castopod.org/patryk"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt="Patryk Karczmarczyk"/><br /><sub><b>Patryk Karczmarczyk</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/commits/master" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://code.castopod.org/ddenis"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt="denis d"/><br /><sub><b>denis d</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=ddenis" title="Bug reports">🐛</a> <a href="#ideas-ddenis" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://code.castopod.org/douglaskastle"><img src="https://secure.gravatar.com/avatar/b7e652ba4b6bcd440afa069e7f7bc9e6?s=80&d=identicon?s=100" width="100px;" alt="Douglas Kastle"/><br /><sub><b>Douglas Kastle</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=douglaskastle" title="Bug reports">🐛</a> <a href="#ideas-douglaskastle" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://code.castopod.org/cExplorer"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt="cExplorer"/><br /><sub><b>cExplorer</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=cExplorer" title="Bug reports">🐛</a> <a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://code.castopod.org/imacrea"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt="ImaCrea"/><br /><sub><b>ImaCrea</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=imacrea" title="Bug reports">🐛</a> <a href="#ideas-imacrea" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://code.castopod.org/jonas"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt="Jonas S"/><br /><sub><b>Jonas S</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/commits/master" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://code.castopod.org/yannL"><img src="https://secure.gravatar.com/avatar/9c46600ce566ec6d526370d8e104b1c8?s=80&d=identicon?s=100" width="100px;" alt="LEFEBVRE Yann"/><br /><sub><b>LEFEBVRE Yann</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=yannL" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://code.castopod.org/spaetz"><img src="https://secure.gravatar.com/avatar/278e1af65e82993efd0ba7bbbacf6435?s=80&d=identicon?s=100" width="100px;" alt="Sebastian Späth"/><br /><sub><b>Sebastian Späth</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=spaetz" title="Bug reports">🐛</a> <a href="#ideas-spaetz" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://code.castopod.org/rocky"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt="rocky III"/><br /><sub><b>rocky III</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=rocky" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://code.castopod.org/Regenpfeifer"><img src="https://code.castopod.org/uploads/-/system/user/avatar/103/avatar.png?s=100" width="100px;" alt="Hermann Josef Eckl"/><br /><sub><b>Hermann Josef Eckl</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=Regenpfeifer" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://code.castopod.org/cyrilledel"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt="Delhaye Cyrille"/><br /><sub><b>Delhaye Cyrille</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=cyrilledel" title="Bug reports">🐛</a> <a href="#ideas-cyrilledel" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://twitter.com/otetranome"><img src="https://code.castopod.org/uploads/-/system/user/avatar/113/avatar.png?s=100" width="100px;" alt="João Leandro"/><br /><sub><b>João Leandro</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a> <a href="#ideas-otetranome" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://achouvardas.eu/"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt="Angelos Chouvardas"/><br /><sub><b>Angelos Chouvardas</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://mastodon.fjerland.no/@eivind"><img src="https://mastodon.fjerland.no/system/accounts/avatars/107/769/768/295/192/222/original/e5c985fea6487dcb.jpg?s=100" width="100px;" alt="Eivind"/><br /><sub><b>Eivind</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://crowdin.com/profile/forght"><img src="https://crowdin-static.downloads.crowdin.com/avatar/15073833/large/82d1e2e443a6df7edc43a7405dfeeb75_default.png?s=100" width="100px;" alt="forght"/><br /><sub><b>forght</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://crowdin.com/profile/glottis0q"><img src="https://crowdin-static.downloads.crowdin.com/avatar/15209934/large/8b17ef6a7399f0b82a8198f87c224195.png?s=100" width="100px;" alt="glottis0q"/><br /><sub><b>glottis0q</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://mstdn.fr/@ButterflyOfFire"><img src="https://static.mstdn.fr/static/accounts/avatars/000/065/901/original/5908e93ad5447f15.png?s=100" width="100px;" alt="ButterflyOfFire"/><br /><sub><b>ButterflyOfFire</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lil5"><img src="https://avatars.githubusercontent.com/u/17646836?v=4?s=100" width="100px;" alt="Lucian I. Last"/><br /><sub><b>Lucian I. Last</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://crowdin.com/profile/luuzviir"><img src="https://crowdin-static.downloads.crowdin.com/avatar/13166188/large/d03ab0abc7ce354b210d836955cd3805_default.png?s=100" width="100px;" alt="LuuzViir"/><br /><sub><b>LuuzViir</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://crowdin.com/profile/cthtc"><img src="https://crowdin-static.downloads.crowdin.com/avatar/15211502/large/ed0651060cb8474a9519b5168bd377c1_default.png?s=100" width="100px;" alt="CTHTC"/><br /><sub><b>CTHTC</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://crowdin.com/profile/retrograde"><img src="https://crowdin-static.downloads.crowdin.com/avatar/15021651/large/b10c4057f85bf4de49c7fdf01354ecde.jpeg?s=100" width="100px;" alt="Russian Retro"/><br /><sub><b>Russian Retro</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://crowdin.com/profile/mareklach"><img src="https://crowdin-static.downloads.crowdin.com/avatar/13572324/large/3eeba8d569c247ace33862bf4ef4748f.jpeg?s=100" width="100px;" alt="Marek L'ach"/><br /><sub><b>Marek L'ach</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://crowdin.com/profile/gunchleoc"><img src="https://crowdin-static.downloads.crowdin.com/avatar/13043878/large/3223f7b606296a8b1c92c5de39c459a2_default.png?s=100" width="100px;" alt="GunChleoc"/><br /><sub><b>GunChleoc</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://crowdin.com/profile/gabisnow"><img src="https://crowdin-static.downloads.crowdin.com/avatar/15214858/large/5b083bdf9c9e9de67cc6ee72a6c8db18_default.png?s=100" width="100px;" alt="GabiSnow"/><br /><sub><b>GabiSnow</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://crowdin.com/profile/bendaha"><img src="https://crowdin-static.downloads.crowdin.com/avatar/15331656/large/cd92450d2c20202299fb3a0075903e20_default.png?s=100" width="100px;" alt="bendaha"/><br /><sub><b>bendaha</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://crowdin.com/profile/samuelroland"><img src="https://crowdin-static.downloads.crowdin.com/avatar/14980053/large/3e154a37d03d6e98ae402ed3f930f4f5.png?s=100" width="100px;" alt="Samuel Roland"/><br /><sub><b>Samuel Roland</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://dimitriregnier.net/"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt="Dimitri Regnier"/><br /><sub><b>Dimitri Regnier</b></sub></a><br /><a href="#ideas-dimregnier" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://im.irithys.com/@thy"><img src="https://crowdin-static.downloads.crowdin.com/avatar/15405614/large/3086461c47cce0a0c031925e5f943412.png?s=100" width="100px;" alt="irithys"/><br /><sub><b>irithys</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://twitter.com/caos30"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt="Sergi"/><br /><sub><b>Sergi</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://crowdin.com/profile/basen1982"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt="Andreas Olsson"/><br /><sub><b>Andreas Olsson</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://crowdin.com/profile/leonfrom"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt="leonfrom"/><br /><sub><b>leonfrom</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://crowdin.com/profile/agentcobra57"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt="agentcobra"/><br /><sub><b>agentcobra</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://crowdin.com/profile/alephoto85"><img src="https://crowdin-static.downloads.crowdin.com/avatar/15094649/large/530391f54157af52ae33058ec15b0f99.jpg?s=100" width="100px;" alt="Alessandro"/><br /><sub><b>Alessandro</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://crowdin.com/profile/liimee"><img src="https://castopod.org/assets/images/castopod-avatar.jpg?s=100" width="100px;" alt="liimee"/><br /><sub><b>liimee</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ahmedsabouni"><img src="https://avatars.githubusercontent.com/u/74497842?v=4?s=100" width="100px;" alt="Ahmed Sabouni"/><br /><sub><b>Ahmed Sabouni</b></sub></a><br /><a href="https://translate.castopod.org" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/KrzysztofDomanczyk"><img src="https://avatars.githubusercontent.com/u/75178474?v=4?s=100" width="100px;" alt="KrzysztofDomanczyk"/><br /><sub><b>KrzysztofDomanczyk</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/commits/master" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Dwev"><img src="https://avatars.githubusercontent.com/u/46626050?v=4?s=100" width="100px;" alt="Guy Martin"/><br /><sub><b>Guy Martin</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/issues?author_username=Dwev" title="Bug reports">🐛</a> <a href="https://code.castopod.org/adaures/castopod/commits/master" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/prcutler"><img src="https://avatars.githubusercontent.com/u/67276?v=4?s=100" width="100px;" alt="Paul Cutler"/><br /><sub><b>Paul Cutler</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/commits/master" title="Documentation">📖</a> <a href="#question-prcutler" title="Answering Questions">💬</a> <a href="#ideas-prcutler" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/nateritter"><img src="https://avatars.githubusercontent.com/u/198798?v=4?s=100" width="100px;" alt="Nate Ritter"/><br /><sub><b>Nate Ritter</b></sub></a><br /><a href="https://code.castopod.org/adaures/castopod/commits/master" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
|
|
@ -136,7 +141,7 @@ Alternatively, you can follow us on social media platforms to get news about
|
|||
Castopod:
|
||||
|
||||
- [podlibre.social](https://podlibre.social/@Castopod) (Mastodon instance)
|
||||
- [Twitter](https://twitter.com/castopod)
|
||||
- [Bluesky](https://bsky.app/profile/castopod.org)
|
||||
- [LinkedIn](https://linkedin.com/company/castopod)
|
||||
- [Facebook](https://www.facebook.com/castopod)
|
||||
|
||||
|
|
@ -150,10 +155,10 @@ backers. If you'd like to help, please consider
|
|||
<tbody>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://docs.castopod.org/images/sponsors/adaures.svg" target="_blank" rel="noopener noreferrer"><img height="48" src="https://docs.castopod.org/images/sponsors/adaures.svg" alt="Netlify" /></a>
|
||||
<a href="https://docs.castopod.org/images/sponsors/adaures.svg" target="_blank" rel="noopener noreferrer"><img height="48" src="./docs/src/assets/images/sponsors/adaures.svg" alt="Ad Aures" /></a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://nlnet.nl/project/Castopod/" target="_blank" rel="noopener noreferrer"><img src="https://docs.castopod.org/images/sponsors/nlnet.svg" alt="NLnet Logo" height="48" /></a>
|
||||
<a href="https://nlnet.nl/project/Castopod/" target="_blank" rel="noopener noreferrer"><img src="./docs/src/assets/images/sponsors/nlnet.svg" alt="NLnet Logo" height="48" /></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,2 @@
|
|||
<IfModule authz_core_module>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
<IfModule !authz_core_module>
|
||||
Deny from all
|
||||
</IfModule>
|
||||
<IfModule authz_core_module> Require all denied </IfModule>
|
||||
<IfModule !authz_core_module> Deny from all </IfModule>
|
||||
|
|
|
|||
50
app/Commands/EpisodesComputeDownloads.php
Normal file
50
app/Commands/EpisodesComputeDownloads.php
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Commands;
|
||||
|
||||
use App\Models\EpisodeModel;
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
|
||||
class EpisodesComputeDownloads extends BaseCommand
|
||||
{
|
||||
/**
|
||||
* The Command's Group
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group = 'Episodes';
|
||||
|
||||
/**
|
||||
* The Command's Name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'episodes:compute-downloads';
|
||||
|
||||
/**
|
||||
* The Command's Description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = "Calculates all episodes downloads and stores results in episodes' downloads_count field.";
|
||||
|
||||
/**
|
||||
* Actually execute a command.
|
||||
*/
|
||||
public function run(array $params): void
|
||||
{
|
||||
$episodesModel = new EpisodeModel();
|
||||
$query = $episodesModel->builder()
|
||||
->select('episodes.id as id, IFNULL(SUM(ape.hits),0) as downloads_count')
|
||||
->join('analytics_podcasts_by_episode ape', 'episodes.id=ape.episode_id', 'left')
|
||||
->groupBy('episodes.id');
|
||||
|
||||
$episodeModel2 = new EpisodeModel();
|
||||
$episodeModel2->builder()
|
||||
->setQueryAsData($query)
|
||||
->onConstraint('id')
|
||||
->updateBatch();
|
||||
}
|
||||
}
|
||||
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Config\Services;
|
||||
use Config\View;
|
||||
use ViewThemes\Theme;
|
||||
|
||||
/**
|
||||
|
|
@ -14,7 +12,7 @@ use ViewThemes\Theme;
|
|||
* This can be looked at as a `master helper` file that is loaded early on, and may also contain additional functions
|
||||
* that you'd like to use throughout your entire application
|
||||
*
|
||||
* @link: https://codeigniter4.github.io/CodeIgniter4/
|
||||
* @see: https://codeigniter.com/user_guide/extending/common.html
|
||||
*/
|
||||
|
||||
if (! function_exists('view')) {
|
||||
|
|
@ -29,12 +27,17 @@ if (! function_exists('view')) {
|
|||
*/
|
||||
function view(string $name, array $data = [], array $options = []): string
|
||||
{
|
||||
if (array_key_exists('theme', $options)) {
|
||||
Theme::setTheme($options['theme']);
|
||||
}
|
||||
|
||||
$path = Theme::path();
|
||||
|
||||
/** @var CodeIgniter\View\View $renderer */
|
||||
$renderer = single_service('renderer', $path);
|
||||
|
||||
$saveData = config(View::class)->saveData;
|
||||
$saveData = config('View')
|
||||
->saveData;
|
||||
|
||||
if (array_key_exists('saveData', $options)) {
|
||||
$saveData = (bool) $options['saveData'];
|
||||
|
|
@ -45,40 +48,3 @@ if (! function_exists('view')) {
|
|||
->render($name, $options, $saveData);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('lang')) {
|
||||
/**
|
||||
* A convenience method to translate a string or array of them and format the result with the intl extension's
|
||||
* MessageFormatter.
|
||||
*
|
||||
* Overwritten to include an escape parameter (escaped by default).
|
||||
*
|
||||
* @param array<int|string, string> $args
|
||||
*
|
||||
* @return string|string[]
|
||||
*/
|
||||
function lang(string $line, array $args = [], ?string $locale = null, bool $escape = true): string | array
|
||||
{
|
||||
$language = Services::language();
|
||||
|
||||
// Get active locale
|
||||
$activeLocale = $language->getLocale();
|
||||
|
||||
if ($locale && $locale !== $activeLocale) {
|
||||
$language->setLocale($locale);
|
||||
}
|
||||
|
||||
$line = $language->getLine($line, $args);
|
||||
if (! $locale) {
|
||||
return $escape ? esc($line) : $line;
|
||||
}
|
||||
|
||||
if ($locale === $activeLocale) {
|
||||
return $escape ? esc($line) : $line;
|
||||
}
|
||||
|
||||
// Reset to active locale
|
||||
$language->setLocale($activeLocale);
|
||||
return $escape ? esc($line) : $line;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||
namespace Config;
|
||||
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
use CodeIgniter\Session\Handlers\FileHandler;
|
||||
use Override;
|
||||
|
||||
class App extends BaseConfig
|
||||
{
|
||||
|
|
@ -14,38 +14,34 @@ class App extends BaseConfig
|
|||
* Base Site URL
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* URL to your CodeIgniter root. Typically this will be your base URL,
|
||||
* URL to your CodeIgniter root. Typically, this will be your base URL,
|
||||
* WITH a trailing slash:
|
||||
*
|
||||
* http://example.com/
|
||||
*
|
||||
* If this is not set then CodeIgniter will try guess the protocol, domain
|
||||
* and path to your installation. However, you should always configure this
|
||||
* explicitly and never rely on auto-guessing, especially in production
|
||||
* environments.
|
||||
* E.g., http://example.com/
|
||||
*/
|
||||
public string $baseURL = 'http://localhost:8080/';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Media Base URL
|
||||
* --------------------------------------------------------------------------
|
||||
* Allowed Hostnames in the Site URL other than the hostname in the baseURL.
|
||||
* If you want to accept multiple Hostnames, set this.
|
||||
*
|
||||
* URL to your media root. Typically this will be your base URL,
|
||||
* WITH a trailing slash:
|
||||
* E.g.,
|
||||
* When your site URL ($baseURL) is 'http://example.com/', and your site
|
||||
* also accepts 'http://media.example.com/' and 'http://accounts.example.com/':
|
||||
* ['media.example.com', 'accounts.example.com']
|
||||
*
|
||||
* http://cdn.example.com/
|
||||
* @var list<string>
|
||||
*/
|
||||
public string $mediaBaseURL = 'http://localhost:8080/';
|
||||
public array $allowedHostnames = [];
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Index File
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Typically this will be your index.php file, unless you've renamed it to
|
||||
* something else. If you are using mod_rewrite to remove the page set this
|
||||
* variable so that it is blank.
|
||||
* Typically, this will be your `index.php` file, unless you've renamed it to
|
||||
* something else. If you have configured your web server to remove this file
|
||||
* from your site URIs, set this variable to an empty string.
|
||||
*/
|
||||
public string $indexPage = '';
|
||||
|
||||
|
|
@ -55,17 +51,41 @@ class App extends BaseConfig
|
|||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* This item determines which server global should be used to retrieve the
|
||||
* URI string. The default setting of 'REQUEST_URI' works for most servers.
|
||||
* URI string. The default setting of 'REQUEST_URI' works for most servers.
|
||||
* If your links do not seem to work, try one of the other delicious flavors:
|
||||
*
|
||||
* 'REQUEST_URI' Uses $_SERVER['REQUEST_URI']
|
||||
* 'QUERY_STRING' Uses $_SERVER['QUERY_STRING']
|
||||
* 'PATH_INFO' Uses $_SERVER['PATH_INFO']
|
||||
* 'REQUEST_URI': Uses $_SERVER['REQUEST_URI']
|
||||
* 'QUERY_STRING': Uses $_SERVER['QUERY_STRING']
|
||||
* 'PATH_INFO': Uses $_SERVER['PATH_INFO']
|
||||
*
|
||||
* WARNING: If you set this to 'PATH_INFO', URIs will always be URL-decoded!
|
||||
*/
|
||||
public string $uriProtocol = 'REQUEST_URI';
|
||||
|
||||
/*
|
||||
*--------------------------------------------------------------------------
|
||||
* Allowed URL Characters
|
||||
*--------------------------------------------------------------------------
|
||||
*
|
||||
* This lets you specify which characters are permitted within your URLs.
|
||||
* When someone tries to submit a URL with disallowed characters they will
|
||||
* get a warning message.
|
||||
*
|
||||
* As a security measure you are STRONGLY encouraged to restrict URLs to
|
||||
* as few characters as possible.
|
||||
*
|
||||
* By default, only these are allowed: `a-z 0-9~%.:_-`
|
||||
*
|
||||
* Set an empty string to allow all characters -- but only if you are insane.
|
||||
*
|
||||
* The configured value is actually a regular expression character group
|
||||
* and it will be used as: '/\A[<permittedURIChars>]+\z/iu'
|
||||
*
|
||||
* DO NOT CHANGE THIS UNLESS YOU FULLY UNDERSTAND THE REPERCUSSIONS!!
|
||||
*
|
||||
*/
|
||||
public string $permittedURIChars = 'a-z 0-9~%.:_\-@';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Default Locale
|
||||
|
|
@ -99,9 +119,23 @@ class App extends BaseConfig
|
|||
* by the application in descending order of priority. If no match is
|
||||
* found, the first locale will be used.
|
||||
*
|
||||
* @var string[]
|
||||
* IncomingRequest::setLocale() also uses this list.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
public array $supportedLocales = ['en', 'fr', 'pl', 'de', 'pt-BR', 'nn-NO', 'es', 'zh-Hans', 'ca'];
|
||||
public array $supportedLocales = [
|
||||
'en',
|
||||
'fr',
|
||||
'pl',
|
||||
'de',
|
||||
'pt-br',
|
||||
'nn-no',
|
||||
'es',
|
||||
'zh-hans',
|
||||
'ca',
|
||||
'br',
|
||||
'sr-latn',
|
||||
];
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
|
|
@ -110,6 +144,9 @@ class App extends BaseConfig
|
|||
*
|
||||
* The default timezone that will be used in your application to display
|
||||
* dates with the date helper, and can be retrieved through app_timezone()
|
||||
*
|
||||
* @see https://www.php.net/manual/en/timezones.php for list of timezones
|
||||
* supported by PHP.
|
||||
*/
|
||||
public string $appTimezone = 'UTC';
|
||||
|
||||
|
|
@ -133,170 +170,10 @@ class App extends BaseConfig
|
|||
* If true, this will force every request made to this application to be
|
||||
* made via a secure connection (HTTPS). If the incoming request is not
|
||||
* secure, the user will be redirected to a secure version of the page
|
||||
* and the HTTP Strict Transport Security header will be set.
|
||||
* and the HTTP Strict Transport Security (HSTS) header will be set.
|
||||
*/
|
||||
public bool $forceGlobalSecureRequests = true;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Session Driver
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* The session storage driver to use:
|
||||
* - `CodeIgniter\Session\Handlers\FileHandler`
|
||||
* - `CodeIgniter\Session\Handlers\DatabaseHandler`
|
||||
* - `CodeIgniter\Session\Handlers\MemcachedHandler`
|
||||
* - `CodeIgniter\Session\Handlers\RedisHandler`
|
||||
*/
|
||||
public string $sessionDriver = FileHandler::class;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Session Cookie Name
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* The session cookie name, must contain only [0-9a-z_-] characters
|
||||
*/
|
||||
public string $sessionCookieName = 'ci_session';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Session Expiration
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* The number of SECONDS you want the session to last.
|
||||
* Setting to 0 (zero) means expire when the browser is closed.
|
||||
*/
|
||||
public int $sessionExpiration = 7200;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Session Save Path
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* The location to save sessions to and is driver dependent.
|
||||
*
|
||||
* For the 'files' driver, it's a path to a writable directory.
|
||||
* WARNING: Only absolute paths are supported!
|
||||
*
|
||||
* For the 'database' driver, it's a table name.
|
||||
* Please read up the manual for the format with other session drivers.
|
||||
*
|
||||
* IMPORTANT: You are REQUIRED to set a valid save path!
|
||||
*/
|
||||
public string $sessionSavePath = WRITEPATH . 'session';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Session Match IP
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Whether to match the user's IP address when reading the session data.
|
||||
*
|
||||
* WARNING: If you're using the database driver, don't forget to update
|
||||
* your session table's PRIMARY KEY when changing this setting.
|
||||
*/
|
||||
public bool $sessionMatchIP = false;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Session Time to Update
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* How many seconds between CI regenerating the session ID.
|
||||
*/
|
||||
public int $sessionTimeToUpdate = 300;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Session Regenerate Destroy
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Whether to destroy session data associated with the old session ID
|
||||
* when auto-regenerating the session ID. When set to FALSE, the data
|
||||
* will be later deleted by the garbage collector.
|
||||
*/
|
||||
public bool $sessionRegenerateDestroy = false;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Cookie Prefix
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Set a cookie name prefix if you need to avoid collisions.
|
||||
*
|
||||
* @deprecated use Config\Cookie::$prefix property instead.
|
||||
*/
|
||||
public string $cookiePrefix = '';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Cookie Domain
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Set to `.your-domain.com` for site-wide cookies.
|
||||
*
|
||||
* @deprecated use Config\Cookie::$domain property instead.
|
||||
*/
|
||||
public string $cookieDomain = '';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Cookie Path
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Typically will be a forward slash.
|
||||
*
|
||||
* @deprecated use Config\Cookie::$path property instead.
|
||||
*/
|
||||
public string $cookiePath = '/';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Cookie Secure
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Cookie will only be set if a secure HTTPS connection exists.
|
||||
*
|
||||
* @deprecated use Config\Cookie::$secure property instead.
|
||||
*/
|
||||
public bool $cookieSecure = false;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Cookie HttpOnly
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Cookie will only be accessible via HTTP(S) (no JavaScript).
|
||||
*
|
||||
* @deprecated use Config\Cookie::$httponly property instead.
|
||||
*/
|
||||
public bool $cookieHTTPOnly = true;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Cookie SameSite
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Configure cookie SameSite setting. Allowed values are:
|
||||
* - None
|
||||
* - Lax
|
||||
* - Strict
|
||||
* - ''
|
||||
*
|
||||
* Alternatively, you can use the constant names:
|
||||
* - `Cookie::SAMESITE_NONE`
|
||||
* - `Cookie::SAMESITE_LAX`
|
||||
* - `Cookie::SAMESITE_STRICT`
|
||||
*
|
||||
* Defaults to `Lax` for compatibility with modern browsers. Setting `''`
|
||||
* (empty string) means default SameSite attribute set by browsers (`Lax`)
|
||||
* will be set on cookies. If set to `None`, `$cookieSecure` must also be set.
|
||||
*
|
||||
* @deprecated `Config\Cookie` $samesite property is used.
|
||||
*/
|
||||
public string $cookieSameSite = 'Lax';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Reverse Proxy IPs
|
||||
|
|
@ -304,103 +181,21 @@ class App extends BaseConfig
|
|||
*
|
||||
* If your server is behind a reverse proxy, you must whitelist the proxy
|
||||
* IP addresses from which CodeIgniter should trust headers such as
|
||||
* HTTP_X_FORWARDED_FOR and HTTP_CLIENT_IP in order to properly identify
|
||||
* X-Forwarded-For or Client-IP in order to properly identify
|
||||
* the visitor's IP address.
|
||||
*
|
||||
* You can use both an array or a comma-separated list of proxy addresses,
|
||||
* as well as specifying whole subnets. Here are a few examples:
|
||||
* You need to set a proxy IP address or IP address with subnets and
|
||||
* the HTTP header for the client IP address.
|
||||
*
|
||||
* Comma-separated: '10.0.1.200,192.168.5.0/24'
|
||||
* Array: ['10.0.1.200', '192.168.5.0/24']
|
||||
* Here are some examples:
|
||||
* [
|
||||
* '10.0.1.200' => 'X-Forwarded-For',
|
||||
* '192.168.5.0/24' => 'X-Real-IP',
|
||||
* ]
|
||||
*
|
||||
* @var string|string[]
|
||||
* @var array<string, string>|string
|
||||
*/
|
||||
public string | array $proxyIPs = '';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* CSRF Token Name
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* The token name.
|
||||
*
|
||||
* @deprecated Use `Config\Security` $tokenName property instead of using this property.
|
||||
*/
|
||||
public string $CSRFTokenName = 'csrf_test_name';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* CSRF Header Name
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* The header name.
|
||||
*
|
||||
* @deprecated Use `Config\Security` $headerName property instead of using this property.
|
||||
*/
|
||||
public string $CSRFHeaderName = 'X-CSRF-TOKEN';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* CSRF Cookie Name
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* The cookie name.
|
||||
*
|
||||
* @deprecated Use `Config\Security` $cookieName property instead of using this property.
|
||||
*/
|
||||
public string $CSRFCookieName = 'csrf_cookie_name';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* CSRF Expire
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* The number in seconds the token should expire.
|
||||
*
|
||||
* @deprecated Use `Config\Security` $expire property instead of using this property.
|
||||
*/
|
||||
public int $CSRFExpire = 7200;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* CSRF Regenerate
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Regenerate token on every submission?
|
||||
*
|
||||
* @deprecated Use `Config\Security` $regenerate property instead of using this property.
|
||||
*/
|
||||
public bool $CSRFRegenerate = true;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* CSRF Redirect
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Redirect to previous page with error on failure?
|
||||
*
|
||||
* @deprecated Use `Config\Security` $redirect property instead of using this property.
|
||||
*/
|
||||
public bool $CSRFRedirect = true;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* CSRF SameSite
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Setting for CSRF SameSite cookie token. Allowed values are:
|
||||
* - None
|
||||
* - Lax
|
||||
* - Strict
|
||||
* - ''
|
||||
*
|
||||
* Defaults to `Lax` as recommended in this link:
|
||||
*
|
||||
* @see https://portswigger.net/web-security/csrf/samesite-cookies
|
||||
*
|
||||
* @deprecated Use `Config\Security` $samesite property instead of using this property.
|
||||
*/
|
||||
public string $CSRFSameSite = 'Lax';
|
||||
public $proxyIPs = [];
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
|
|
@ -420,14 +215,6 @@ class App extends BaseConfig
|
|||
*/
|
||||
public bool $CSPEnabled = false;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Media root folder
|
||||
* --------------------------------------------------------------------------
|
||||
* Defines the root folder for media files storage
|
||||
*/
|
||||
public string $mediaRoot = 'media';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Instance / Site Config
|
||||
|
|
@ -444,7 +231,7 @@ class App extends BaseConfig
|
|||
*/
|
||||
public array $siteIcon = [
|
||||
'ico' => '/favicon.ico',
|
||||
'64' => '/icon-64.png',
|
||||
'64' => '/icon-64.png',
|
||||
'180' => '/icon-180.png',
|
||||
'192' => '/icon-192.png',
|
||||
'512' => '/icon-512.png',
|
||||
|
|
@ -457,5 +244,43 @@ class App extends BaseConfig
|
|||
*/
|
||||
public ?int $storageLimit = null;
|
||||
|
||||
/**
|
||||
* Bandwidth limit (per month) in Gigabytes
|
||||
*/
|
||||
public ?int $bandwidthLimit = null;
|
||||
|
||||
public ?string $legalNoticeURL = null;
|
||||
|
||||
/**
|
||||
* AuthToken Config Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
if (is_string($this->proxyIPs)) {
|
||||
$array = json_decode($this->proxyIPs, true);
|
||||
if (is_array($array)) {
|
||||
$this->proxyIPs = $array;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent initEnvValue() to allow for direct setting to array properties values from ENV
|
||||
*
|
||||
* In order to set array properties via ENV vars we need to set the property to a string value first.
|
||||
*
|
||||
* @param mixed $property
|
||||
*/
|
||||
#[Override]
|
||||
protected function initEnvValue(&$property, string $name, string $prefix, string $shortPrefix): void
|
||||
{
|
||||
// if attempting to set property from ENV, first set to empty string
|
||||
if ($name === 'proxyIPs' && $this->getEnvValue($name, $prefix, $shortPrefix) !== null) {
|
||||
$property = '';
|
||||
}
|
||||
|
||||
parent::initEnvValue($property, $name, $prefix, $shortPrefix);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,42 +27,39 @@ class Autoload extends AutoloadConfig
|
|||
* their location on the file system. These are used by the autoloader
|
||||
* to locate files the first time they have been instantiated.
|
||||
*
|
||||
* The '/app' and '/system' directories are already mapped for you.
|
||||
* you may change the name of the 'App' namespace if you wish,
|
||||
* The 'Config' (APPPATH . 'Config') and 'CodeIgniter' (SYSTEMPATH) are
|
||||
* already mapped for you.
|
||||
*
|
||||
* You may change the name of the 'App' namespace if you wish,
|
||||
* but this should be done prior to creating any namespaced classes,
|
||||
* else you will need to modify all of those classes for this to work.
|
||||
*
|
||||
* Prototype:
|
||||
*
|
||||
* $psr4 = [
|
||||
* 'CodeIgniter' => SYSTEMPATH,
|
||||
* 'App' => APPPATH
|
||||
* ];
|
||||
*
|
||||
* @var array<string, string>
|
||||
* @var array<string, list<string>|string>
|
||||
*/
|
||||
public $psr4 = [
|
||||
APP_NAMESPACE => APPPATH,
|
||||
'Modules' => ROOTPATH . 'modules/',
|
||||
'Modules\Admin' => ROOTPATH . 'modules/Admin/',
|
||||
'Modules\Auth' => ROOTPATH . 'modules/Auth/',
|
||||
'Modules\Analytics' => ROOTPATH . 'modules/Analytics/',
|
||||
'Modules\Install' => ROOTPATH . 'modules/Install/',
|
||||
'Modules\Fediverse' => ROOTPATH . 'modules/Fediverse/',
|
||||
'Modules\WebSub' => ROOTPATH . 'modules/WebSub/',
|
||||
'Modules\Api\Rest\V1' => ROOTPATH . 'modules/Api/Rest/V1',
|
||||
APP_NAMESPACE => APPPATH,
|
||||
'Modules' => ROOTPATH . 'modules/',
|
||||
'Modules\Admin' => ROOTPATH . 'modules/Admin/',
|
||||
'Modules\Analytics' => ROOTPATH . 'modules/Analytics/',
|
||||
'Modules\Api\Rest\V1' => ROOTPATH . 'modules/Api/Rest/V1',
|
||||
'Modules\Auth' => ROOTPATH . 'modules/Auth/',
|
||||
'Modules\Fediverse' => ROOTPATH . 'modules/Fediverse/',
|
||||
'Modules\Install' => ROOTPATH . 'modules/Install/',
|
||||
'Modules\Media' => ROOTPATH . 'modules/Media/',
|
||||
'Modules\MediaClipper' => ROOTPATH . 'modules/MediaClipper/',
|
||||
'Modules\Platforms' => ROOTPATH . 'modules/Platforms/',
|
||||
'Modules\Plugins' => ROOTPATH . 'modules/Plugins/',
|
||||
'Modules\PodcastImport' => ROOTPATH . 'modules/PodcastImport/',
|
||||
'Modules\PremiumPodcasts' => ROOTPATH . 'modules/PremiumPodcasts/',
|
||||
'Config' => APPPATH . 'Config/',
|
||||
'ViewComponents' => APPPATH . 'Libraries/ViewComponents/',
|
||||
'ViewThemes' => APPPATH . 'Libraries/ViewThemes/',
|
||||
'MediaClipper' => APPPATH . 'Libraries/MediaClipper/',
|
||||
'Vite' => APPPATH . 'Libraries/Vite/',
|
||||
'Themes' => ROOTPATH . 'themes',
|
||||
'Modules\Update' => ROOTPATH . 'modules/Update/',
|
||||
'Modules\WebSub' => ROOTPATH . 'modules/WebSub/',
|
||||
'Themes' => ROOTPATH . 'themes',
|
||||
'ViewComponents' => APPPATH . 'Libraries/ViewComponents/',
|
||||
'ViewThemes' => APPPATH . 'Libraries/ViewThemes/',
|
||||
];
|
||||
|
||||
/**
|
||||
* -------------------------------------------------------------------
|
||||
* Class Map
|
||||
* -------------------------------------------------------------------
|
||||
* The class map provides a map of class names and their exact
|
||||
* location on the drive. Classes loaded in this manner will have
|
||||
|
|
@ -89,12 +86,25 @@ class Autoload extends AutoloadConfig
|
|||
* or for loading functions.
|
||||
*
|
||||
* Prototype:
|
||||
* ```
|
||||
*
|
||||
* $files = [
|
||||
* '/path/to/my/file.php',
|
||||
* ];
|
||||
* ```
|
||||
* @var array<int, string>
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
public $files = [APPPATH . 'Libraries/ViewComponents/Helpers/view_components_helper.php'];
|
||||
public $files = [];
|
||||
|
||||
/**
|
||||
* -------------------------------------------------------------------
|
||||
* Helpers
|
||||
* -------------------------------------------------------------------
|
||||
* Prototype:
|
||||
* $helpers = [
|
||||
* 'form',
|
||||
* ];
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
public $helpers = ['auth', 'setting', 'plugins'];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,10 @@ declare(strict_types=1);
|
|||
* In development, we want to show as many errors as possible to help
|
||||
* make sure they don't make it to production. And save us hours of
|
||||
* painful debugging.
|
||||
*
|
||||
* If you set 'display_errors' to '1', CI4's detailed error report will show.
|
||||
*/
|
||||
error_reporting(-1);
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '1');
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -8,9 +8,13 @@ declare(strict_types=1);
|
|||
* --------------------------------------------------------------------------
|
||||
* Don't show ANY in production environments. Instead, let the system catch
|
||||
* it and display a generic error message.
|
||||
*
|
||||
* If you set 'display_errors' to '1', CI4's detailed error report will show.
|
||||
*/
|
||||
error_reporting(E_ALL & ~E_DEPRECATED);
|
||||
// If you want to suppress more types of errors.
|
||||
// error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT & ~E_USER_NOTICE & ~E_USER_DEPRECATED);
|
||||
ini_set('display_errors', '0');
|
||||
error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT & ~E_USER_NOTICE & ~E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* The environment testing is reserved for PHPUnit testing. It has special
|
||||
* conditions built into the framework at various places to assist with that.
|
||||
* You can’t use it for your development.
|
||||
*/
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* ERROR DISPLAY
|
||||
|
|
@ -10,7 +16,7 @@ declare(strict_types=1);
|
|||
* make sure they don't make it to production. And save us hours of
|
||||
* painful debugging.
|
||||
*/
|
||||
error_reporting(-1);
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '1');
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -8,6 +8,19 @@ use CodeIgniter\Config\BaseConfig;
|
|||
|
||||
class CURLRequest extends BaseConfig
|
||||
{
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* CURLRequest Share Connection Options
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Share connection options between requests.
|
||||
*
|
||||
* @var list<int>
|
||||
*
|
||||
* @see https://www.php.net/manual/en/curl.constants.php#constant.curl-lock-data-connect
|
||||
*/
|
||||
public array $shareConnectionOptions = [CURL_LOCK_DATA_CONNECT, CURL_LOCK_DATA_DNS];
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* CURLRequest Share Options
|
||||
|
|
@ -18,5 +31,5 @@ class CURLRequest extends BaseConfig
|
|||
* If true, all the options won't be reset between requests.
|
||||
* It may cause an error request with unnecessary headers.
|
||||
*/
|
||||
public bool $shareOptions = true;
|
||||
public bool $shareOptions = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace Config;
|
||||
|
||||
use CodeIgniter\Cache\CacheInterface;
|
||||
use CodeIgniter\Cache\Handlers\ApcuHandler;
|
||||
use CodeIgniter\Cache\Handlers\DummyHandler;
|
||||
use CodeIgniter\Cache\Handlers\FileHandler;
|
||||
use CodeIgniter\Cache\Handlers\MemcachedHandler;
|
||||
|
|
@ -35,37 +37,6 @@ class Cache extends BaseConfig
|
|||
*/
|
||||
public string $backupHandler = 'dummy';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Cache Directory Path
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* The path to where cache files should be stored, if using a file-based
|
||||
* system.
|
||||
*
|
||||
* @deprecated Use the driver-specific variant under $file
|
||||
*/
|
||||
public string $storePath = WRITEPATH . 'cache/';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Cache Include Query String
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Whether to take the URL query string into consideration when generating
|
||||
* output cache files. Valid options are:
|
||||
*
|
||||
* false = Disabled
|
||||
* true = Enabled, take all query parameters into account.
|
||||
* Please be aware that this may result in numerous cache
|
||||
* files generated for the same page over and over again.
|
||||
* array('q') = Enabled, but only take into account the specified list
|
||||
* of query parameters.
|
||||
*
|
||||
* @var boolean|string[]
|
||||
*/
|
||||
public bool | array $cacheQueryString = false;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Key Prefix
|
||||
|
|
@ -97,6 +68,7 @@ class Cache extends BaseConfig
|
|||
* A string of reserved characters that will not be allowed in keys or tags.
|
||||
* Strings that violate this restriction will cause handlers to throw.
|
||||
* Default: {}()/\@:
|
||||
*
|
||||
* Note: The default set is required for PSR-6 compliance.
|
||||
*/
|
||||
public string $reservedCharacters = '{}()/\@:';
|
||||
|
|
@ -105,32 +77,34 @@ class Cache extends BaseConfig
|
|||
* --------------------------------------------------------------------------
|
||||
* File settings
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Your file storage preferences can be specified below, if you are using
|
||||
* the File driver.
|
||||
*
|
||||
* @var array<string, string|int|null>
|
||||
* @var array{storePath?: string, mode?: int}
|
||||
*/
|
||||
public array $file = [
|
||||
'storePath' => WRITEPATH . 'cache/',
|
||||
'mode' => 0640,
|
||||
'mode' => 0640,
|
||||
];
|
||||
|
||||
/**
|
||||
* -------------------------------------------------------------------------
|
||||
* Memcached settings
|
||||
* -------------------------------------------------------------------------
|
||||
*
|
||||
* Your Memcached servers can be specified below, if you are using
|
||||
* the Memcached drivers.
|
||||
*
|
||||
* @see https://codeigniter.com/user_guide/libraries/caching.html#memcached
|
||||
*
|
||||
* @var array<string, string|int|boolean>
|
||||
* @var array{host?: string, port?: int, weight?: int, raw?: bool}
|
||||
*/
|
||||
public array $memcached = [
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 11211,
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 11211,
|
||||
'weight' => 1,
|
||||
'raw' => false,
|
||||
'raw' => false,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -140,14 +114,24 @@ class Cache extends BaseConfig
|
|||
* Your Redis server can be specified below, if you are using
|
||||
* the Redis or Predis drivers.
|
||||
*
|
||||
* @var array<string, string|int|null>
|
||||
* @var array{
|
||||
* host?: string,
|
||||
* password?: string|null,
|
||||
* port?: int,
|
||||
* timeout?: int,
|
||||
* async?: bool,
|
||||
* persistent?: bool,
|
||||
* database?: int
|
||||
* }
|
||||
*/
|
||||
public array $redis = [
|
||||
'host' => '127.0.0.1',
|
||||
'password' => null,
|
||||
'port' => 6379,
|
||||
'timeout' => 0,
|
||||
'database' => 0,
|
||||
'host' => '127.0.0.1',
|
||||
'password' => null,
|
||||
'port' => 6379,
|
||||
'timeout' => 0,
|
||||
'async' => false, // specific to Predis and ignored by the native Redis extension
|
||||
'persistent' => false,
|
||||
'database' => 0,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -158,14 +142,58 @@ class Cache extends BaseConfig
|
|||
* This is an array of cache engine alias' and class names. Only engines
|
||||
* that are listed here are allowed to be used.
|
||||
*
|
||||
* @var array<string, string>
|
||||
* @var array<string, class-string<CacheInterface>>
|
||||
*/
|
||||
public array $validHandlers = [
|
||||
'dummy' => DummyHandler::class,
|
||||
'file' => FileHandler::class,
|
||||
'apcu' => ApcuHandler::class,
|
||||
'dummy' => DummyHandler::class,
|
||||
'file' => FileHandler::class,
|
||||
'memcached' => MemcachedHandler::class,
|
||||
'predis' => PredisHandler::class,
|
||||
'redis' => RedisHandler::class,
|
||||
'wincache' => WincacheHandler::class,
|
||||
'predis' => PredisHandler::class,
|
||||
'redis' => RedisHandler::class,
|
||||
'wincache' => WincacheHandler::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Web Page Caching: Cache Include Query String
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Whether to take the URL query string into consideration when generating
|
||||
* output cache files. Valid options are:
|
||||
*
|
||||
* false = Disabled
|
||||
* true = Enabled, take all query parameters into account.
|
||||
* Please be aware that this may result in numerous cache
|
||||
* files generated for the same page over and over again.
|
||||
* ['q'] = Enabled, but only take into account the specified list
|
||||
* of query parameters.
|
||||
*
|
||||
* @var bool|list<string>
|
||||
*/
|
||||
public $cacheQueryString = false;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Web Page Caching: Cache Status Codes
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* HTTP status codes that are allowed to be cached. Only responses with
|
||||
* these status codes will be cached by the PageCache filter.
|
||||
*
|
||||
* Default: [] - Cache all status codes (backward compatible)
|
||||
*
|
||||
* Recommended: [200] - Only cache successful responses
|
||||
*
|
||||
* You can also use status codes like:
|
||||
* [200, 404, 410] - Cache successful responses and specific error codes
|
||||
* [200, 201, 202, 203, 204] - All 2xx successful responses
|
||||
*
|
||||
* WARNING: Using [] may cache temporary error pages (404, 500, etc).
|
||||
* Consider restricting to [200] for production applications to avoid
|
||||
* caching errors that should be temporary.
|
||||
*
|
||||
* @var list<int>
|
||||
*/
|
||||
public array $cacheStatusCodes = [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,136 +14,136 @@ class Colors extends BaseConfig
|
|||
public array $themes = [
|
||||
/* Castopod's brand color */
|
||||
'pine' => [
|
||||
'accent-base' => [174, 100, 29],
|
||||
'accent-hover' => [172, 100, 17],
|
||||
'accent-muted' => [131, 100, 12],
|
||||
'accent-base' => [174, 100, 29],
|
||||
'accent-hover' => [172, 100, 17],
|
||||
'accent-muted' => [131, 100, 12],
|
||||
'accent-contrast' => [0, 0, 100],
|
||||
|
||||
'heading-foreground' => [172, 100, 17],
|
||||
'heading-background' => [111, 64, 94],
|
||||
|
||||
'background-elevated' => [0, 0, 100],
|
||||
'background-base' => [173, 44, 96],
|
||||
'background-elevated' => [0, 0, 100],
|
||||
'background-base' => [173, 44, 96],
|
||||
'background-navigation' => [172, 100, 17],
|
||||
'background-header' => [172, 100, 17],
|
||||
'background-highlight' => [111, 64, 94],
|
||||
'background-backdrop' => [0, 0, 50],
|
||||
'background-header' => [172, 100, 17],
|
||||
'background-highlight' => [111, 64, 94],
|
||||
'background-backdrop' => [0, 0, 50],
|
||||
|
||||
'border-subtle' => [111, 42, 86],
|
||||
'border-contrast' => [0, 0, 0],
|
||||
'border-subtle' => [111, 42, 86],
|
||||
'border-contrast' => [0, 0, 0],
|
||||
'border-navigation' => [131, 100, 12],
|
||||
|
||||
'text-base' => [158, 8, 3],
|
||||
'text-base' => [158, 8, 3],
|
||||
'text-muted' => [172, 8, 38],
|
||||
],
|
||||
/* Red / Rose color */
|
||||
'crimson' => [
|
||||
'accent-base' => [350, 87, 61],
|
||||
'accent-hover' => [348, 75, 40],
|
||||
'accent-muted' => [348, 73, 32],
|
||||
'accent-base' => [350, 87, 61],
|
||||
'accent-hover' => [348, 75, 40],
|
||||
'accent-muted' => [348, 73, 32],
|
||||
'accent-contrast' => [0, 0, 100],
|
||||
|
||||
'heading-foreground' => [348, 73, 32],
|
||||
'heading-background' => [344, 79, 96],
|
||||
|
||||
'background-elevated' => [0, 0, 100],
|
||||
'background-base' => [350, 44, 96],
|
||||
'background-header' => [348, 75, 40],
|
||||
'background-elevated' => [0, 0, 100],
|
||||
'background-base' => [350, 44, 96],
|
||||
'background-header' => [348, 75, 40],
|
||||
'background-highlight' => [344, 79, 96],
|
||||
'background-backdrop' => [0, 0, 50],
|
||||
'background-backdrop' => [0, 0, 50],
|
||||
|
||||
'border-subtle' => [348, 42, 86],
|
||||
'border-subtle' => [348, 42, 86],
|
||||
'border-contrast' => [0, 0, 0],
|
||||
|
||||
'text-base' => [340, 8, 3],
|
||||
'text-base' => [340, 8, 3],
|
||||
'text-muted' => [345, 8, 38],
|
||||
],
|
||||
/* Blue color */
|
||||
'lake' => [
|
||||
'accent-base' => [194, 100, 44],
|
||||
'accent-hover' => [194, 100, 22],
|
||||
'accent-muted' => [195, 100, 11],
|
||||
'accent-base' => [194, 100, 44],
|
||||
'accent-hover' => [194, 100, 22],
|
||||
'accent-muted' => [195, 100, 11],
|
||||
'accent-contrast' => [0, 0, 100],
|
||||
|
||||
'heading-foreground' => [194, 100, 22],
|
||||
'heading-background' => [195, 100, 92],
|
||||
|
||||
'background-elevated' => [0, 0, 100],
|
||||
'background-base' => [196, 44, 96],
|
||||
'background-header' => [194, 100, 22],
|
||||
'background-elevated' => [0, 0, 100],
|
||||
'background-base' => [196, 44, 96],
|
||||
'background-header' => [194, 100, 22],
|
||||
'background-highlight' => [195, 100, 92],
|
||||
'background-backdrop' => [0, 0, 50],
|
||||
'background-backdrop' => [0, 0, 50],
|
||||
|
||||
'border-subtle' => [195, 42, 86],
|
||||
'border-subtle' => [195, 42, 86],
|
||||
'border-contrast' => [0, 0, 0],
|
||||
|
||||
'text-base' => [194, 8, 3],
|
||||
'text-base' => [194, 8, 3],
|
||||
'text-muted' => [195, 8, 38],
|
||||
],
|
||||
/* Orange color */
|
||||
'amber' => [
|
||||
'accent-base' => [17, 100, 57],
|
||||
'accent-hover' => [17, 100, 35],
|
||||
'accent-muted' => [17, 100, 24],
|
||||
'accent-base' => [17, 100, 57],
|
||||
'accent-hover' => [17, 100, 35],
|
||||
'accent-muted' => [17, 100, 24],
|
||||
'accent-contrast' => [0, 0, 100],
|
||||
|
||||
'heading-foreground' => [17, 100, 35],
|
||||
'heading-background' => [17, 100, 89],
|
||||
|
||||
'background-elevated' => [0, 0, 100],
|
||||
'background-base' => [15, 44, 96],
|
||||
'background-header' => [17, 100, 35],
|
||||
'background-elevated' => [0, 0, 100],
|
||||
'background-base' => [15, 44, 96],
|
||||
'background-header' => [17, 100, 35],
|
||||
'background-highlight' => [17, 100, 89],
|
||||
'background-backdrop' => [0, 0, 50],
|
||||
'background-backdrop' => [0, 0, 50],
|
||||
|
||||
'border-subtle' => [17, 42, 86],
|
||||
'border-subtle' => [17, 42, 86],
|
||||
'border-contrast' => [0, 0, 0],
|
||||
|
||||
'text-base' => [15, 8, 3],
|
||||
'text-base' => [15, 8, 3],
|
||||
'text-muted' => [17, 8, 38],
|
||||
],
|
||||
/* Violet color */
|
||||
'jacaranda' => [
|
||||
'accent-base' => [254, 72, 52],
|
||||
'accent-hover' => [254, 73, 30],
|
||||
'accent-muted' => [254, 71, 19],
|
||||
'accent-base' => [254, 72, 52],
|
||||
'accent-hover' => [254, 73, 30],
|
||||
'accent-muted' => [254, 71, 19],
|
||||
'accent-contrast' => [0, 0, 100],
|
||||
|
||||
'heading-foreground' => [254, 73, 30],
|
||||
'heading-background' => [254, 73, 84],
|
||||
|
||||
'background-elevated' => [0, 0, 100],
|
||||
'background-base' => [253, 44, 96],
|
||||
'background-header' => [254, 73, 30],
|
||||
'background-elevated' => [0, 0, 100],
|
||||
'background-base' => [253, 44, 96],
|
||||
'background-header' => [254, 73, 30],
|
||||
'background-highlight' => [254, 88, 91],
|
||||
'background-backdrop' => [0, 0, 50],
|
||||
'background-backdrop' => [0, 0, 50],
|
||||
|
||||
'border-subtle' => [254, 42, 86],
|
||||
'border-subtle' => [254, 42, 86],
|
||||
'border-contrast' => [0, 0, 0],
|
||||
|
||||
'text-base' => [253, 8, 3],
|
||||
'text-base' => [253, 8, 3],
|
||||
'text-muted' => [254, 8, 38],
|
||||
],
|
||||
/* Black color */
|
||||
'onyx' => [
|
||||
'accent-base' => [240, 17, 2],
|
||||
'accent-hover' => [240, 17, 17],
|
||||
'accent-muted' => [240, 17, 17],
|
||||
'accent-base' => [240, 17, 2],
|
||||
'accent-hover' => [240, 17, 17],
|
||||
'accent-muted' => [240, 17, 17],
|
||||
'accent-contrast' => [0, 0, 100],
|
||||
|
||||
'heading-foreground' => [240, 17, 17],
|
||||
'heading-background' => [240, 17, 94],
|
||||
|
||||
'background-elevated' => [0, 0, 100],
|
||||
'background-base' => [240, 17, 96],
|
||||
'background-header' => [240, 12, 17],
|
||||
'background-elevated' => [0, 0, 100],
|
||||
'background-base' => [240, 17, 96],
|
||||
'background-header' => [240, 12, 17],
|
||||
'background-highlight' => [240, 17, 94],
|
||||
'background-backdrop' => [0, 0, 50],
|
||||
'background-backdrop' => [0, 0, 50],
|
||||
|
||||
'border-subtle' => [240, 17, 86],
|
||||
'border-subtle' => [240, 17, 86],
|
||||
'border-contrast' => [0, 0, 0],
|
||||
|
||||
'text-base' => [240, 8, 3],
|
||||
'text-base' => [240, 8, 3],
|
||||
'text-muted' => [240, 8, 38],
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ declare(strict_types=1);
|
|||
|
|
||||
| NOTE: this constant is updated upon release with Continuous Integration.
|
||||
*/
|
||||
defined('CP_VERSION') || define('CP_VERSION', '1.0.0-beta.24');
|
||||
defined('CP_VERSION') || define('CP_VERSION', '2.0.0-next.3');
|
||||
|
||||
/*
|
||||
| --------------------------------------------------------------------
|
||||
|
|
@ -24,10 +24,23 @@ defined('CP_VERSION') || define('CP_VERSION', '1.0.0-beta.24');
|
|||
| classes should use.
|
||||
|
|
||||
| NOTE: changing this will require manually modifying the
|
||||
| existing namespaces of App\* namespaced-classes.
|
||||
| existing namespaces of App* namespaced-classes.
|
||||
*/
|
||||
defined('APP_NAMESPACE') || define('APP_NAMESPACE', 'App');
|
||||
|
||||
/*
|
||||
| --------------------------------------------------------------------
|
||||
| Plugins Path
|
||||
| --------------------------------------------------------------------
|
||||
|
|
||||
| This defines the folder in which plugins will live.
|
||||
*/
|
||||
defined('PLUGINS_PATH') ||
|
||||
define('PLUGINS_PATH', ROOTPATH . 'plugins' . DIRECTORY_SEPARATOR);
|
||||
|
||||
defined('PLUGINS_KEY_PATTERN') ||
|
||||
define('PLUGINS_KEY_PATTERN', '[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9]([_.-]?[a-z0-9]+)*');
|
||||
|
||||
/*
|
||||
| --------------------------------------------------------------------------
|
||||
| Composer Path
|
||||
|
|
@ -91,18 +104,3 @@ defined('EXIT_USER_INPUT') || define('EXIT_USER_INPUT', 7); // invalid user inpu
|
|||
defined('EXIT_DATABASE') || define('EXIT_DATABASE', 8); // database error
|
||||
defined('EXIT__AUTO_MIN') || define('EXIT__AUTO_MIN', 9); // lowest automatically-assigned error code
|
||||
defined('EXIT__AUTO_MAX') || define('EXIT__AUTO_MAX', 125); // highest automatically-assigned error code
|
||||
|
||||
/**
|
||||
* @deprecated Use \CodeIgniter\Events\Events::PRIORITY_LOW instead.
|
||||
*/
|
||||
define('EVENT_PRIORITY_LOW', 200);
|
||||
|
||||
/**
|
||||
* @deprecated Use \CodeIgniter\Events\Events::PRIORITY_NORMAL instead.
|
||||
*/
|
||||
define('EVENT_PRIORITY_NORMAL', 100);
|
||||
|
||||
/**
|
||||
* @deprecated Use \CodeIgniter\Events\Events::PRIORITY_HIGH instead.
|
||||
*/
|
||||
define('EVENT_PRIORITY_HIGH', 10);
|
||||
|
|
|
|||
|
|
@ -26,37 +26,77 @@ class ContentSecurityPolicy extends BaseConfig
|
|||
*/
|
||||
public ?string $reportURI = null;
|
||||
|
||||
/**
|
||||
* Specifies a reporting endpoint to which violation reports ought to be sent.
|
||||
*/
|
||||
public ?string $reportTo = null;
|
||||
|
||||
/**
|
||||
* Instructs user agents to rewrite URL schemes, changing HTTP to HTTPS. This directive is for websites with large
|
||||
* numbers of old URLs that need to be rewritten.
|
||||
*/
|
||||
public bool $upgradeInsecureRequests = false;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// CSP DIRECTIVES SETTINGS
|
||||
// NOTE: once you set a policy to 'none', it cannot be further restricted
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Will default to self if not overridden
|
||||
* Will default to `'self'` if not overridden
|
||||
*
|
||||
* @var string|string[]|null
|
||||
* @var list<string>|string|null
|
||||
*/
|
||||
public string | array | null $defaultSrc = null;
|
||||
|
||||
/**
|
||||
* Lists allowed scripts' URLs.
|
||||
*
|
||||
* @var string|string[]
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public string | array $scriptSrc = 'self';
|
||||
|
||||
/**
|
||||
* Specifies valid sources for JavaScript <script> elements.
|
||||
*
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public array|string $scriptSrcElem = 'self';
|
||||
|
||||
/**
|
||||
* Specifies valid sources for JavaScript inline event
|
||||
* handlers and JavaScript URLs.
|
||||
*
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public array|string $scriptSrcAttr = 'self';
|
||||
|
||||
/**
|
||||
* Lists allowed stylesheets' URLs.
|
||||
*
|
||||
* @var string|string[]
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public string | array $styleSrc = 'self';
|
||||
|
||||
/**
|
||||
* Specifies valid sources for stylesheets <link> elements.
|
||||
*
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public array|string $styleSrcElem = 'self';
|
||||
|
||||
/**
|
||||
* Specifies valid sources for stylesheets inline
|
||||
* style attributes and `<style>` elements.
|
||||
*
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public array|string $styleSrcAttr = 'self';
|
||||
|
||||
/**
|
||||
* Defines the origins from which images can be loaded.
|
||||
*
|
||||
* @var string|string[]
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public string | array $imageSrc = 'self';
|
||||
|
||||
|
|
@ -65,35 +105,35 @@ class ContentSecurityPolicy extends BaseConfig
|
|||
*
|
||||
* Will default to self if not overridden
|
||||
*
|
||||
* @var string|string[]|null
|
||||
* @var list<string>|string|null
|
||||
*/
|
||||
public string | array | null $baseURI = null;
|
||||
|
||||
/**
|
||||
* Lists the URLs for workers and embedded frame contents
|
||||
*
|
||||
* @var string|string[]
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public string | array $childSrc = 'self';
|
||||
|
||||
/**
|
||||
* Limits the origins that you can connect to (via XHR, WebSockets, and EventSource).
|
||||
*
|
||||
* @var string|string[]
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public string | array $connectSrc = 'self';
|
||||
|
||||
/**
|
||||
* Specifies the origins that can serve web fonts.
|
||||
*
|
||||
* @var string|string[]
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public string | array $fontSrc;
|
||||
|
||||
/**
|
||||
* Lists valid endpoints for submission from `<form>` tags.
|
||||
*
|
||||
* @var string|string[]
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public string | array $formAction = 'self';
|
||||
|
||||
|
|
@ -102,62 +142,67 @@ class ContentSecurityPolicy extends BaseConfig
|
|||
* `<embed>`, and `<applet>` tags. This directive can't be used in `<meta>` tags and applies only to non-HTML
|
||||
* resources.
|
||||
*
|
||||
* @var string|string[]|null
|
||||
* @var list<string>|string|null
|
||||
*/
|
||||
public string | array | null $frameAncestors = null;
|
||||
|
||||
/**
|
||||
* The frame-src directive restricts the URLs which may be loaded into nested browsing contexts.
|
||||
*
|
||||
* @var string[]|string|null
|
||||
* @var list<string>|string|null
|
||||
*/
|
||||
public string | array | null $frameSrc = null;
|
||||
|
||||
/**
|
||||
* Restricts the origins allowed to deliver video and audio.
|
||||
*
|
||||
* @var string|string[]|null
|
||||
* @var list<string>|string|null
|
||||
*/
|
||||
public string | array | null $mediaSrc = null;
|
||||
|
||||
/**
|
||||
* Allows control over Flash and other plugins.
|
||||
*
|
||||
* @var string|string[]
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public string | array $objectSrc = 'self';
|
||||
|
||||
/**
|
||||
* @var string|string[]|null
|
||||
* @var list<string>|string|null
|
||||
*/
|
||||
public string | array | null $manifestSrc = null;
|
||||
|
||||
/**
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public array|string $workerSrc = [];
|
||||
|
||||
/**
|
||||
* Limits the kinds of plugins a page may invoke.
|
||||
*
|
||||
* @var string|string[]|null
|
||||
* @var list<string>|string|null
|
||||
*/
|
||||
public string | array | null $pluginTypes = null;
|
||||
|
||||
/**
|
||||
* List of actions allowed.
|
||||
*
|
||||
* @var string|string[]|null
|
||||
* @var list<string>|string|null
|
||||
*/
|
||||
public string | array | null $sandbox = null;
|
||||
|
||||
/**
|
||||
* Nonce tag for style
|
||||
* Nonce placeholder for style tags.
|
||||
*/
|
||||
public string $styleNonceTag = '{csp-style-nonce}';
|
||||
|
||||
/**
|
||||
* Nonce tag for script
|
||||
* Nonce placeholder for script tags.
|
||||
*/
|
||||
public string $scriptNonceTag = '{csp-script-nonce}';
|
||||
|
||||
/**
|
||||
* Replace nonce tag automatically
|
||||
* Replace nonce tag automatically?
|
||||
*/
|
||||
public bool $autoNonce = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,6 +84,8 @@ class Cookie extends BaseConfig
|
|||
* Defaults to `Lax` for compatibility with modern browsers. Setting `''`
|
||||
* (empty string) means default SameSite attribute set by browsers (`Lax`)
|
||||
* will be set on cookies. If set to `None`, `$secure` must also be set.
|
||||
*
|
||||
* @var ''|'Lax'|'None'|'Strict'
|
||||
*/
|
||||
public string $samesite = 'Lax';
|
||||
|
||||
|
|
|
|||
107
app/Config/Cors.php
Normal file
107
app/Config/Cors.php
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Config;
|
||||
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
|
||||
/**
|
||||
* Cross-Origin Resource Sharing (CORS) Configuration
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
||||
*/
|
||||
class Cors extends BaseConfig
|
||||
{
|
||||
/**
|
||||
* The default CORS configuration.
|
||||
*
|
||||
* @var array{
|
||||
* allowedOrigins: list<string>,
|
||||
* allowedOriginsPatterns: list<string>,
|
||||
* supportsCredentials: bool,
|
||||
* allowedHeaders: list<string>,
|
||||
* exposedHeaders: list<string>,
|
||||
* allowedMethods: list<string>,
|
||||
* maxAge: int,
|
||||
* }
|
||||
*/
|
||||
public array $default = [
|
||||
/**
|
||||
* Origins for the `Access-Control-Allow-Origin` header.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
|
||||
*
|
||||
* E.g.:
|
||||
* - ['http://localhost:8080']
|
||||
* - ['https://www.example.com']
|
||||
*/
|
||||
'allowedOrigins' => [],
|
||||
|
||||
/**
|
||||
* Origin regex patterns for the `Access-Control-Allow-Origin` header.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
|
||||
*
|
||||
* NOTE: A pattern specified here is part of a regular expression. It will
|
||||
* be actually `#\A<pattern>\z#`.
|
||||
*
|
||||
* E.g.:
|
||||
* - ['https://\w+\.example\.com']
|
||||
*/
|
||||
'allowedOriginsPatterns' => [],
|
||||
|
||||
/**
|
||||
* Weather to send the `Access-Control-Allow-Credentials` header.
|
||||
*
|
||||
* The Access-Control-Allow-Credentials response header tells browsers whether
|
||||
* the server allows cross-origin HTTP requests to include credentials.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
|
||||
*/
|
||||
'supportsCredentials' => false,
|
||||
|
||||
/**
|
||||
* Set headers to allow.
|
||||
*
|
||||
* The Access-Control-Allow-Headers response header is used in response to
|
||||
* a preflight request which includes the Access-Control-Request-Headers to
|
||||
* indicate which HTTP headers can be used during the actual request.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
|
||||
*/
|
||||
'allowedHeaders' => [],
|
||||
|
||||
/**
|
||||
* Set headers to expose.
|
||||
*
|
||||
* The Access-Control-Expose-Headers response header allows a server to
|
||||
* indicate which response headers should be made available to scripts running
|
||||
* in the browser, in response to a cross-origin request.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
|
||||
*/
|
||||
'exposedHeaders' => [],
|
||||
|
||||
/**
|
||||
* Set methods to allow.
|
||||
*
|
||||
* The Access-Control-Allow-Methods response header specifies one or more
|
||||
* methods allowed when accessing a resource in response to a preflight
|
||||
* request.
|
||||
*
|
||||
* E.g.:
|
||||
* - ['GET', 'POST', 'PUT', 'DELETE']
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
|
||||
*/
|
||||
'allowedMethods' => [],
|
||||
|
||||
/**
|
||||
* Set how many seconds the results of a preflight request can be cached.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
|
||||
*/
|
||||
'maxAge' => 7200,
|
||||
];
|
||||
}
|
||||
|
|
@ -27,34 +27,39 @@ class Database extends Config
|
|||
* @var array<string, mixed>
|
||||
*/
|
||||
public array $default = [
|
||||
'DSN' => '',
|
||||
'hostname' => 'localhost',
|
||||
'username' => '',
|
||||
'password' => '',
|
||||
'database' => '',
|
||||
'DBDriver' => 'MySQLi',
|
||||
'DBPrefix' => 'cp_',
|
||||
'pConnect' => false,
|
||||
'DBDebug' => ENVIRONMENT !== 'production',
|
||||
'charset' => 'utf8mb4',
|
||||
'DBCollat' => 'utf8mb4_unicode_ci',
|
||||
'swapPre' => '',
|
||||
'encrypt' => false,
|
||||
'compress' => false,
|
||||
'strictOn' => false,
|
||||
'failover' => [],
|
||||
'port' => 3306,
|
||||
'DSN' => '',
|
||||
'hostname' => 'localhost',
|
||||
'username' => '',
|
||||
'password' => '',
|
||||
'database' => '',
|
||||
'DBDriver' => 'MySQLi',
|
||||
'DBPrefix' => 'cp_',
|
||||
'pConnect' => false,
|
||||
'DBDebug' => true,
|
||||
'charset' => 'utf8mb4',
|
||||
'DBCollat' => 'utf8mb4_unicode_ci',
|
||||
'swapPre' => '',
|
||||
'encrypt' => false,
|
||||
'compress' => false,
|
||||
'strictOn' => false,
|
||||
'failover' => [],
|
||||
'port' => 3306,
|
||||
'numberNative' => false,
|
||||
'foundRows' => false,
|
||||
'dateFormat' => [
|
||||
'date' => 'Y-m-d',
|
||||
'datetime' => 'Y-m-d H:i:s',
|
||||
'time' => 'H:i:s',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* This database connection is used when running PHPUnit database tests.
|
||||
*
|
||||
* @noRector StringClassNameToClassConstantRector
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
public array $tests = [
|
||||
'DSN' => '',
|
||||
'DSN' => '',
|
||||
'hostname' => '127.0.0.1',
|
||||
'username' => '',
|
||||
'password' => '',
|
||||
|
|
@ -62,17 +67,24 @@ class Database extends Config
|
|||
'DBDriver' => 'SQLite3',
|
||||
'DBPrefix' => 'db_',
|
||||
// Needed to ensure we're working correctly with prefixes live. DO NOT REMOVE FOR CI DEVS
|
||||
'pConnect' => false,
|
||||
'DBDebug' => (ENVIRONMENT !== 'production'),
|
||||
'charset' => 'utf8',
|
||||
'DBCollat' => 'utf8_general_ci',
|
||||
'swapPre' => '',
|
||||
'encrypt' => false,
|
||||
'compress' => false,
|
||||
'strictOn' => false,
|
||||
'failover' => [],
|
||||
'port' => 3306,
|
||||
'pConnect' => false,
|
||||
'DBDebug' => true,
|
||||
'charset' => 'utf8',
|
||||
'DBCollat' => '',
|
||||
'swapPre' => '',
|
||||
'encrypt' => false,
|
||||
'compress' => false,
|
||||
'strictOn' => false,
|
||||
'failover' => [],
|
||||
'port' => 3306,
|
||||
'foreignKeys' => true,
|
||||
'busyTimeout' => 1000,
|
||||
'synchronous' => null,
|
||||
'dateFormat' => [
|
||||
'date' => 'Y-m-d',
|
||||
'datetime' => 'Y-m-d H:i:s',
|
||||
'time' => 'H:i:s',
|
||||
],
|
||||
];
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
|
@ -84,7 +96,6 @@ class Database extends Config
|
|||
// Ensure that we always set the database group to 'tests' if
|
||||
// we are currently running an automated test suite, so that
|
||||
// we don't overwrite live data on accident.
|
||||
/** @noRector RemoveAlwaysTrueIfConditionRector */
|
||||
if (ENVIRONMENT === 'testing') {
|
||||
$this->defaultGroup = 'tests';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,42 +12,34 @@ class DocTypes
|
|||
* @var array<string, string>
|
||||
*/
|
||||
public array $list = [
|
||||
'xhtml11' =>
|
||||
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
|
||||
'xhtml1-strict' =>
|
||||
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
|
||||
'xhtml1-trans' =>
|
||||
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
|
||||
'xhtml1-frame' =>
|
||||
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
|
||||
'xhtml-basic11' =>
|
||||
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
|
||||
'html5' => '<!DOCTYPE html>',
|
||||
'html4-strict' =>
|
||||
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">',
|
||||
'html4-trans' =>
|
||||
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
|
||||
'html4-frame' =>
|
||||
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">',
|
||||
'mathml1' =>
|
||||
'<!DOCTYPE math SYSTEM "http://www.w3.org/Math/DTD/mathml1/mathml.dtd">',
|
||||
'mathml2' =>
|
||||
'<!DOCTYPE math PUBLIC "-//W3C//DTD MathML 2.0//EN" "http://www.w3.org/Math/DTD/mathml2/mathml2.dtd">',
|
||||
'svg10' =>
|
||||
'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">',
|
||||
'svg11' =>
|
||||
'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">',
|
||||
'svg11-basic' =>
|
||||
'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Basic//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd">',
|
||||
'svg11-tiny' =>
|
||||
'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">',
|
||||
'xhtml-math-svg-xh' =>
|
||||
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd">',
|
||||
'xhtml-math-svg-sh' =>
|
||||
'<!DOCTYPE svg:svg PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd">',
|
||||
'xhtml-rdfa-1' =>
|
||||
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">',
|
||||
'xhtml-rdfa-2' =>
|
||||
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.1//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-2.dtd">',
|
||||
'xhtml11' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
|
||||
'xhtml1-strict' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
|
||||
'xhtml1-trans' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
|
||||
'xhtml1-frame' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
|
||||
'xhtml-basic11' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
|
||||
'html5' => '<!DOCTYPE html>',
|
||||
'html4-strict' => '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">',
|
||||
'html4-trans' => '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
|
||||
'html4-frame' => '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">',
|
||||
'mathml1' => '<!DOCTYPE math SYSTEM "http://www.w3.org/Math/DTD/mathml1/mathml.dtd">',
|
||||
'mathml2' => '<!DOCTYPE math PUBLIC "-//W3C//DTD MathML 2.0//EN" "http://www.w3.org/Math/DTD/mathml2/mathml2.dtd">',
|
||||
'svg10' => '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">',
|
||||
'svg11' => '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">',
|
||||
'svg11-basic' => '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Basic//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd">',
|
||||
'svg11-tiny' => '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">',
|
||||
'xhtml-math-svg-xh' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd">',
|
||||
'xhtml-math-svg-sh' => '<!DOCTYPE svg:svg PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd">',
|
||||
'xhtml-rdfa-1' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">',
|
||||
'xhtml-rdfa-2' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.1//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-2.dtd">',
|
||||
];
|
||||
|
||||
/**
|
||||
* Whether to remove the solidus (`/`) character for void HTML elements (e.g. `<input>`)
|
||||
* for HTML5 compatibility.
|
||||
*
|
||||
* Set to:
|
||||
* `true` - to be HTML5 compatible
|
||||
* `false` - to be XHTML compatible
|
||||
*/
|
||||
public bool $html5 = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class Email extends BaseConfig
|
|||
|
||||
public string $fromName = 'Castopod';
|
||||
|
||||
public string $recipients;
|
||||
public string $recipients = '';
|
||||
|
||||
/**
|
||||
* The "user agent"
|
||||
|
|
@ -30,10 +30,15 @@ class Email extends BaseConfig
|
|||
public string $mailPath = '/usr/sbin/sendmail';
|
||||
|
||||
/**
|
||||
* SMTP Server Address
|
||||
* SMTP Server Hostname
|
||||
*/
|
||||
public string $SMTPHost = '';
|
||||
|
||||
/**
|
||||
* Which SMTP authentication method to use: login, plain
|
||||
*/
|
||||
public string $SMTPAuthMethod = 'login';
|
||||
|
||||
/**
|
||||
* SMTP Username
|
||||
*/
|
||||
|
|
@ -60,7 +65,11 @@ class Email extends BaseConfig
|
|||
public bool $SMTPKeepAlive = false;
|
||||
|
||||
/**
|
||||
* SMTP Encryption. Either tls or ssl
|
||||
* SMTP Encryption.
|
||||
*
|
||||
* @var string '', 'tls' or 'ssl'. 'tls' will issue a STARTTLS command
|
||||
* to the server. 'ssl' means implicit SSL. Connection on port
|
||||
* 465 should set this to ''.
|
||||
*/
|
||||
public string $SMTPCrypto = 'tls';
|
||||
|
||||
|
|
@ -77,7 +86,7 @@ class Email extends BaseConfig
|
|||
/**
|
||||
* Type of mail, either 'text' or 'html'
|
||||
*/
|
||||
public string $mailType = 'text';
|
||||
public string $mailType = 'html';
|
||||
|
||||
/**
|
||||
* Character set (utf-8, iso-8859-1, etc.)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,23 @@ class Encryption extends BaseConfig
|
|||
*/
|
||||
public string $key = '';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Previous Encryption Keys
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* When rotating encryption keys, add old keys here to maintain ability
|
||||
* to decrypt data encrypted with previous keys. Encryption always uses
|
||||
* the current $key. Decryption tries current key first, then falls back
|
||||
* to previous keys if decryption fails.
|
||||
*
|
||||
* In .env file, use comma-separated string:
|
||||
* encryption.previousKeys = hex2bin:9be8c64fcea509867...,hex2bin:3f5a1d8e9c2b7a4f6...
|
||||
*
|
||||
* @var list<string>|string
|
||||
*/
|
||||
public array|string $previousKeys = '';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Encryption Driver to Use
|
||||
|
|
@ -58,4 +75,37 @@ class Encryption extends BaseConfig
|
|||
* HMAC digest to use, e.g. 'SHA512' or 'SHA256'. Default value is 'SHA512'.
|
||||
*/
|
||||
public string $digest = 'SHA512';
|
||||
|
||||
/**
|
||||
* Whether the cipher-text should be raw. If set to false, then it will be base64 encoded.
|
||||
* This setting is only used by OpenSSLHandler.
|
||||
*
|
||||
* Set to false for CI3 Encryption compatibility.
|
||||
*/
|
||||
public bool $rawData = true;
|
||||
|
||||
/**
|
||||
* Encryption key info.
|
||||
* This setting is only used by OpenSSLHandler.
|
||||
*
|
||||
* Set to 'encryption' for CI3 Encryption compatibility.
|
||||
*/
|
||||
public string $encryptKeyInfo = '';
|
||||
|
||||
/**
|
||||
* Authentication key info.
|
||||
* This setting is only used by OpenSSLHandler.
|
||||
*
|
||||
* Set to 'authentication' for CI3 Encryption compatibility.
|
||||
*/
|
||||
public string $authKeyInfo = '';
|
||||
|
||||
/**
|
||||
* Cipher to use.
|
||||
* This setting is only used by OpenSSLHandler.
|
||||
*
|
||||
* Set to 'AES-128-CBC' to decrypt encrypted data that encrypted
|
||||
* by CI3 Encryption default configuration.
|
||||
*/
|
||||
public string $cipher = 'AES-256-CTR';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@ namespace Config;
|
|||
use App\Entities\Actor;
|
||||
use App\Entities\Post;
|
||||
use App\Models\EpisodeModel;
|
||||
use CodeIgniter\Debug\Toolbar\Collectors\Database;
|
||||
use CodeIgniter\Events\Events;
|
||||
use CodeIgniter\Exceptions\FrameworkException;
|
||||
use Modules\Auth\Entities\User;
|
||||
use CodeIgniter\HotReloader\HotReloader;
|
||||
|
||||
/*
|
||||
* --------------------------------------------------------------------
|
||||
|
|
@ -28,8 +29,7 @@ use Modules\Auth\Entities\User;
|
|||
* Events::on('create', [$myInstance, 'myMethod']);
|
||||
*/
|
||||
|
||||
Events::on('pre_system', static function () {
|
||||
// @phpstan-ignore-next-line
|
||||
Events::on('pre_system', static function (): void {
|
||||
if (ENVIRONMENT !== 'testing') {
|
||||
if (ini_get('zlib.output_compression')) {
|
||||
throw FrameworkException::forEnabledZlibOutputCompression();
|
||||
|
|
@ -47,30 +47,22 @@ Events::on('pre_system', static function () {
|
|||
* Debug Toolbar Listeners.
|
||||
* --------------------------------------------------------------------
|
||||
* If you delete, they will no longer be collected.
|
||||
*
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
if (CI_DEBUG && ! is_cli()) {
|
||||
Events::on('DBQuery', 'CodeIgniter\Debug\Toolbar\Collectors\Database::collect');
|
||||
Services::toolbar()->respond();
|
||||
}
|
||||
});
|
||||
Events::on('DBQuery', Database::class . '::collect');
|
||||
service('toolbar')
|
||||
->respond();
|
||||
|
||||
Events::on('login', static function (User $user): void {
|
||||
helper('auth');
|
||||
// set interact_as_actor_id value
|
||||
$userPodcasts = $user->podcasts;
|
||||
if ($userPodcasts = $user->podcasts) {
|
||||
set_interact_as_actor($userPodcasts[0]->actor_id);
|
||||
// Hot Reload route - for framework use on the hot reloader.
|
||||
if (ENVIRONMENT === 'development') {
|
||||
service('routes')->get('__hot-reload', static function (): void {
|
||||
new HotReloader()
|
||||
->run();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Events::on('logout', static function (User $user): void {
|
||||
helper('auth');
|
||||
// remove user's interact_as_actor session
|
||||
remove_interact_as_actor();
|
||||
});
|
||||
|
||||
/*
|
||||
* --------------------------------------------------------------------
|
||||
* Fediverse events
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ declare(strict_types=1);
|
|||
namespace Config;
|
||||
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
use CodeIgniter\Debug\ExceptionHandler;
|
||||
use CodeIgniter\Debug\ExceptionHandlerInterface;
|
||||
use Psr\Log\LogLevel;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Setup how the exception handler works.
|
||||
|
|
@ -29,7 +33,7 @@ class Exceptions extends BaseConfig
|
|||
* Any status codes here will NOT be logged if logging is turned on.
|
||||
* By default, only 404 (Page Not Found) exceptions are ignored.
|
||||
*
|
||||
* @var int[]
|
||||
* @var list<int>
|
||||
*/
|
||||
public array $ignoreCodes = [404];
|
||||
|
||||
|
|
@ -52,7 +56,53 @@ class Exceptions extends BaseConfig
|
|||
* In order to specify 2 levels, use "/" to separate.
|
||||
* ex. ['server', 'setup/password', 'secret_token']
|
||||
*
|
||||
* @var string[]
|
||||
* @var list<string>
|
||||
*/
|
||||
public array $sensitiveDataInTrace = [];
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* WHETHER TO THROW AN EXCEPTION ON DEPRECATED ERRORS
|
||||
* --------------------------------------------------------------------------
|
||||
* If set to `true`, DEPRECATED errors are only logged and no exceptions are
|
||||
* thrown. This option also works for user deprecations.
|
||||
*/
|
||||
public bool $logDeprecations = true;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* LOG LEVEL THRESHOLD FOR DEPRECATIONS
|
||||
* --------------------------------------------------------------------------
|
||||
* If `$logDeprecations` is set to `true`, this sets the log level
|
||||
* to which the deprecation will be logged. This should be one of the log
|
||||
* levels recognized by PSR-3.
|
||||
*
|
||||
* The related `Config\Logger::$threshold` should be adjusted, if needed,
|
||||
* to capture logging the deprecations.
|
||||
*/
|
||||
public string $deprecationLogLevel = LogLevel::WARNING;
|
||||
|
||||
/*
|
||||
* DEFINE THE HANDLERS USED
|
||||
* --------------------------------------------------------------------------
|
||||
* Given the HTTP status code, returns exception handler that
|
||||
* should be used to deal with this error. By default, it will run CodeIgniter's
|
||||
* default handler and display the error information in the expected format
|
||||
* for CLI, HTTP, or AJAX requests, as determined by is_cli() and the expected
|
||||
* response format.
|
||||
*
|
||||
* Custom handlers can be returned if you want to handle one or more specific
|
||||
* error codes yourself like:
|
||||
*
|
||||
* if (in_array($statusCode, [400, 404, 500])) {
|
||||
* return new \App\Libraries\MyExceptionHandler();
|
||||
* }
|
||||
* if ($exception instanceOf PageNotFoundException) {
|
||||
* return new \App\Libraries\MyExceptionHandler();
|
||||
* }
|
||||
*/
|
||||
public function handler(int $statusCode, Throwable $exception): ExceptionHandlerInterface
|
||||
{
|
||||
return new ExceptionHandler($this);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,21 +12,28 @@ use CodeIgniter\Config\BaseConfig;
|
|||
class Feature extends BaseConfig
|
||||
{
|
||||
/**
|
||||
* Enable multiple filters for a route or not.
|
||||
*
|
||||
* If you enable this:
|
||||
* - CodeIgniter\CodeIgniter::handleRequest() uses:
|
||||
* - CodeIgniter\Filters\Filters::enableFilters(), instead of enableFilter()
|
||||
* - CodeIgniter\CodeIgniter::tryToRouteIt() uses:
|
||||
* - CodeIgniter\Router\Router::getFilters(), instead of getFilter()
|
||||
* - CodeIgniter\Router\Router::handle() uses:
|
||||
* - property $filtersInfo, instead of $filterInfo
|
||||
* - CodeIgniter\Router\RouteCollection::getFiltersForRoute(), instead of getFilterForRoute()
|
||||
* Use improved new auto routing instead of the legacy version.
|
||||
*/
|
||||
public bool $multipleFilters = false;
|
||||
public bool $autoRoutesImproved = true;
|
||||
|
||||
/**
|
||||
* Use improved new auto routing instead of the default legacy version.
|
||||
* Use filter execution order in 4.4 or before.
|
||||
*/
|
||||
public bool $autoRoutesImproved = false;
|
||||
public bool $oldFilterOrder = false;
|
||||
|
||||
/**
|
||||
* The behavior of `limit(0)` in Query Builder.
|
||||
*
|
||||
* If true, `limit(0)` returns all records. (the behavior of 4.4.x or before in version 4.x.)
|
||||
* If false, `limit(0)` returns no records. (the behavior of 3.1.9 or later in version 3.x.)
|
||||
*/
|
||||
public bool $limitZeroAsAll = true;
|
||||
|
||||
/**
|
||||
* Use strict location negotiation.
|
||||
*
|
||||
* By default, the locale is selected based on a loose comparison of the language code (ISO 639-1)
|
||||
* Enabling strict comparison will also consider the region code (ISO 3166-1 alpha-2).
|
||||
*/
|
||||
public bool $strictLocaleNegotiation = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class Fediverse extends FediverseBaseConfig
|
|||
*/
|
||||
public string $noteObject = NoteObject::class;
|
||||
|
||||
public string $defaultAvatarImagePath = 'media/castopod-avatar_thumbnail.webp';
|
||||
public string $defaultAvatarImagePath = 'castopod-avatar_thumbnail.webp';
|
||||
|
||||
public string $defaultAvatarImageMimetype = 'image/webp';
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ class Fediverse extends FediverseBaseConfig
|
|||
}
|
||||
|
||||
['dirname' => $dirname, 'extension' => $extension, 'filename' => $filename] = pathinfo(
|
||||
$defaultBanner['path']
|
||||
$defaultBanner['path'],
|
||||
);
|
||||
$defaultBannerPath = $filename;
|
||||
if ($dirname !== '.') {
|
||||
|
|
@ -52,7 +52,7 @@ class Fediverse extends FediverseBaseConfig
|
|||
|
||||
helper('media');
|
||||
|
||||
$this->defaultCoverImagePath = media_path($defaultBannerPath . '_federation.' . $extension);
|
||||
$this->defaultCoverImagePath = $defaultBannerPath . '_federation.' . $extension;
|
||||
$this->defaultCoverImageMimetype = $defaultBanner['mimetype'];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,57 +4,87 @@ declare(strict_types=1);
|
|||
|
||||
namespace Config;
|
||||
|
||||
use App\Filters\AllowCorsFilter;
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
use CodeIgniter\Filters\CSRF;
|
||||
use CodeIgniter\Filters\DebugToolbar;
|
||||
use CodeIgniter\Filters\ForceHTTPS;
|
||||
use CodeIgniter\Filters\Honeypot;
|
||||
use CodeIgniter\Filters\InvalidChars;
|
||||
use CodeIgniter\Filters\PageCache;
|
||||
use CodeIgniter\Filters\PerformanceMetrics;
|
||||
use CodeIgniter\Filters\SecureHeaders;
|
||||
use Modules\Api\Rest\V1\Filters\ApiFilter;
|
||||
use Modules\Auth\Filters\PermissionFilter;
|
||||
use Modules\Fediverse\Filters\AllowCorsFilter;
|
||||
use Modules\Fediverse\Filters\FediverseFilter;
|
||||
use Modules\PremiumPodcasts\Filters\PodcastUnlockFilter;
|
||||
use Myth\Auth\Filters\LoginFilter;
|
||||
use Myth\Auth\Filters\RoleFilter;
|
||||
|
||||
class Filters extends BaseConfig
|
||||
{
|
||||
/**
|
||||
* Configures aliases for Filter classes to make reading things nicer and simpler.
|
||||
*
|
||||
* @var array<string, string>
|
||||
* @var array<string, class-string|list<class-string>>
|
||||
*
|
||||
* [filter_name => classname]
|
||||
* or [filter_name => [classname1, classname2, ...]]
|
||||
*/
|
||||
public array $aliases = [
|
||||
'csrf' => CSRF::class,
|
||||
'toolbar' => DebugToolbar::class,
|
||||
'honeypot' => Honeypot::class,
|
||||
'invalidchars' => InvalidChars::class,
|
||||
'csrf' => CSRF::class,
|
||||
'toolbar' => DebugToolbar::class,
|
||||
'honeypot' => Honeypot::class,
|
||||
'invalidchars' => InvalidChars::class,
|
||||
'secureheaders' => SecureHeaders::class,
|
||||
'login' => LoginFilter::class,
|
||||
'role' => RoleFilter::class,
|
||||
'permission' => PermissionFilter::class,
|
||||
'fediverse' => FediverseFilter::class,
|
||||
'allow-cors' => AllowCorsFilter::class,
|
||||
'rest-api' => ApiFilter::class,
|
||||
'podcast-unlock' => PodcastUnlockFilter::class,
|
||||
'allow-cors' => AllowCorsFilter::class,
|
||||
'cors' => Cors::class,
|
||||
'forcehttps' => ForceHTTPS::class,
|
||||
'pagecache' => PageCache::class,
|
||||
'performance' => PerformanceMetrics::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* List of special required filters.
|
||||
*
|
||||
* The filters listed here are special. They are applied before and after
|
||||
* other kinds of filters, and always applied even if a route does not exist.
|
||||
*
|
||||
* Filters set by default provide framework functionality. If removed,
|
||||
* those functions will no longer work.
|
||||
*
|
||||
* @see https://codeigniter.com/user_guide/incoming/filters.html#provided-filters
|
||||
*
|
||||
* @var array{before: list<string>, after: list<string>}
|
||||
*/
|
||||
public array $required = [
|
||||
'before' => [
|
||||
'forcehttps', // Force Global Secure Requests
|
||||
'pagecache', // Web Page Caching
|
||||
],
|
||||
'after' => [
|
||||
'pagecache', // Web Page Caching
|
||||
'performance', // Performance Metrics
|
||||
'toolbar', // Debug Toolbar
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* List of filter aliases that are always applied before and after every request.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
* @var array{
|
||||
* before: array<string, array{except: list<string>|string}>|list<string>,
|
||||
* after: array<string, array{except: list<string>|string}>|list<string>
|
||||
* }
|
||||
*/
|
||||
public array $globals = [
|
||||
'before' => [
|
||||
// 'honeypot',
|
||||
'csrf' => [
|
||||
'except' => ['@[a-zA-Z0-9\_]{1,32}/inbox'],
|
||||
'except' => [
|
||||
'@[a-zA-Z0-9\_]{1,32}/inbox',
|
||||
'api/rest/v1/episodes',
|
||||
'api/rest/v1/episodes/[0-9]+/publish',
|
||||
],
|
||||
],
|
||||
// 'invalidchars',
|
||||
],
|
||||
'after' => [
|
||||
'toolbar',
|
||||
// 'honeypot',
|
||||
// 'secureheaders',
|
||||
],
|
||||
|
|
@ -63,12 +93,12 @@ class Filters extends BaseConfig
|
|||
/**
|
||||
* List of filter aliases that works on a particular HTTP method (GET, POST, etc.).
|
||||
*
|
||||
* Example: 'post' => ['foo', 'bar']
|
||||
* Example: 'POST' => ['foo', 'bar']
|
||||
*
|
||||
* If you use this, you should disable auto-routing because auto-routing permits any HTTP method to access a
|
||||
* controller. Accessing the controller with a method you don’t expect could bypass the filter.
|
||||
*
|
||||
* @var array<string, string[]>
|
||||
* @var array<string, list<string>>
|
||||
*/
|
||||
public array $methods = [];
|
||||
|
||||
|
|
@ -77,7 +107,7 @@ class Filters extends BaseConfig
|
|||
*
|
||||
* Example: 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']]
|
||||
*
|
||||
* @var array<string, array<string, string[]>>
|
||||
* @var array<string, array<string, list<string>>>
|
||||
*/
|
||||
public array $filters = [];
|
||||
|
||||
|
|
@ -86,12 +116,14 @@ class Filters extends BaseConfig
|
|||
parent::__construct();
|
||||
|
||||
$this->filters = [
|
||||
'login' => [
|
||||
'session' => [
|
||||
'before' => [config('Admin')->gateway . '*', config('Analytics')->gateway . '*'],
|
||||
],
|
||||
'podcast-unlock' => [
|
||||
'before' => ['*@*/episodes/*'],
|
||||
],
|
||||
];
|
||||
|
||||
$this->aliases['permission'] = PermissionFilter::class;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ namespace Config;
|
|||
|
||||
use CodeIgniter\Config\ForeignCharacters as BaseForeignCharacters;
|
||||
|
||||
/**
|
||||
* @immutable
|
||||
*/
|
||||
class ForeignCharacters extends BaseForeignCharacters
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ declare(strict_types=1);
|
|||
namespace Config;
|
||||
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
use CodeIgniter\Format\FormatterInterface;
|
||||
use CodeIgniter\Format\JSONFormatter;
|
||||
use CodeIgniter\Format\XMLFormatter;
|
||||
|
||||
|
|
@ -24,7 +23,7 @@ class Format extends BaseConfig
|
|||
* These formats are only checked when the data passed to the respond()
|
||||
* method is an array.
|
||||
*
|
||||
* @var string[]
|
||||
* @var list<string>
|
||||
*/
|
||||
public array $supportedResponseFormats = [
|
||||
'application/json',
|
||||
|
|
@ -45,8 +44,8 @@ class Format extends BaseConfig
|
|||
*/
|
||||
public array $formatters = [
|
||||
'application/json' => JSONFormatter::class,
|
||||
'application/xml' => XMLFormatter::class,
|
||||
'text/xml' => XMLFormatter::class,
|
||||
'application/xml' => XMLFormatter::class,
|
||||
'text/xml' => XMLFormatter::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -61,19 +60,16 @@ class Format extends BaseConfig
|
|||
*/
|
||||
public array $formatterOptions = [
|
||||
'application/json' => JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,
|
||||
'application/xml' => 0,
|
||||
'text/xml' => 0,
|
||||
'application/xml' => 0,
|
||||
'text/xml' => 0,
|
||||
];
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* A Factory method to return the appropriate formatter for the given mime type.
|
||||
* --------------------------------------------------------------------------
|
||||
* Maximum depth for JSON encoding.
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* @deprecated This is an alias of `\CodeIgniter\Format\Format::getFormatter`. Use that instead.
|
||||
* This value determines how deep the JSON encoder will traverse nested structures.
|
||||
*/
|
||||
public function getFormatter(string $mime): FormatterInterface
|
||||
{
|
||||
return Services::format()->getFormatter($mime);
|
||||
}
|
||||
public int $jsonEncodeDepth = 512;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,23 +25,22 @@ class Generators extends BaseConfig
|
|||
*
|
||||
* YOU HAVE BEEN WARNED!
|
||||
*
|
||||
* @var array<string, string>
|
||||
* @var array<string, string|array<string,string>>
|
||||
*/
|
||||
public array $views = [
|
||||
'make:command' =>
|
||||
'CodeIgniter\Commands\Generators\Views\command.tpl.php',
|
||||
'make:config' => 'CodeIgniter\Commands\Generators\Views\config.tpl.php',
|
||||
'make:controller' =>
|
||||
'CodeIgniter\Commands\Generators\Views\controller.tpl.php',
|
||||
'make:entity' => 'CodeIgniter\Commands\Generators\Views\entity.tpl.php',
|
||||
'make:filter' => 'CodeIgniter\Commands\Generators\Views\filter.tpl.php',
|
||||
'make:migration' =>
|
||||
'CodeIgniter\Commands\Generators\Views\migration.tpl.php',
|
||||
'make:model' => 'CodeIgniter\Commands\Generators\Views\model.tpl.php',
|
||||
'make:seeder' => 'CodeIgniter\Commands\Generators\Views\seeder.tpl.php',
|
||||
'make:validation' =>
|
||||
'CodeIgniter\Commands\Generators\Views\validation.tpl.php',
|
||||
'session:migration' =>
|
||||
'CodeIgniter\Commands\Generators\Views\migration.tpl.php',
|
||||
'make:cell' => [
|
||||
'class' => 'CodeIgniter\Commands\Generators\Views\cell.tpl.php',
|
||||
'view' => 'CodeIgniter\Commands\Generators\Views\cell_view.tpl.php',
|
||||
],
|
||||
'make:command' => 'CodeIgniter\Commands\Generators\Views\command.tpl.php',
|
||||
'make:config' => 'CodeIgniter\Commands\Generators\Views\config.tpl.php',
|
||||
'make:controller' => 'CodeIgniter\Commands\Generators\Views\controller.tpl.php',
|
||||
'make:entity' => 'CodeIgniter\Commands\Generators\Views\entity.tpl.php',
|
||||
'make:filter' => 'CodeIgniter\Commands\Generators\Views\filter.tpl.php',
|
||||
'make:migration' => 'CodeIgniter\Commands\Generators\Views\migration.tpl.php',
|
||||
'make:model' => 'CodeIgniter\Commands\Generators\Views\model.tpl.php',
|
||||
'make:seeder' => 'CodeIgniter\Commands\Generators\Views\seeder.tpl.php',
|
||||
'make:validation' => 'CodeIgniter\Commands\Generators\Views\validation.tpl.php',
|
||||
'session:migration' => 'CodeIgniter\Commands\Generators\Views\migration.tpl.php',
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,15 @@ class Honeypot extends BaseConfig
|
|||
|
||||
/**
|
||||
* Honeypot container
|
||||
*
|
||||
* If you enabled CSP, you can remove `style="display:none"`.
|
||||
*/
|
||||
public string $container = '<div style="display:none">{template}</div>';
|
||||
|
||||
/**
|
||||
* The id attribute for Honeypot container tag
|
||||
*
|
||||
* Used when CSP is enabled.
|
||||
*/
|
||||
public string $containerId = 'hpc';
|
||||
}
|
||||
|
|
|
|||
42
app/Config/Hostnames.php
Normal file
42
app/Config/Hostnames.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Config;
|
||||
|
||||
class Hostnames
|
||||
{
|
||||
// List of known two-part TLDs for subdomain extraction
|
||||
public const TWO_PART_TLDS = [
|
||||
'co.uk', 'org.uk', 'gov.uk', 'ac.uk', 'sch.uk', 'ltd.uk', 'plc.uk',
|
||||
'com.au', 'net.au', 'org.au', 'edu.au', 'gov.au', 'asn.au', 'id.au',
|
||||
'co.jp', 'ac.jp', 'go.jp', 'or.jp', 'ne.jp', 'gr.jp',
|
||||
'co.nz', 'org.nz', 'govt.nz', 'ac.nz', 'net.nz', 'geek.nz', 'maori.nz', 'school.nz',
|
||||
'co.in', 'net.in', 'org.in', 'ind.in', 'ac.in', 'gov.in', 'res.in',
|
||||
'com.cn', 'net.cn', 'org.cn', 'gov.cn', 'edu.cn',
|
||||
'com.sg', 'net.sg', 'org.sg', 'gov.sg', 'edu.sg', 'per.sg',
|
||||
'co.za', 'org.za', 'gov.za', 'ac.za', 'net.za',
|
||||
'co.kr', 'or.kr', 'go.kr', 'ac.kr', 'ne.kr', 'pe.kr',
|
||||
'co.th', 'or.th', 'go.th', 'ac.th', 'net.th', 'in.th',
|
||||
'com.my', 'net.my', 'org.my', 'edu.my', 'gov.my', 'mil.my', 'name.my',
|
||||
'com.mx', 'org.mx', 'net.mx', 'edu.mx', 'gob.mx',
|
||||
'com.br', 'net.br', 'org.br', 'gov.br', 'edu.br', 'art.br', 'eng.br',
|
||||
'co.il', 'org.il', 'ac.il', 'gov.il', 'net.il', 'muni.il',
|
||||
'co.id', 'or.id', 'ac.id', 'go.id', 'net.id', 'web.id', 'my.id',
|
||||
'com.hk', 'edu.hk', 'gov.hk', 'idv.hk', 'net.hk', 'org.hk',
|
||||
'com.tw', 'net.tw', 'org.tw', 'edu.tw', 'gov.tw', 'idv.tw',
|
||||
'com.sa', 'net.sa', 'org.sa', 'gov.sa', 'edu.sa', 'sch.sa', 'med.sa',
|
||||
'co.ae', 'net.ae', 'org.ae', 'gov.ae', 'ac.ae', 'sch.ae',
|
||||
'com.tr', 'net.tr', 'org.tr', 'gov.tr', 'edu.tr', 'av.tr', 'gen.tr',
|
||||
'co.ke', 'or.ke', 'go.ke', 'ac.ke', 'sc.ke', 'me.ke', 'mobi.ke', 'info.ke',
|
||||
'com.ng', 'org.ng', 'gov.ng', 'edu.ng', 'net.ng', 'sch.ng', 'name.ng',
|
||||
'com.pk', 'net.pk', 'org.pk', 'gov.pk', 'edu.pk', 'fam.pk',
|
||||
'com.eg', 'edu.eg', 'gov.eg', 'org.eg', 'net.eg',
|
||||
'com.cy', 'net.cy', 'org.cy', 'gov.cy', 'ac.cy',
|
||||
'com.lk', 'org.lk', 'edu.lk', 'gov.lk', 'net.lk', 'int.lk',
|
||||
'com.bd', 'net.bd', 'org.bd', 'ac.bd', 'gov.bd', 'mil.bd',
|
||||
'com.ar', 'net.ar', 'org.ar', 'gov.ar', 'edu.ar', 'mil.ar',
|
||||
'gob.cl', 'com.pl', 'net.pl', 'org.pl', 'gov.pl', 'edu.pl',
|
||||
'co.ir', 'ac.ir', 'org.ir', 'id.ir', 'gov.ir', 'sch.ir', 'net.ir',
|
||||
];
|
||||
}
|
||||
|
|
@ -17,6 +17,8 @@ class Images extends BaseConfig
|
|||
|
||||
/**
|
||||
* The path to the image library. Required for ImageMagick, GraphicsMagick, or NetPBM.
|
||||
*
|
||||
* @deprecated 4.7.0 No longer used.
|
||||
*/
|
||||
public string $libraryPath = '/usr/local/bin/convert';
|
||||
|
||||
|
|
@ -26,7 +28,7 @@ class Images extends BaseConfig
|
|||
* @var array<string, string>
|
||||
*/
|
||||
public array $handlers = [
|
||||
'gd' => GDHandler::class,
|
||||
'gd' => GDHandler::class,
|
||||
'imagick' => ImageMagickHandler::class,
|
||||
];
|
||||
|
||||
|
|
@ -51,55 +53,55 @@ class Images extends BaseConfig
|
|||
*/
|
||||
public array $podcastCoverSizes = [
|
||||
'tiny' => [
|
||||
'width' => 40,
|
||||
'height' => 40,
|
||||
'mimetype' => 'image/webp',
|
||||
'width' => 40,
|
||||
'height' => 40,
|
||||
'mimetype' => 'image/webp',
|
||||
'extension' => 'webp',
|
||||
],
|
||||
'thumbnail' => [
|
||||
'width' => 150,
|
||||
'height' => 150,
|
||||
'mimetype' => 'image/webp',
|
||||
'width' => 150,
|
||||
'height' => 150,
|
||||
'mimetype' => 'image/webp',
|
||||
'extension' => 'webp',
|
||||
],
|
||||
'medium' => [
|
||||
'width' => 320,
|
||||
'height' => 320,
|
||||
'mimetype' => 'image/webp',
|
||||
'width' => 320,
|
||||
'height' => 320,
|
||||
'mimetype' => 'image/webp',
|
||||
'extension' => 'webp',
|
||||
],
|
||||
'large' => [
|
||||
'width' => 1024,
|
||||
'height' => 1024,
|
||||
'mimetype' => 'image/webp',
|
||||
'width' => 1024,
|
||||
'height' => 1024,
|
||||
'mimetype' => 'image/webp',
|
||||
'extension' => 'webp',
|
||||
],
|
||||
'feed' => [
|
||||
'width' => 1400,
|
||||
'width' => 1400,
|
||||
'height' => 1400,
|
||||
],
|
||||
'id3' => [
|
||||
'width' => 500,
|
||||
'width' => 500,
|
||||
'height' => 500,
|
||||
],
|
||||
'og' => [
|
||||
'width' => 1200,
|
||||
'width' => 1200,
|
||||
'height' => 1200,
|
||||
],
|
||||
'federation' => [
|
||||
'width' => 400,
|
||||
'width' => 400,
|
||||
'height' => 400,
|
||||
],
|
||||
'webmanifest192' => [
|
||||
'width' => 192,
|
||||
'height' => 192,
|
||||
'mimetype' => 'image/png',
|
||||
'width' => 192,
|
||||
'height' => 192,
|
||||
'mimetype' => 'image/png',
|
||||
'extension' => 'png',
|
||||
],
|
||||
'webmanifest512' => [
|
||||
'width' => 512,
|
||||
'height' => 512,
|
||||
'mimetype' => 'image/png',
|
||||
'width' => 512,
|
||||
'height' => 512,
|
||||
'mimetype' => 'image/png',
|
||||
'extension' => 'png',
|
||||
],
|
||||
];
|
||||
|
|
@ -113,24 +115,24 @@ class Images extends BaseConfig
|
|||
*/
|
||||
public array $podcastBannerSizes = [
|
||||
'small' => [
|
||||
'width' => 320,
|
||||
'height' => 128,
|
||||
'mimetype' => 'image/webp',
|
||||
'width' => 320,
|
||||
'height' => 128,
|
||||
'mimetype' => 'image/webp',
|
||||
'extension' => 'webp',
|
||||
],
|
||||
'medium' => [
|
||||
'width' => 960,
|
||||
'height' => 320,
|
||||
'mimetype' => 'image/webp',
|
||||
'width' => 960,
|
||||
'height' => 320,
|
||||
'mimetype' => 'image/webp',
|
||||
'extension' => 'webp',
|
||||
],
|
||||
'federation' => [
|
||||
'width' => 1500,
|
||||
'width' => 1500,
|
||||
'height' => 500,
|
||||
],
|
||||
];
|
||||
|
||||
public string $avatarDefaultPath = 'castopod-avatar.jpg';
|
||||
public string $avatarDefaultPath = 'assets/images/castopod-avatar.jpg';
|
||||
|
||||
public string $avatarDefaultMimeType = 'image/jpg';
|
||||
|
||||
|
|
@ -139,31 +141,31 @@ class Images extends BaseConfig
|
|||
*/
|
||||
public array $podcastBannerDefaultPaths = [
|
||||
'default' => [
|
||||
'path' => 'castopod-banner-pine.jpg',
|
||||
'path' => 'assets/images/castopod-banner-pine.jpg',
|
||||
'mimetype' => 'image/jpeg',
|
||||
],
|
||||
'pine' => [
|
||||
'path' => 'castopod-banner-pine.jpg',
|
||||
'path' => 'assets/images/castopod-banner-pine.jpg',
|
||||
'mimetype' => 'image/jpeg',
|
||||
],
|
||||
'crimson' => [
|
||||
'path' => 'castopod-banner-crimson.jpg',
|
||||
'path' => 'assets/images/castopod-banner-crimson.jpg',
|
||||
'mimetype' => 'image/jpeg',
|
||||
],
|
||||
'amber' => [
|
||||
'path' => 'castopod-banner-amber.jpg',
|
||||
'path' => 'assets/images/castopod-banner-amber.jpg',
|
||||
'mimetype' => 'image/jpeg',
|
||||
],
|
||||
'lake' => [
|
||||
'path' => 'castopod-banner-lake.jpg',
|
||||
'path' => 'assets/images/castopod-banner-lake.jpg',
|
||||
'mimetype' => 'image/jpeg',
|
||||
],
|
||||
'jacaranda' => [
|
||||
'path' => 'castopod-banner-jacaranda.jpg',
|
||||
'path' => 'assets/images/castopod-banner-jacaranda.jpg',
|
||||
'mimetype' => 'image/jpeg',
|
||||
],
|
||||
'onyx' => [
|
||||
'path' => 'castopod-banner-onyx.jpg',
|
||||
'path' => 'assets/images/castopod-banner-onyx.jpg',
|
||||
'mimetype' => 'image/jpeg',
|
||||
],
|
||||
];
|
||||
|
|
@ -181,26 +183,25 @@ class Images extends BaseConfig
|
|||
*/
|
||||
public array $personAvatarSizes = [
|
||||
'federation' => [
|
||||
'width' => 400,
|
||||
'width' => 400,
|
||||
'height' => 400,
|
||||
],
|
||||
'tiny' => [
|
||||
'width' => 40,
|
||||
'height' => 40,
|
||||
'mimetype' => 'image/webp',
|
||||
'width' => 40,
|
||||
'height' => 40,
|
||||
'mimetype' => 'image/webp',
|
||||
'extension' => 'webp',
|
||||
],
|
||||
'thumbnail' => [
|
||||
'width' => 150,
|
||||
'height' => 150,
|
||||
'mimetype' => 'image/webp',
|
||||
'width' => 150,
|
||||
'height' => 150,
|
||||
'mimetype' => 'image/webp',
|
||||
'extension' => 'webp',
|
||||
],
|
||||
'medium' => [
|
||||
'width' => 320,
|
||||
'height' => 320,
|
||||
'mimetype' =>
|
||||
'image/webp',
|
||||
'width' => 320,
|
||||
'height' => 320,
|
||||
'mimetype' => 'image/webp',
|
||||
'extension' => 'webp',
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ declare(strict_types=1);
|
|||
|
||||
namespace Config;
|
||||
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
use Kint\Renderer\Renderer;
|
||||
use Kint\Parser\ConstructablePluginInterface;
|
||||
use Kint\Renderer\Rich\TabPluginInterface;
|
||||
use Kint\Renderer\Rich\ValuePluginInterface;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Kint
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* We use Kint's `RichRenderer` and `CLIRenderer`. This area contains options
|
||||
|
|
@ -17,7 +17,7 @@ use Kint\Renderer\Renderer;
|
|||
*
|
||||
* @see https://kint-php.github.io/kint/ for details on these settings.
|
||||
*/
|
||||
class Kint extends BaseConfig
|
||||
class Kint
|
||||
{
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
@ -26,9 +26,9 @@ class Kint extends BaseConfig
|
|||
*/
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
* @var list<class-string<ConstructablePluginInterface>|ConstructablePluginInterface>|null
|
||||
*/
|
||||
public array $plugins = [];
|
||||
public ?array $plugins = [];
|
||||
|
||||
public int $maxDepth = 6;
|
||||
|
||||
|
|
@ -46,17 +46,15 @@ class Kint extends BaseConfig
|
|||
|
||||
public bool $richFolder = false;
|
||||
|
||||
public int $richSort = Renderer::SORT_FULL;
|
||||
/**
|
||||
* @var array<string, class-string<ValuePluginInterface>>|null
|
||||
*/
|
||||
public ?array $richObjectPlugins = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
* @var array<string, class-string<TabPluginInterface>>|null
|
||||
*/
|
||||
public array $richObjectPlugins = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public array $richTabPlugins = [];
|
||||
public ?array $richTabPlugins = [];
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ namespace Config;
|
|||
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
use CodeIgniter\Log\Handlers\FileHandler;
|
||||
use CodeIgniter\Log\Handlers\HandlerInterface;
|
||||
|
||||
class Logger extends BaseConfig
|
||||
{
|
||||
|
|
@ -38,9 +39,9 @@ class Logger extends BaseConfig
|
|||
* For a live site you'll usually enable Critical or higher (3) to be logged otherwise
|
||||
* your log files will fill up very fast.
|
||||
*
|
||||
* @var int|int[]
|
||||
* @var int|list<int>
|
||||
*/
|
||||
public int | array $threshold = 4;
|
||||
public int | array $threshold = (ENVIRONMENT === 'production') ? 4 : 9;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
|
|
@ -75,7 +76,7 @@ class Logger extends BaseConfig
|
|||
* Handlers are executed in the order defined in this array, starting with
|
||||
* the handler on top and continuing down.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
* @var array<class-string<HandlerInterface>, array<string, int|list<string>|string>>
|
||||
*/
|
||||
public array $handlers = [
|
||||
/*
|
||||
|
|
@ -114,5 +115,32 @@ class Logger extends BaseConfig
|
|||
*/
|
||||
'path' => '',
|
||||
],
|
||||
|
||||
/*
|
||||
* The ChromeLoggerHandler requires the use of the Chrome web browser
|
||||
* and the ChromeLogger extension. Uncomment this block to use it.
|
||||
*/
|
||||
// 'CodeIgniter\Log\Handlers\ChromeLoggerHandler' => [
|
||||
// /*
|
||||
// * The log levels that this handler will handle.
|
||||
// */
|
||||
// 'handles' => ['critical', 'alert', 'emergency', 'debug',
|
||||
// 'error', 'info', 'notice', 'warning'],
|
||||
// ],
|
||||
|
||||
/*
|
||||
* The ErrorlogHandler writes the logs to PHP's native `error_log()` function.
|
||||
* Uncomment this block to use it.
|
||||
*/
|
||||
// 'CodeIgniter\Log\Handlers\ErrorlogHandler' => [
|
||||
// /* The log levels this handler can handle. */
|
||||
// 'handles' => ['critical', 'alert', 'emergency', 'debug', 'error', 'info', 'notice', 'warning'],
|
||||
//
|
||||
// /*
|
||||
// * The message type where the error should go. Can be 0 or 4, or use the
|
||||
// * class constants: `ErrorlogHandler::TYPE_OS` (0) or `ErrorlogHandler::TYPE_SAPI` (4)
|
||||
// */
|
||||
// 'messageType' => 0,
|
||||
// ],
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,9 +27,7 @@ class Migrations extends BaseConfig
|
|||
*
|
||||
* This is the name of the table that will store the current migrations state.
|
||||
* When migrations runs it will store in a database table which migration
|
||||
* level the system is at. It then compares the migration level in this
|
||||
* table to the $config['migration_version'] if they are not the same it
|
||||
* will migrate up. This must be set.
|
||||
* files have already been run.
|
||||
*/
|
||||
public string $table = 'migrations';
|
||||
|
||||
|
|
@ -48,4 +46,19 @@ class Migrations extends BaseConfig
|
|||
* - Y_m_d_His_
|
||||
*/
|
||||
public string $timestampFormat = 'Y-m-d-His_';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Enable/Disable Migration Lock
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Locking is disabled by default.
|
||||
*
|
||||
* When enabled, it will prevent multiple migration processes
|
||||
* from running at the same time by using a lock mechanism.
|
||||
*
|
||||
* This is useful in production environments to avoid conflicts
|
||||
* or race conditions during concurrent deployments.
|
||||
*/
|
||||
public bool $lock = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ declare(strict_types=1);
|
|||
namespace Config;
|
||||
|
||||
/**
|
||||
* Mimes
|
||||
*
|
||||
* This file contains an array of mime types. It is used by the Upload class to help identify allowed file types.
|
||||
*
|
||||
* When more than one variation for an extension exist (like jpg, jpeg, etc) the most common one should be first in the
|
||||
|
|
@ -15,13 +13,12 @@ namespace Config;
|
|||
* When working with mime types, please make sure you have the ´fileinfo´ extension enabled to reliably detect the
|
||||
* media types.
|
||||
*/
|
||||
|
||||
class Mimes
|
||||
{
|
||||
/**
|
||||
* Map of extensions to mime types.
|
||||
*
|
||||
* @var array<string, string|string[]>
|
||||
* @var array<string, list<string>|string>
|
||||
*/
|
||||
public static $mimes = [
|
||||
'hqx' => [
|
||||
|
|
@ -53,21 +50,24 @@ class Mimes
|
|||
'dms' => 'application/octet-stream',
|
||||
'lha' => 'application/octet-stream',
|
||||
'lzh' => 'application/octet-stream',
|
||||
'exe' => ['application/octet-stream', 'application/x-msdownload'],
|
||||
'exe' => ['application/octet-stream',
|
||||
'application/vnd.microsoft.portable-executable',
|
||||
'application/x-dosexec',
|
||||
'application/x-msdownload'],
|
||||
'class' => 'application/octet-stream',
|
||||
'psd' => ['application/x-photoshop', 'image/vnd.adobe.photoshop'],
|
||||
'so' => 'application/octet-stream',
|
||||
'sea' => 'application/octet-stream',
|
||||
'dll' => 'application/octet-stream',
|
||||
'oda' => 'application/oda',
|
||||
'pdf' => ['application/pdf', 'application/force-download', 'application/x-download'],
|
||||
'ai' => ['application/pdf', 'application/postscript'],
|
||||
'eps' => 'application/postscript',
|
||||
'ps' => 'application/postscript',
|
||||
'smi' => 'application/smil',
|
||||
'smil' => 'application/smil',
|
||||
'mif' => 'application/vnd.mif',
|
||||
'xls' => [
|
||||
'psd' => ['application/x-photoshop', 'image/vnd.adobe.photoshop'],
|
||||
'so' => 'application/octet-stream',
|
||||
'sea' => 'application/octet-stream',
|
||||
'dll' => 'application/octet-stream',
|
||||
'oda' => 'application/oda',
|
||||
'pdf' => ['application/pdf', 'application/force-download', 'application/x-download'],
|
||||
'ai' => ['application/pdf', 'application/postscript'],
|
||||
'eps' => 'application/postscript',
|
||||
'ps' => 'application/postscript',
|
||||
'smi' => 'application/smil',
|
||||
'smil' => 'application/smil',
|
||||
'mif' => 'application/vnd.mif',
|
||||
'xls' => [
|
||||
'application/vnd.ms-excel',
|
||||
'application/msexcel',
|
||||
'application/x-msexcel',
|
||||
|
|
@ -87,17 +87,17 @@ class Mimes
|
|||
'application/vnd.ms-office',
|
||||
'application/msword',
|
||||
],
|
||||
'pptx' => ['application/vnd.openxmlformats-officedocument.presentationml.presentation'],
|
||||
'pptx' => ['application/vnd.openxmlformats-officedocument.presentationml.presentation'],
|
||||
'wbxml' => 'application/wbxml',
|
||||
'wmlc' => 'application/wmlc',
|
||||
'dcr' => 'application/x-director',
|
||||
'dir' => 'application/x-director',
|
||||
'dxr' => 'application/x-director',
|
||||
'dvi' => 'application/x-dvi',
|
||||
'gtar' => 'application/x-gtar',
|
||||
'gz' => 'application/x-gzip',
|
||||
'gzip' => 'application/x-gzip',
|
||||
'php' => [
|
||||
'wmlc' => 'application/wmlc',
|
||||
'dcr' => 'application/x-director',
|
||||
'dir' => 'application/x-director',
|
||||
'dxr' => 'application/x-director',
|
||||
'dvi' => 'application/x-dvi',
|
||||
'gtar' => 'application/x-gtar',
|
||||
'gz' => 'application/x-gzip',
|
||||
'gzip' => 'application/x-gzip',
|
||||
'php' => [
|
||||
'application/x-php',
|
||||
'application/x-httpd-php',
|
||||
'application/php',
|
||||
|
|
@ -105,41 +105,41 @@ class Mimes
|
|||
'text/x-php',
|
||||
'application/x-httpd-php-source',
|
||||
],
|
||||
'php4' => 'application/x-httpd-php',
|
||||
'php3' => 'application/x-httpd-php',
|
||||
'php4' => 'application/x-httpd-php',
|
||||
'php3' => 'application/x-httpd-php',
|
||||
'phtml' => 'application/x-httpd-php',
|
||||
'phps' => 'application/x-httpd-php-source',
|
||||
'js' => ['application/x-javascript', 'text/plain'],
|
||||
'swf' => 'application/x-shockwave-flash',
|
||||
'sit' => 'application/x-stuffit',
|
||||
'tar' => 'application/x-tar',
|
||||
'tgz' => ['application/x-tar', 'application/x-gzip-compressed'],
|
||||
'z' => 'application/x-compress',
|
||||
'phps' => 'application/x-httpd-php-source',
|
||||
'js' => ['application/x-javascript', 'text/plain'],
|
||||
'swf' => 'application/x-shockwave-flash',
|
||||
'sit' => 'application/x-stuffit',
|
||||
'tar' => 'application/x-tar',
|
||||
'tgz' => ['application/x-tar', 'application/x-gzip-compressed'],
|
||||
'z' => 'application/x-compress',
|
||||
'xhtml' => 'application/xhtml+xml',
|
||||
'xht' => 'application/xhtml+xml',
|
||||
'zip' => [
|
||||
'xht' => 'application/xhtml+xml',
|
||||
'zip' => [
|
||||
'application/x-zip',
|
||||
'application/zip',
|
||||
'application/x-zip-compressed',
|
||||
'application/s-compressed',
|
||||
'multipart/x-zip',
|
||||
],
|
||||
'rar' => ['application/vnd.rar', 'application/x-rar', 'application/rar', 'application/x-rar-compressed'],
|
||||
'mid' => 'audio/midi',
|
||||
'rar' => ['application/vnd.rar', 'application/x-rar', 'application/rar', 'application/x-rar-compressed'],
|
||||
'mid' => 'audio/midi',
|
||||
'midi' => 'audio/midi',
|
||||
'mp3' => ['audio/mpeg', 'audio/mpg', 'audio/mpeg3', 'audio/mp3', 'application/octet-stream'],
|
||||
'mp3' => ['audio/mpeg', 'audio/mpg', 'audio/mpeg3', 'audio/mp3', 'application/octet-stream'],
|
||||
'mpga' => 'audio/mpeg',
|
||||
'mp2' => 'audio/mpeg',
|
||||
'aif' => ['audio/x-aiff', 'audio/aiff'],
|
||||
'mp2' => 'audio/mpeg',
|
||||
'aif' => ['audio/x-aiff', 'audio/aiff'],
|
||||
'aiff' => ['audio/x-aiff', 'audio/aiff'],
|
||||
'aifc' => 'audio/x-aiff',
|
||||
'ram' => 'audio/x-pn-realaudio',
|
||||
'rm' => 'audio/x-pn-realaudio',
|
||||
'rpm' => 'audio/x-pn-realaudio-plugin',
|
||||
'ra' => 'audio/x-realaudio',
|
||||
'rv' => 'video/vnd.rn-realvideo',
|
||||
'wav' => ['audio/x-wav', 'audio/wave', 'audio/wav'],
|
||||
'bmp' => [
|
||||
'ram' => 'audio/x-pn-realaudio',
|
||||
'rm' => 'audio/x-pn-realaudio',
|
||||
'rpm' => 'audio/x-pn-realaudio-plugin',
|
||||
'ra' => 'audio/x-realaudio',
|
||||
'rv' => 'video/vnd.rn-realvideo',
|
||||
'wav' => ['audio/x-wav', 'audio/wave', 'audio/wav'],
|
||||
'bmp' => [
|
||||
'image/bmp',
|
||||
'image/x-bmp',
|
||||
'image/x-bitmap',
|
||||
|
|
@ -152,48 +152,48 @@ class Mimes
|
|||
'application/x-bmp',
|
||||
'application/x-win-bitmap',
|
||||
],
|
||||
'gif' => 'image/gif',
|
||||
'jpg' => ['image/jpeg', 'image/pjpeg'],
|
||||
'jpeg' => ['image/jpeg', 'image/pjpeg'],
|
||||
'jpe' => ['image/jpeg', 'image/pjpeg'],
|
||||
'jp2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'j2k' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'jpf' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'jpg2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'jpx' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'jpm' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'mj2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'mjp2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'png' => ['image/png', 'image/x-png'],
|
||||
'webp' => 'image/webp',
|
||||
'tif' => 'image/tiff',
|
||||
'tiff' => 'image/tiff',
|
||||
'css' => ['text/css', 'text/plain'],
|
||||
'html' => ['text/html', 'text/plain'],
|
||||
'htm' => ['text/html', 'text/plain'],
|
||||
'gif' => 'image/gif',
|
||||
'jpg' => ['image/jpeg', 'image/pjpeg'],
|
||||
'jpeg' => ['image/jpeg', 'image/pjpeg'],
|
||||
'jpe' => ['image/jpeg', 'image/pjpeg'],
|
||||
'jp2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'j2k' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'jpf' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'jpg2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'jpx' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'jpm' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'mj2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'mjp2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||
'png' => ['image/png', 'image/x-png'],
|
||||
'webp' => 'image/webp',
|
||||
'tif' => 'image/tiff',
|
||||
'tiff' => 'image/tiff',
|
||||
'css' => ['text/css', 'text/plain'],
|
||||
'html' => ['text/html', 'text/plain'],
|
||||
'htm' => ['text/html', 'text/plain'],
|
||||
'shtml' => ['text/html', 'text/plain'],
|
||||
'txt' => 'text/plain',
|
||||
'text' => 'text/plain',
|
||||
'log' => ['text/plain', 'text/x-log'],
|
||||
'rtx' => 'text/richtext',
|
||||
'rtf' => 'text/rtf',
|
||||
'xml' => ['application/xml', 'text/xml', 'text/plain'],
|
||||
'xsl' => ['application/xml', 'text/xsl', 'text/xml'],
|
||||
'mpeg' => 'video/mpeg',
|
||||
'mpg' => 'video/mpeg',
|
||||
'mpe' => 'video/mpeg',
|
||||
'qt' => 'video/quicktime',
|
||||
'mov' => 'video/quicktime',
|
||||
'avi' => ['video/x-msvideo', 'video/msvideo', 'video/avi', 'application/x-troff-msvideo'],
|
||||
'txt' => 'text/plain',
|
||||
'text' => 'text/plain',
|
||||
'log' => ['text/plain', 'text/x-log'],
|
||||
'rtx' => 'text/richtext',
|
||||
'rtf' => 'text/rtf',
|
||||
'xml' => ['application/xml', 'text/xml', 'text/plain'],
|
||||
'xsl' => ['application/xml', 'text/xsl', 'text/xml'],
|
||||
'mpeg' => 'video/mpeg',
|
||||
'mpg' => 'video/mpeg',
|
||||
'mpe' => 'video/mpeg',
|
||||
'qt' => 'video/quicktime',
|
||||
'mov' => 'video/quicktime',
|
||||
'avi' => ['video/x-msvideo', 'video/msvideo', 'video/avi', 'application/x-troff-msvideo'],
|
||||
'movie' => 'video/x-sgi-movie',
|
||||
'doc' => ['application/msword', 'application/vnd.ms-office'],
|
||||
'docx' => [
|
||||
'doc' => ['application/msword', 'application/vnd.ms-office'],
|
||||
'docx' => [
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/zip',
|
||||
'application/msword',
|
||||
'application/x-zip',
|
||||
],
|
||||
'dot' => ['application/msword', 'application/vnd.ms-office'],
|
||||
'dot' => ['application/msword', 'application/vnd.ms-office'],
|
||||
'dotx' => [
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/zip',
|
||||
|
|
@ -209,49 +209,49 @@ class Mimes
|
|||
'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
|
||||
'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12',
|
||||
'word' => ['application/msword', 'application/octet-stream'],
|
||||
'xl' => 'application/excel',
|
||||
'eml' => 'message/rfc822',
|
||||
'json' => ['application/json', 'text/json'],
|
||||
'pem' => ['application/x-x509-user-cert', 'application/x-pem-file', 'application/octet-stream'],
|
||||
'p10' => ['application/x-pkcs10', 'application/pkcs10'],
|
||||
'p12' => 'application/x-pkcs12',
|
||||
'p7a' => 'application/x-pkcs7-signature',
|
||||
'p7c' => ['application/pkcs7-mime', 'application/x-pkcs7-mime'],
|
||||
'p7m' => ['application/pkcs7-mime', 'application/x-pkcs7-mime'],
|
||||
'p7r' => 'application/x-pkcs7-certreqresp',
|
||||
'p7s' => 'application/pkcs7-signature',
|
||||
'crt' => ['application/x-x509-ca-cert', 'application/x-x509-user-cert', 'application/pkix-cert'],
|
||||
'crl' => ['application/pkix-crl', 'application/pkcs-crl'],
|
||||
'der' => 'application/x-x509-ca-cert',
|
||||
'kdb' => 'application/octet-stream',
|
||||
'pgp' => 'application/pgp',
|
||||
'gpg' => 'application/gpg-keys',
|
||||
'sst' => 'application/octet-stream',
|
||||
'csr' => 'application/octet-stream',
|
||||
'rsa' => 'application/x-pkcs7',
|
||||
'cer' => ['application/pkix-cert', 'application/x-x509-ca-cert'],
|
||||
'3g2' => 'video/3gpp2',
|
||||
'3gp' => ['video/3gp', 'video/3gpp'],
|
||||
'mp4' => 'video/mp4',
|
||||
'm4a' => ['audio/m4a', 'audio/x-m4a', 'application/octet-stream'],
|
||||
'f4v' => ['video/mp4', 'video/x-f4v'],
|
||||
'flv' => 'video/x-flv',
|
||||
'xl' => 'application/excel',
|
||||
'eml' => 'message/rfc822',
|
||||
'json' => ['application/json', 'text/json', 'text/plain'],
|
||||
'pem' => ['application/x-x509-user-cert', 'application/x-pem-file', 'application/octet-stream'],
|
||||
'p10' => ['application/x-pkcs10', 'application/pkcs10'],
|
||||
'p12' => 'application/x-pkcs12',
|
||||
'p7a' => 'application/x-pkcs7-signature',
|
||||
'p7c' => ['application/pkcs7-mime', 'application/x-pkcs7-mime'],
|
||||
'p7m' => ['application/pkcs7-mime', 'application/x-pkcs7-mime'],
|
||||
'p7r' => 'application/x-pkcs7-certreqresp',
|
||||
'p7s' => 'application/pkcs7-signature',
|
||||
'crt' => ['application/x-x509-ca-cert', 'application/x-x509-user-cert', 'application/pkix-cert'],
|
||||
'crl' => ['application/pkix-crl', 'application/pkcs-crl'],
|
||||
'der' => 'application/x-x509-ca-cert',
|
||||
'kdb' => 'application/octet-stream',
|
||||
'pgp' => 'application/pgp',
|
||||
'gpg' => 'application/gpg-keys',
|
||||
'sst' => 'application/octet-stream',
|
||||
'csr' => 'application/octet-stream',
|
||||
'rsa' => 'application/x-pkcs7',
|
||||
'cer' => ['application/pkix-cert', 'application/x-x509-ca-cert'],
|
||||
'3g2' => 'video/3gpp2',
|
||||
'3gp' => ['video/3gp', 'video/3gpp'],
|
||||
'mp4' => 'video/mp4',
|
||||
'm4a' => ['audio/m4a', 'audio/x-m4a', 'application/octet-stream'],
|
||||
'f4v' => ['video/mp4', 'video/x-f4v'],
|
||||
'flv' => 'video/x-flv',
|
||||
'webm' => 'video/webm',
|
||||
'aac' => 'audio/x-acc',
|
||||
'm4u' => 'application/vnd.mpegurl',
|
||||
'm3u' => 'text/plain',
|
||||
'aac' => 'audio/x-acc',
|
||||
'm4u' => 'application/vnd.mpegurl',
|
||||
'm3u' => 'text/plain',
|
||||
'xspf' => 'application/xspf+xml',
|
||||
'vlc' => 'application/videolan',
|
||||
'wmv' => ['video/x-ms-wmv', 'video/x-ms-asf'],
|
||||
'au' => 'audio/x-au',
|
||||
'ac3' => 'audio/ac3',
|
||||
'vlc' => 'application/videolan',
|
||||
'wmv' => ['video/x-ms-wmv', 'video/x-ms-asf'],
|
||||
'au' => 'audio/x-au',
|
||||
'ac3' => 'audio/ac3',
|
||||
'flac' => 'audio/x-flac',
|
||||
'ogg' => ['audio/ogg', 'video/ogg', 'application/ogg'],
|
||||
'kmz' => ['application/vnd.google-earth.kmz', 'application/zip', 'application/x-zip'],
|
||||
'kml' => ['application/vnd.google-earth.kml+xml', 'application/xml', 'text/xml'],
|
||||
'ics' => 'text/calendar',
|
||||
'ogg' => ['audio/ogg', 'video/ogg', 'application/ogg'],
|
||||
'kmz' => ['application/vnd.google-earth.kmz', 'application/zip', 'application/x-zip'],
|
||||
'kml' => ['application/vnd.google-earth.kml+xml', 'application/xml', 'text/xml'],
|
||||
'ics' => 'text/calendar',
|
||||
'ical' => 'text/calendar',
|
||||
'zsh' => 'text/x-scriptzsh',
|
||||
'zsh' => 'text/x-scriptzsh',
|
||||
'7zip' => [
|
||||
'application/x-compressed',
|
||||
'application/x-zip-compressed',
|
||||
|
|
@ -276,10 +276,11 @@ class Mimes
|
|||
],
|
||||
'svg' => ['image/svg+xml', 'image/svg', 'application/xml', 'text/xml'],
|
||||
'vcf' => 'text/x-vcard',
|
||||
'srt' => ['text/srt', 'text/plain', 'application/octet-stream'],
|
||||
'srt' => ['application/x-subrip', 'text/srt', 'text/plain', 'application/octet-stream'],
|
||||
'vtt' => ['text/vtt', 'text/plain'],
|
||||
'ico' => ['image/x-icon', 'image/x-ico', 'image/vnd.microsoft.icon'],
|
||||
'stl' => ['application/sla', 'application/vnd.ms-pki.stl', 'application/x-navistyle'],
|
||||
'stl' => ['application/sla', 'application/vnd.ms-pki.stl', 'application/x-navistyle', 'model/stl',
|
||||
'application/octet-stream', ],
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -306,7 +307,7 @@ class Mimes
|
|||
* @param string|null $proposedExtension - default extension (in case there is more than one with the same mime type)
|
||||
* @return string|null The extension determined, or null if unable to match.
|
||||
*/
|
||||
public static function guessExtensionFromType(string $type, string $proposedExtension = null): ?string
|
||||
public static function guessExtensionFromType(string $type, ?string $proposedExtension = null): ?string
|
||||
{
|
||||
$type = trim(strtolower($type), '. ');
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,12 @@ namespace Config;
|
|||
|
||||
use CodeIgniter\Modules\Modules as BaseModules;
|
||||
|
||||
/**
|
||||
* Modules Configuration.
|
||||
*
|
||||
* NOTE: This class is required prior to Autoloader instantiation,
|
||||
* and does not extend BaseConfig.
|
||||
*/
|
||||
class Modules extends BaseModules
|
||||
{
|
||||
/**
|
||||
|
|
@ -33,6 +39,29 @@ class Modules extends BaseModules
|
|||
*/
|
||||
public $discoverInComposer = true;
|
||||
|
||||
/**
|
||||
* The Composer package list for Auto-Discovery
|
||||
* This setting is optional.
|
||||
*
|
||||
* E.g.:
|
||||
* [
|
||||
* 'only' => [
|
||||
* // List up all packages to auto-discover
|
||||
* 'codeigniter4/shield',
|
||||
* ],
|
||||
* ]
|
||||
* or
|
||||
* [
|
||||
* 'exclude' => [
|
||||
* // List up packages to exclude.
|
||||
* 'pestphp/pest',
|
||||
* ],
|
||||
* ]
|
||||
*
|
||||
* @var array{only?: list<string>, exclude?: list<string>}
|
||||
*/
|
||||
public $composerPackages = [];
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Auto-Discovery Rules
|
||||
|
|
@ -43,7 +72,7 @@ class Modules extends BaseModules
|
|||
*
|
||||
* If it is not listed, only the base application elements will be used.
|
||||
*
|
||||
* @var string[]
|
||||
* @var list<string>
|
||||
*/
|
||||
public $aliases = ['events', 'filters', 'registrars', 'routes', 'services'];
|
||||
}
|
||||
|
|
|
|||
34
app/Config/Optimize.php
Normal file
34
app/Config/Optimize.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Config;
|
||||
|
||||
/**
|
||||
* Optimization Configuration.
|
||||
*
|
||||
* NOTE: This class does not extend BaseConfig for performance reasons.
|
||||
* So you cannot replace the property values with Environment Variables.
|
||||
*
|
||||
* WARNING: Do not use these options when running the app in the Worker Mode.
|
||||
*/
|
||||
class Optimize
|
||||
{
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Config Caching
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* @see https://codeigniter.com/user_guide/concepts/factories.html#config-caching
|
||||
*/
|
||||
public bool $configCacheEnabled = false;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Config Caching
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* @see https://codeigniter.com/user_guide/concepts/autoloader.html#file-locator-caching
|
||||
*/
|
||||
public bool $locatorCacheEnabled = false;
|
||||
}
|
||||
|
|
@ -21,13 +21,11 @@ class Pager extends BaseConfig
|
|||
* and the desired group as $pagerGroup;
|
||||
*
|
||||
* @var array<string, string>
|
||||
*
|
||||
* @noRector Rector\Php55\Rector\String_\StringClassNameToClassConstantRector
|
||||
*/
|
||||
public $templates = [
|
||||
'default_full' => 'App\Views\pager\default_full',
|
||||
public array $templates = [
|
||||
'default_full' => 'App\Views\pager\default_full',
|
||||
'default_simple' => 'CodeIgniter\Pager\Views\default_simple',
|
||||
'default_head' => 'CodeIgniter\Pager\Views\default_head',
|
||||
'default_head' => 'CodeIgniter\Pager\Views\default_head',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -5,16 +5,16 @@ declare(strict_types=1);
|
|||
namespace Config;
|
||||
|
||||
/**
|
||||
* Paths
|
||||
*
|
||||
* Holds the paths that are used by the system to locate the main directories, app, system, etc.
|
||||
*
|
||||
* Modifying these allows you to restructure your application, share a system folder between multiple applications, and
|
||||
* more.
|
||||
*
|
||||
* All paths are relative to the project's root folder.
|
||||
*
|
||||
* NOTE: This class is required prior to Autoloader instantiation,
|
||||
* and does not extend BaseConfig.
|
||||
*/
|
||||
|
||||
class Paths
|
||||
{
|
||||
/**
|
||||
|
|
@ -72,7 +72,7 @@ class Paths
|
|||
* This variable must contain the name of the directory that
|
||||
* contains the view files used by your application. By
|
||||
* default this is in `app/Views`. This value
|
||||
* is used when no value is provided to `Services::renderer()`.
|
||||
* is used when no value is provided to `service('renderer')`.
|
||||
*/
|
||||
public string $viewDirectory = __DIR__ . '/../Views';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@ class Publisher extends BasePublisher
|
|||
* to directories not in this list will result in a PublisherException. Files that do no fit the pattern will cause
|
||||
* copy/merge to fail.
|
||||
*
|
||||
* @var array<string,string>
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public $restrictions = [
|
||||
ROOTPATH => '*',
|
||||
FCPATH => '#\.(s?css|js|map|html?|xml|json|webmanifest|ttf|eot|woff2?|gif|jpe?g|tiff?|png|webp|bmp|ico|svg)$#i',
|
||||
FCPATH => '#\.(s?css|js|map|html?|xml|json|webmanifest|ttf|eot|woff2?|gif|jpe?g|tiff?|png|webp|bmp|ico|svg)$#i',
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,44 +2,17 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Config;
|
||||
|
||||
// Create a new instance of our RouteCollection class.
|
||||
$routes = Services::routes();
|
||||
|
||||
// Load the system's routing file first, so that the app and ENVIRONMENT
|
||||
// can override as needed.
|
||||
if (is_file(SYSTEMPATH . 'Config/Routes.php')) {
|
||||
require SYSTEMPATH . 'Config/Routes.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------
|
||||
* Router Setup
|
||||
* --------------------------------------------------------------------
|
||||
*/
|
||||
$routes->setDefaultNamespace('App\Controllers');
|
||||
$routes->setDefaultController('Home');
|
||||
$routes->setDefaultMethod('index');
|
||||
$routes->setTranslateURIDashes(false);
|
||||
$routes->set404Override();
|
||||
|
||||
// The Auto Routing (Legacy) is very dangerous. It is easy to create vulnerable apps
|
||||
// where controller filters or CSRF protection are bypassed.
|
||||
// If you don't want to define all routes, please use the Auto Routing (Improved).
|
||||
// Set `$autoRoutesImproved` to true in `app/Config/Feature.php` and set the following to true.
|
||||
$routes->setAutoRoute(false);
|
||||
use CodeIgniter\Router\RouteCollection;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------
|
||||
* Placeholder definitions
|
||||
* --------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/** @var RouteCollection $routes */
|
||||
$routes->addPlaceholder('podcastHandle', '[a-zA-Z0-9\_]{1,32}');
|
||||
$routes->addPlaceholder('slug', '[a-zA-Z0-9\-]{1,128}');
|
||||
$routes->addPlaceholder('base64', '[A-Za-z0-9\.\_]+\-{0,2}');
|
||||
$routes->addPlaceholder('platformType', '\bpodcasting|\bsocial|\bfunding');
|
||||
$routes->addPlaceholder('postAction', '\bfavourite|\breblog|\breply');
|
||||
$routes->addPlaceholder('embedTheme', '\blight|\bdark|\blight-transparent|\bdark-transparent');
|
||||
$routes->addPlaceholder(
|
||||
|
|
@ -60,6 +33,11 @@ $routes->get('themes/colors', 'ColorsController', [
|
|||
'as' => 'themes-colors-css',
|
||||
]);
|
||||
|
||||
// health check
|
||||
$routes->get('/health', 'HomeController::health', [
|
||||
'as' => 'health',
|
||||
]);
|
||||
|
||||
// We get a performance increase by specifying the default
|
||||
// route since we don't have to scan directories.
|
||||
$routes->get('/', 'HomeController', [
|
||||
|
|
@ -68,48 +46,54 @@ $routes->get('/', 'HomeController', [
|
|||
|
||||
$routes->get('.well-known/platforms', 'Platform');
|
||||
|
||||
service('auth')
|
||||
->routes($routes);
|
||||
|
||||
// Podcast's Public routes
|
||||
$routes->group('@(:podcastHandle)', static function ($routes): void {
|
||||
// override default Fediverse Library's actor route
|
||||
$routes->options('/', 'ActivityPubController::preflight');
|
||||
$routes->get('/', 'PodcastController::activity/$1', [
|
||||
'as' => 'podcast-activity',
|
||||
'as' => 'podcast-activity',
|
||||
'alternate-content' => [
|
||||
'application/activity+json' => [
|
||||
'namespace' => 'Modules\Fediverse\Controllers',
|
||||
'controller-method' => 'ActorController::index/$1',
|
||||
],
|
||||
'application/podcast-activity+json' => [
|
||||
'namespace' => 'App\Controllers',
|
||||
'controller-method' => 'PodcastController::podcastActor/$1',
|
||||
],
|
||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
|
||||
'namespace' => 'Modules\Fediverse\Controllers',
|
||||
'controller-method' => 'ActorController::index/$1',
|
||||
],
|
||||
],
|
||||
'filter' => 'allow-cors',
|
||||
]);
|
||||
$routes->get('manifest.webmanifest', 'WebmanifestController::podcastManifest/$1', [
|
||||
'as' => 'podcast-webmanifest',
|
||||
]);
|
||||
// override default Fediverse Library's actor route
|
||||
$routes->options('/', 'ActivityPubController::preflight');
|
||||
$routes->get('/', 'PodcastController::activity/$1', [
|
||||
'as' => 'actor',
|
||||
'alternate-content' => [
|
||||
'application/activity+json' => [
|
||||
'namespace' => 'Modules\Fediverse\Controllers',
|
||||
'controller-method' => 'ActorController/$1',
|
||||
],
|
||||
'application/podcast-activity+json' => [
|
||||
'namespace' => 'App\Controllers',
|
||||
'controller-method' => 'PodcastController::podcastActor/$1',
|
||||
],
|
||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
|
||||
'namespace' => 'Modules\Fediverse\Controllers',
|
||||
'controller-method' => 'ActorController/$1',
|
||||
],
|
||||
],
|
||||
'filter' => 'allow-cors',
|
||||
$routes->get('links', 'PodcastController::links/$1', [
|
||||
'as' => 'podcast-links',
|
||||
]);
|
||||
$routes->get('about', 'PodcastController::about/$1', [
|
||||
'as' => 'podcast-about',
|
||||
]);
|
||||
$routes->options('episodes', 'ActivityPubController::preflight');
|
||||
$routes->get('episodes', 'PodcastController::episodes/$1', [
|
||||
'as' => 'podcast-episodes',
|
||||
'as' => 'podcast-episodes',
|
||||
'alternate-content' => [
|
||||
'application/activity+json' => [
|
||||
'namespace' => 'App\Controllers',
|
||||
'controller-method' => 'PodcastController::episodeCollection/$1',
|
||||
],
|
||||
'application/podcast-activity+json' => [
|
||||
'namespace' => 'App\Controllers',
|
||||
'controller-method' => 'PodcastController::episodeCollection/$1',
|
||||
],
|
||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
|
||||
'namespace' => 'App\Controllers',
|
||||
'controller-method' => 'PodcastController::episodeCollection/$1',
|
||||
],
|
||||
],
|
||||
|
|
@ -117,16 +101,19 @@ $routes->group('@(:podcastHandle)', static function ($routes): void {
|
|||
]);
|
||||
$routes->group('episodes/(:slug)', static function ($routes): void {
|
||||
$routes->options('/', 'ActivityPubController::preflight');
|
||||
$routes->get('/', 'EpisodeController/$1/$2', [
|
||||
'as' => 'episode',
|
||||
$routes->get('/', 'EpisodeController::index/$1/$2', [
|
||||
'as' => 'episode',
|
||||
'alternate-content' => [
|
||||
'application/activity+json' => [
|
||||
'namespace' => 'App\Controllers',
|
||||
'controller-method' => 'EpisodeController::episodeObject/$1/$2',
|
||||
],
|
||||
'application/podcast-activity+json' => [
|
||||
'namespace' => 'App\Controllers',
|
||||
'controller-method' => 'EpisodeController::episodeObject/$1/$2',
|
||||
],
|
||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
|
||||
'namespace' => 'App\Controllers',
|
||||
'controller-method' => 'EpisodeController::episodeObject/$1/$2',
|
||||
],
|
||||
],
|
||||
|
|
@ -135,9 +122,15 @@ $routes->group('@(:podcastHandle)', static function ($routes): void {
|
|||
$routes->get('activity', 'EpisodeController::activity/$1/$2', [
|
||||
'as' => 'episode-activity',
|
||||
]);
|
||||
$routes->get('chapters', 'EpisodeController::chapters/$1/$2', [
|
||||
'as' => 'episode-chapters',
|
||||
]);
|
||||
$routes->get('transcript', 'EpisodeController::transcript/$1/$2', [
|
||||
'as' => 'episode-transcript',
|
||||
]);
|
||||
$routes->options('comments', 'ActivityPubController::preflight');
|
||||
$routes->get('comments', 'EpisodeController::comments/$1/$2', [
|
||||
'as' => 'episode-comments',
|
||||
'as' => 'episode-comments',
|
||||
'application/activity+json' => [
|
||||
'controller-method' => 'EpisodeController::comments/$1/$2',
|
||||
],
|
||||
|
|
@ -151,7 +144,7 @@ $routes->group('@(:podcastHandle)', static function ($routes): void {
|
|||
]);
|
||||
$routes->options('comments/(:uuid)', 'ActivityPubController::preflight');
|
||||
$routes->get('comments/(:uuid)', 'EpisodeCommentController::view/$1/$2/$3', [
|
||||
'as' => 'episode-comment',
|
||||
'as' => 'episode-comment',
|
||||
'application/activity+json' => [
|
||||
'controller-method' => 'EpisodeController::commentObject/$1/$2',
|
||||
],
|
||||
|
|
@ -166,7 +159,7 @@ $routes->group('@(:podcastHandle)', static function ($routes): void {
|
|||
$routes->get('comments/(:uuid)/replies', 'EpisodeCommentController::replies/$1/$2/$3', [
|
||||
'as' => 'episode-comment-replies',
|
||||
]);
|
||||
$routes->post('comments/(:uuid)/like', 'EpisodeCommentController::attemptLike/$1/$2/$3', [
|
||||
$routes->post('comments/(:uuid)/like', 'EpisodeCommentController::likeAction/$1/$2/$3', [
|
||||
'as' => 'episode-comment-attempt-like',
|
||||
]);
|
||||
$routes->get('oembed.json', 'EpisodeController::oembedJSON/$1/$2', [
|
||||
|
|
@ -184,16 +177,38 @@ $routes->group('@(:podcastHandle)', static function ($routes): void {
|
|||
],);
|
||||
});
|
||||
});
|
||||
$routes->head('feed.xml', 'FeedController/$1', [
|
||||
$routes->head('feed.xml', 'FeedController::index/$1', [
|
||||
'as' => 'podcast-rss-feed',
|
||||
]);
|
||||
$routes->get('feed.xml', 'FeedController/$1', [
|
||||
$routes->get('feed.xml', 'FeedController::index/$1', [
|
||||
'as' => 'podcast-rss-feed',
|
||||
]);
|
||||
$routes->head('feed', 'FeedController/$1');
|
||||
$routes->get('feed', 'FeedController/$1');
|
||||
$routes->head('feed', 'FeedController::index/$1');
|
||||
$routes->get('feed', 'FeedController::index/$1');
|
||||
});
|
||||
|
||||
// audio routes
|
||||
$routes->head('/audio/@(:podcastHandle)/(:slug).(:alphanum)', 'EpisodeAudioController::index/$1/$2', [
|
||||
'as' => 'episode-audio',
|
||||
], );
|
||||
$routes->get('/audio/@(:podcastHandle)/(:slug).(:alphanum)', 'EpisodeAudioController::index/$1/$2', [
|
||||
'as' => 'episode-audio',
|
||||
], );
|
||||
|
||||
// episode preview link
|
||||
$routes->get('/p/(:uuid)', 'EpisodePreviewController::index/$1', [
|
||||
'as' => 'episode-preview',
|
||||
]);
|
||||
$routes->get('/p/(:uuid)/activity', 'EpisodePreviewController::activity/$1', [
|
||||
'as' => 'episode-preview-activity',
|
||||
]);
|
||||
$routes->get('/p/(:uuid)/chapters', 'EpisodePreviewController::chapters/$1', [
|
||||
'as' => 'episode-preview-chapters',
|
||||
]);
|
||||
$routes->get('/p/(:uuid)/transcript', 'EpisodePreviewController::transcript/$1', [
|
||||
'as' => 'episode-preview-transcript',
|
||||
]);
|
||||
|
||||
// Other pages
|
||||
$routes->get('/credits', 'CreditsController', [
|
||||
'as' => 'credits',
|
||||
|
|
@ -204,7 +219,7 @@ $routes->get('/map', 'MapController', [
|
|||
$routes->get('/episodes-markers', 'MapController::getEpisodesMarkers', [
|
||||
'as' => 'episodes-markers',
|
||||
]);
|
||||
$routes->get('/pages/(:slug)', 'PageController/$1', [
|
||||
$routes->get('/pages/(:slug)', 'PageController::index/$1', [
|
||||
'as' => 'page',
|
||||
]);
|
||||
|
||||
|
|
@ -212,97 +227,80 @@ $routes->get('/pages/(:slug)', 'PageController/$1', [
|
|||
* Overwriting Fediverse routes file
|
||||
*/
|
||||
$routes->group('@(:podcastHandle)', static function ($routes): void {
|
||||
$routes->post('posts/new', 'PostController::attemptCreate/$1', [
|
||||
'as' => 'post-attempt-create',
|
||||
'filter' => 'permission:podcast-manage_publications',
|
||||
$routes->post('posts/new', 'PostController::createAction/$1', [
|
||||
'as' => 'post-attempt-create',
|
||||
'filter' => 'permission:podcast$1.manage-publications',
|
||||
]);
|
||||
// Post
|
||||
$routes->group('posts/(:uuid)', static function ($routes): void {
|
||||
$routes->options('/', 'ActivityPubController::preflight');
|
||||
$routes->get('/', 'PostController::view/$1/$2', [
|
||||
'as' => 'post',
|
||||
'as' => 'post',
|
||||
'alternate-content' => [
|
||||
'application/activity+json' => [
|
||||
'namespace' => 'Modules\Fediverse\Controllers',
|
||||
'controller-method' => 'PostController/$2',
|
||||
'namespace' => 'Modules\Fediverse\Controllers',
|
||||
'controller-method' => 'PostController::index/$2',
|
||||
],
|
||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
|
||||
'namespace' => 'Modules\Fediverse\Controllers',
|
||||
'controller-method' => 'PostController/$2',
|
||||
'namespace' => 'Modules\Fediverse\Controllers',
|
||||
'controller-method' => 'PostController::index/$2',
|
||||
],
|
||||
],
|
||||
'filter' => 'allow-cors',
|
||||
]);
|
||||
$routes->options('replies', 'ActivityPubController::preflight');
|
||||
$routes->get('replies', 'PostController/$1/$2', [
|
||||
'as' => 'post-replies',
|
||||
$routes->get('replies', 'PostController::index/$1/$2', [
|
||||
'as' => 'post-replies',
|
||||
'alternate-content' => [
|
||||
'application/activity+json' => [
|
||||
'namespace' => 'Modules\Fediverse\Controllers',
|
||||
'namespace' => 'Modules\Fediverse\Controllers',
|
||||
'controller-method' => 'PostController::replies/$2',
|
||||
],
|
||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
|
||||
'namespace' => 'Modules\Fediverse\Controllers',
|
||||
'namespace' => 'Modules\Fediverse\Controllers',
|
||||
'controller-method' => 'PostController::replies/$2',
|
||||
],
|
||||
],
|
||||
'filter' => 'allow-cors',
|
||||
]);
|
||||
// Actions
|
||||
$routes->post('action', 'PostController::attemptAction/$1/$2', [
|
||||
'as' => 'post-attempt-action',
|
||||
'filter' => 'permission:podcast-interact_as',
|
||||
$routes->post('action', 'PostController::action/$1/$2', [
|
||||
'as' => 'post-attempt-action',
|
||||
'filter' => 'permission:podcast$1.interact-as',
|
||||
]);
|
||||
$routes->post(
|
||||
'block-actor',
|
||||
'PostController::attemptBlockActor/$1/$2',
|
||||
'PostController::blockActorAction/$1/$2',
|
||||
[
|
||||
'as' => 'post-attempt-block-actor',
|
||||
'filter' => 'permission:fediverse-block_actors',
|
||||
'as' => 'post-attempt-block-actor',
|
||||
'filter' => 'permission:fediverse.manage-blocks',
|
||||
],
|
||||
);
|
||||
$routes->post(
|
||||
'block-domain',
|
||||
'PostController::attemptBlockDomain/$1/$2',
|
||||
'PostController::blockDomainAction/$1/$2',
|
||||
[
|
||||
'as' => 'post-attempt-block-domain',
|
||||
'filter' => 'permission:fediverse-block_domains',
|
||||
'as' => 'post-attempt-block-domain',
|
||||
'filter' => 'permission:fediverse.manage-blocks',
|
||||
],
|
||||
);
|
||||
$routes->post('delete', 'PostController::attemptDelete/$1/$2', [
|
||||
'as' => 'post-attempt-delete',
|
||||
'filter' => 'permission:podcast-manage_publications',
|
||||
$routes->post('delete', 'PostController::deleteAction/$1/$2', [
|
||||
'as' => 'post-attempt-delete',
|
||||
'filter' => 'permission:podcast$1.manage-publications',
|
||||
]);
|
||||
$routes->get(
|
||||
'remote/(:postAction)',
|
||||
'PostController::remoteAction/$1/$2/$3',
|
||||
'PostController::remoteActionAction/$1/$2/$3',
|
||||
[
|
||||
'as' => 'post-remote-action',
|
||||
],
|
||||
);
|
||||
});
|
||||
$routes->get('follow', 'ActorController::follow/$1', [
|
||||
$routes->get('follow', 'ActorController::followView/$1', [
|
||||
'as' => 'follow',
|
||||
]);
|
||||
$routes->get('outbox', 'ActorController::outbox/$1', [
|
||||
'as' => 'outbox',
|
||||
'as' => 'outbox',
|
||||
'filter' => 'fediverse:verify-activitystream',
|
||||
]);
|
||||
});
|
||||
|
||||
/*
|
||||
* --------------------------------------------------------------------
|
||||
* Additional Routing
|
||||
* --------------------------------------------------------------------
|
||||
*
|
||||
* There will often be times that you need additional routing and you
|
||||
* need it to be able to override any defaults in this file. Environment
|
||||
* based routes is one such time. require() additional route files here
|
||||
* to make that happen.
|
||||
*
|
||||
* You will have access to the $routes object within that file without
|
||||
* needing to reload it.
|
||||
*/
|
||||
if (is_file(APPPATH . 'Config/' . ENVIRONMENT . '/Routes.php')) {
|
||||
require APPPATH . 'Config/' . ENVIRONMENT . '/Routes.php';
|
||||
}
|
||||
|
|
|
|||
151
app/Config/Routing.php
Normal file
151
app/Config/Routing.php
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Config;
|
||||
|
||||
use CodeIgniter\Config\Routing as BaseRouting;
|
||||
|
||||
/**
|
||||
* Routing configuration
|
||||
*/
|
||||
class Routing extends BaseRouting
|
||||
{
|
||||
/**
|
||||
* For Defined Routes.
|
||||
* An array of files that contain route definitions.
|
||||
* Route files are read in order, with the first match
|
||||
* found taking precedence.
|
||||
*
|
||||
* Default: APPPATH . 'Config/Routes.php'
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
public array $routeFiles = [
|
||||
APPPATH . 'Config/Routes.php',
|
||||
ROOTPATH . 'modules/Admin/Config/Routes.php',
|
||||
ROOTPATH . 'modules/Analytics/Config/Routes.php',
|
||||
ROOTPATH . 'modules/Api/Rest/V1/Config/Routes.php',
|
||||
ROOTPATH . 'modules/Auth/Config/Routes.php',
|
||||
ROOTPATH . 'modules/Fediverse/Config/Routes.php',
|
||||
ROOTPATH . 'modules/Install/Config/Routes.php',
|
||||
ROOTPATH . 'modules/Platforms/Config/Routes.php',
|
||||
ROOTPATH . 'modules/PodcastImport/Config/Routes.php',
|
||||
ROOTPATH . 'modules/PremiumPodcasts/Config/Routes.php',
|
||||
];
|
||||
|
||||
/**
|
||||
* For Defined Routes and Auto Routing.
|
||||
* The default namespace to use for Controllers when no other
|
||||
* namespace has been specified.
|
||||
*
|
||||
* Default: 'App\Controllers'
|
||||
*/
|
||||
public string $defaultNamespace = 'App\Controllers';
|
||||
|
||||
/**
|
||||
* For Auto Routing.
|
||||
* The default controller to use when no other controller has been
|
||||
* specified.
|
||||
*
|
||||
* Default: 'Home'
|
||||
*/
|
||||
public string $defaultController = 'HomeController';
|
||||
|
||||
/**
|
||||
* For Defined Routes and Auto Routing.
|
||||
* The default method to call on the controller when no other
|
||||
* method has been set in the route.
|
||||
*
|
||||
* Default: 'index'
|
||||
*/
|
||||
public string $defaultMethod = 'index';
|
||||
|
||||
/**
|
||||
* For Auto Routing.
|
||||
* Whether to translate dashes in URIs for controller/method to underscores.
|
||||
* Primarily useful when using the auto-routing.
|
||||
*
|
||||
* Default: false
|
||||
*/
|
||||
public bool $translateURIDashes = false;
|
||||
|
||||
/**
|
||||
* Sets the class/method that should be called if routing doesn't
|
||||
* find a match. It can be the controller/method name like: Users::index
|
||||
*
|
||||
* This setting is passed to the Router class and handled there.
|
||||
*
|
||||
* If you want to use a closure, you will have to set it in the
|
||||
* routes file by calling:
|
||||
*
|
||||
* $routes->set404Override(function() {
|
||||
* // Do something here
|
||||
* });
|
||||
*
|
||||
* Example:
|
||||
* public $override404 = 'App\Errors::show404';
|
||||
*/
|
||||
public ?string $override404 = null;
|
||||
|
||||
/**
|
||||
* If TRUE, the system will attempt to match the URI against
|
||||
* Controllers by matching each segment against folders/files
|
||||
* in APPPATH/Controllers, when a match wasn't found against
|
||||
* defined routes.
|
||||
*
|
||||
* If FALSE, will stop searching and do NO automatic routing.
|
||||
*/
|
||||
public bool $autoRoute = false;
|
||||
|
||||
/**
|
||||
* If TRUE, the system will look for attributes on controller
|
||||
* class and methods that can run before and after the
|
||||
* controller/method.
|
||||
*
|
||||
* If FALSE, will ignore any attributes.
|
||||
*/
|
||||
public bool $useControllerAttributes = true;
|
||||
|
||||
/**
|
||||
* For Defined Routes.
|
||||
* If TRUE, will enable the use of the 'prioritize' option
|
||||
* when defining routes.
|
||||
*
|
||||
* Default: false
|
||||
*/
|
||||
public bool $prioritize = false;
|
||||
|
||||
/**
|
||||
* For Defined Routes.
|
||||
* If TRUE, matched multiple URI segments will be passed as one parameter.
|
||||
*
|
||||
* Default: false
|
||||
*/
|
||||
public bool $multipleSegmentsOneParam = false;
|
||||
|
||||
/**
|
||||
* For Auto Routing (Improved).
|
||||
* Map of URI segments and namespaces.
|
||||
*
|
||||
* The key is the first URI segment. The value is the controller namespace.
|
||||
* E.g.,
|
||||
* [
|
||||
* 'blog' => 'Acme\Blog\Controllers',
|
||||
* ]
|
||||
*
|
||||
* @var array<string, string> [ uri_segment => namespace ]
|
||||
*/
|
||||
public array $moduleRoutes = [];
|
||||
|
||||
/**
|
||||
* For Auto Routing (Improved).
|
||||
* Whether to translate dashes in URIs for controller/method to CamelCase.
|
||||
* E.g., blog-controller -> BlogController
|
||||
*
|
||||
* If you enable this, $translateURIDashes is ignored.
|
||||
*
|
||||
* Default: false
|
||||
*/
|
||||
public bool $translateUriToCamelCase = true;
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ class Security extends BaseConfig
|
|||
*
|
||||
* @var 'cookie'|'session'
|
||||
*/
|
||||
public string $csrfProtection = 'cookie';
|
||||
public string $csrfProtection = 'session';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
|
|
@ -80,26 +80,7 @@ class Security extends BaseConfig
|
|||
* CSRF Redirect
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Redirect to previous page with error on failure.
|
||||
* @see https://codeigniter4.github.io/userguide/libraries/security.html#redirection-on-failure
|
||||
*/
|
||||
public bool $redirect = true;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* CSRF SameSite
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Setting for CSRF SameSite cookie token.
|
||||
*
|
||||
* Allowed values are: None - Lax - Strict - ''.
|
||||
*
|
||||
* Defaults to `Lax` as recommended in this link:
|
||||
*
|
||||
* @see https://portswigger.net/web-security/csrf/samesite-cookies
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @deprecated `Config\Cookie` $samesite property is used.
|
||||
*/
|
||||
public $samesite = 'Lax';
|
||||
public bool $redirect = (ENVIRONMENT === 'production');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,15 @@ declare(strict_types=1);
|
|||
namespace Config;
|
||||
|
||||
use App\Libraries\Breadcrumb;
|
||||
use App\Libraries\HtmlHead;
|
||||
use App\Libraries\Negotiate;
|
||||
use App\Libraries\Router;
|
||||
use CodeIgniter\Config\BaseService;
|
||||
use CodeIgniter\HTTP\Negotiate as CodeIgniterHTTPNegotiate;
|
||||
use CodeIgniter\HTTP\Request;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\Router\RouteCollectionInterface;
|
||||
use CodeIgniter\Router\Router as CodeIgniterRouter;
|
||||
|
||||
/**
|
||||
* Services Configuration file.
|
||||
|
|
@ -27,20 +30,18 @@ class Services extends BaseService
|
|||
/**
|
||||
* The Router class uses a RouteCollection's array of routes, and determines the correct Controller and Method to
|
||||
* execute.
|
||||
*
|
||||
* @noRector PHPStan\Reflection\MissingMethodFromReflectionException
|
||||
*/
|
||||
public static function router(
|
||||
?RouteCollectionInterface $routes = null,
|
||||
?Request $request = null,
|
||||
bool $getShared = true
|
||||
): Router {
|
||||
bool $getShared = true,
|
||||
): CodeIgniterRouter {
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('router', $routes, $request);
|
||||
}
|
||||
|
||||
$routes = $routes ?? static::routes();
|
||||
$request = $request ?? static::request();
|
||||
$routes ??= static::routes();
|
||||
$request ??= static::request();
|
||||
|
||||
return new Router($routes, $request);
|
||||
}
|
||||
|
|
@ -48,16 +49,16 @@ class Services extends BaseService
|
|||
/**
|
||||
* The Negotiate class provides the content negotiation features for working the request to determine correct
|
||||
* language, encoding, charset, and more.
|
||||
*
|
||||
* @noRector PHPStan\Reflection\MissingMethodFromReflectionException
|
||||
*/
|
||||
public static function negotiator(?RequestInterface $request = null, bool $getShared = true): Negotiate
|
||||
{
|
||||
public static function negotiator(
|
||||
?RequestInterface $request = null,
|
||||
bool $getShared = true,
|
||||
): CodeIgniterHTTPNegotiate {
|
||||
if ($getShared) {
|
||||
return static::getSharedInstance('negotiator', $request);
|
||||
}
|
||||
|
||||
$request = $request ?? static::request();
|
||||
$request ??= static::request();
|
||||
|
||||
return new Negotiate($request);
|
||||
}
|
||||
|
|
@ -70,4 +71,13 @@ class Services extends BaseService
|
|||
|
||||
return new Breadcrumb();
|
||||
}
|
||||
|
||||
public static function html_head(bool $getShared = true): HtmlHead
|
||||
{
|
||||
if ($getShared) {
|
||||
return self::getSharedInstance('html_head');
|
||||
}
|
||||
|
||||
return new HtmlHead();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
130
app/Config/Session.php
Normal file
130
app/Config/Session.php
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Config;
|
||||
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
use CodeIgniter\Session\Handlers\BaseHandler;
|
||||
use CodeIgniter\Session\Handlers\FileHandler;
|
||||
|
||||
class Session extends BaseConfig
|
||||
{
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Session Driver
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* The session storage driver to use:
|
||||
* - `CodeIgniter\Session\Handlers\ArrayHandler` (for testing)
|
||||
* - `CodeIgniter\Session\Handlers\FileHandler`
|
||||
* - `CodeIgniter\Session\Handlers\DatabaseHandler`
|
||||
* - `CodeIgniter\Session\Handlers\MemcachedHandler`
|
||||
* - `CodeIgniter\Session\Handlers\RedisHandler`
|
||||
*
|
||||
* @var class-string<BaseHandler>
|
||||
*/
|
||||
public string $driver = FileHandler::class;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Session Cookie Name
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* The session cookie name, must contain only [0-9a-z_-] characters
|
||||
*/
|
||||
public string $cookieName = 'ci_session';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Session Expiration
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* The number of SECONDS you want the session to last.
|
||||
* Setting to 0 (zero) means expire when the browser is closed.
|
||||
*/
|
||||
public int $expiration = 7200;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Session Save Path
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* The location to save sessions to and is driver dependent.
|
||||
*
|
||||
* For the 'files' driver, it's a path to a writable directory.
|
||||
* WARNING: Only absolute paths are supported!
|
||||
*
|
||||
* For the 'database' driver, it's a table name.
|
||||
* Please read up the manual for the format with other session drivers.
|
||||
*
|
||||
* IMPORTANT: You are REQUIRED to set a valid save path!
|
||||
*/
|
||||
public string $savePath = WRITEPATH . 'session';
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Session Match IP
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Whether to match the user's IP address when reading the session data.
|
||||
*
|
||||
* WARNING: If you're using the database driver, don't forget to update
|
||||
* your session table's PRIMARY KEY when changing this setting.
|
||||
*/
|
||||
public bool $matchIP = false;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Session Time to Update
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* How many seconds between CI regenerating the session ID.
|
||||
*/
|
||||
public int $timeToUpdate = 300;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Session Regenerate Destroy
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Whether to destroy session data associated with the old session ID
|
||||
* when auto-regenerating the session ID. When set to FALSE, the data
|
||||
* will be later deleted by the garbage collector.
|
||||
*/
|
||||
public bool $regenerateDestroy = false;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Session Database Group
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* DB Group for the database session.
|
||||
*/
|
||||
public ?string $DBGroup = null;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Lock Retry Interval (microseconds)
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* This is used for RedisHandler.
|
||||
*
|
||||
* Time (microseconds) to wait if lock cannot be acquired.
|
||||
* The default is 100,000 microseconds (= 0.1 seconds).
|
||||
*/
|
||||
public int $lockRetryInterval = 100_000;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Lock Max Retries
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* This is used for RedisHandler.
|
||||
*
|
||||
* Maximum number of lock acquisition attempts.
|
||||
* The default is 300 times. That is lock timeout is about 30 (0.1 * 300)
|
||||
* seconds.
|
||||
*/
|
||||
public int $lockMaxRetries = 300;
|
||||
}
|
||||
59
app/Config/Tasks.php
Normal file
59
app/Config/Tasks.php
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Config;
|
||||
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
use CodeIgniter\Tasks\Scheduler;
|
||||
|
||||
class Tasks extends BaseConfig
|
||||
{
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Should performance metrics be logged
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* If true, will log the time it takes for each task to run.
|
||||
* Requires the settings table to have been created previously.
|
||||
*/
|
||||
public bool $logPerformance = false;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Maximum performance logs
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* The maximum number of logs that should be saved per Task.
|
||||
* Lower numbers reduced the amount of database required to
|
||||
* store the logs.
|
||||
*/
|
||||
public int $maxLogsPerTask = 10;
|
||||
|
||||
/**
|
||||
* Register any tasks within this method for the application.
|
||||
* Called by the TaskRunner.
|
||||
*/
|
||||
public function init(Scheduler $schedule): void
|
||||
{
|
||||
$schedule->command('fediverse:broadcast')
|
||||
->everyMinute()
|
||||
->named('fediverse-broadcast');
|
||||
|
||||
$schedule->command('websub:publish')
|
||||
->everyMinute()
|
||||
->named('websub-publish');
|
||||
|
||||
$schedule->command('video-clips:generate')
|
||||
->everyMinute()
|
||||
->named('video-clips-generate');
|
||||
|
||||
$schedule->command('podcast:import')
|
||||
->everyMinute()
|
||||
->named('podcast-import');
|
||||
|
||||
$schedule->command('episodes:compute-downloads')
|
||||
->everyHour()
|
||||
->named('episodes:compute-downloads');
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ class Toolbar extends BaseConfig
|
|||
* List of toolbar collectors that will be called when Debug Toolbar
|
||||
* fires up and collects data from.
|
||||
*
|
||||
* @var string[]
|
||||
* @var list<class-string>
|
||||
*/
|
||||
public array $collectors = [
|
||||
Timers::class,
|
||||
|
|
@ -51,7 +51,7 @@ class Toolbar extends BaseConfig
|
|||
* Collect Var Data
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* If set to false var data from the views will not be colleted. Useful to
|
||||
* If set to false var data from the views will not be collected. Useful to
|
||||
* avoid high memory usage when there are lots of data passed to the view.
|
||||
*/
|
||||
public bool $collectVarData = true;
|
||||
|
|
@ -90,4 +90,56 @@ class Toolbar extends BaseConfig
|
|||
* `$maxQueries` defines the maximum amount of queries that will be stored.
|
||||
*/
|
||||
public int $maxQueries = 100;
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Watched Directories
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Contains an array of directories that will be watched for changes and
|
||||
* used to determine if the hot-reload feature should reload the page or not.
|
||||
* We restrict the values to keep performance as high as possible.
|
||||
*
|
||||
* NOTE: The ROOTPATH will be prepended to all values.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
public array $watchedDirectories = ['app', 'modules', 'themes'];
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Watched File Extensions
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* Contains an array of file extensions that will be watched for changes and
|
||||
* used to determine if the hot-reload feature should reload the page or not.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
public array $watchedExtensions = ['php', 'css', 'js', 'html', 'svg', 'json', 'env'];
|
||||
|
||||
/**
|
||||
* --------------------------------------------------------------------------
|
||||
* Ignored HTTP Headers
|
||||
* --------------------------------------------------------------------------
|
||||
*
|
||||
* CodeIgniter Debug Toolbar normally injects HTML and JavaScript into every
|
||||
* HTML response. This is correct for full page loads, but it breaks requests
|
||||
* that expect only a clean HTML fragment.
|
||||
*
|
||||
* Libraries like HTMX, Unpoly, and Hotwire (Turbo) update parts of the page or
|
||||
* manage navigation on the client side. Injecting the Debug Toolbar into their
|
||||
* responses can cause invalid HTML, duplicated scripts, or JavaScript errors
|
||||
* (such as infinite loops or "Maximum call stack size exceeded").
|
||||
*
|
||||
* Any request containing one of the following headers is treated as a
|
||||
* client-managed or partial request, and the Debug Toolbar injection is skipped.
|
||||
*
|
||||
* @var array<string, string|null>
|
||||
*/
|
||||
public array $disableOnHeaders = [
|
||||
'X-Requested-With' => 'xmlhttprequest', // AJAX requests
|
||||
'HX-Request' => 'true', // HTMX requests
|
||||
'X-Up-Version' => null, // Unpoly partial requests
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,47 +27,47 @@ class UserAgents extends BaseConfig
|
|||
*/
|
||||
public array $platforms = [
|
||||
'windows nt 10.0' => 'Windows 10',
|
||||
'windows nt 6.3' => 'Windows 8.1',
|
||||
'windows nt 6.2' => 'Windows 8',
|
||||
'windows nt 6.1' => 'Windows 7',
|
||||
'windows nt 6.0' => 'Windows Vista',
|
||||
'windows nt 5.2' => 'Windows 2003',
|
||||
'windows nt 5.1' => 'Windows XP',
|
||||
'windows nt 5.0' => 'Windows 2000',
|
||||
'windows nt 4.0' => 'Windows NT 4.0',
|
||||
'winnt4.0' => 'Windows NT 4.0',
|
||||
'winnt 4.0' => 'Windows NT',
|
||||
'winnt' => 'Windows NT',
|
||||
'windows 98' => 'Windows 98',
|
||||
'win98' => 'Windows 98',
|
||||
'windows 95' => 'Windows 95',
|
||||
'win95' => 'Windows 95',
|
||||
'windows phone' => 'Windows Phone',
|
||||
'windows' => 'Unknown Windows OS',
|
||||
'android' => 'Android',
|
||||
'blackberry' => 'BlackBerry',
|
||||
'iphone' => 'iOS',
|
||||
'ipad' => 'iOS',
|
||||
'ipod' => 'iOS',
|
||||
'os x' => 'Mac OS X',
|
||||
'ppc mac' => 'Power PC Mac',
|
||||
'freebsd' => 'FreeBSD',
|
||||
'ppc' => 'Macintosh',
|
||||
'linux' => 'Linux',
|
||||
'debian' => 'Debian',
|
||||
'sunos' => 'Sun Solaris',
|
||||
'beos' => 'BeOS',
|
||||
'apachebench' => 'ApacheBench',
|
||||
'aix' => 'AIX',
|
||||
'irix' => 'Irix',
|
||||
'osf' => 'DEC OSF',
|
||||
'hp-ux' => 'HP-UX',
|
||||
'netbsd' => 'NetBSD',
|
||||
'bsdi' => 'BSDi',
|
||||
'openbsd' => 'OpenBSD',
|
||||
'gnu' => 'GNU/Linux',
|
||||
'unix' => 'Unknown Unix OS',
|
||||
'symbian' => 'Symbian OS',
|
||||
'windows nt 6.3' => 'Windows 8.1',
|
||||
'windows nt 6.2' => 'Windows 8',
|
||||
'windows nt 6.1' => 'Windows 7',
|
||||
'windows nt 6.0' => 'Windows Vista',
|
||||
'windows nt 5.2' => 'Windows 2003',
|
||||
'windows nt 5.1' => 'Windows XP',
|
||||
'windows nt 5.0' => 'Windows 2000',
|
||||
'windows nt 4.0' => 'Windows NT 4.0',
|
||||
'winnt4.0' => 'Windows NT 4.0',
|
||||
'winnt 4.0' => 'Windows NT',
|
||||
'winnt' => 'Windows NT',
|
||||
'windows 98' => 'Windows 98',
|
||||
'win98' => 'Windows 98',
|
||||
'windows 95' => 'Windows 95',
|
||||
'win95' => 'Windows 95',
|
||||
'windows phone' => 'Windows Phone',
|
||||
'windows' => 'Unknown Windows OS',
|
||||
'android' => 'Android',
|
||||
'blackberry' => 'BlackBerry',
|
||||
'iphone' => 'iOS',
|
||||
'ipad' => 'iOS',
|
||||
'ipod' => 'iOS',
|
||||
'os x' => 'Mac OS X',
|
||||
'ppc mac' => 'Power PC Mac',
|
||||
'freebsd' => 'FreeBSD',
|
||||
'ppc' => 'Macintosh',
|
||||
'linux' => 'Linux',
|
||||
'debian' => 'Debian',
|
||||
'sunos' => 'Sun Solaris',
|
||||
'beos' => 'BeOS',
|
||||
'apachebench' => 'ApacheBench',
|
||||
'aix' => 'AIX',
|
||||
'irix' => 'Irix',
|
||||
'osf' => 'DEC OSF',
|
||||
'hp-ux' => 'HP-UX',
|
||||
'netbsd' => 'NetBSD',
|
||||
'bsdi' => 'BSDi',
|
||||
'openbsd' => 'OpenBSD',
|
||||
'gnu' => 'GNU/Linux',
|
||||
'unix' => 'Unknown Unix OS',
|
||||
'symbian' => 'Symbian OS',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -81,37 +81,37 @@ class UserAgents extends BaseConfig
|
|||
* @var array<string, string>
|
||||
*/
|
||||
public array $browsers = [
|
||||
'OPR' => 'Opera',
|
||||
'Flock' => 'Flock',
|
||||
'Edge' => 'Spartan',
|
||||
'Edg' => 'Edge',
|
||||
'OPR' => 'Opera',
|
||||
'Flock' => 'Flock',
|
||||
'Edge' => 'Spartan',
|
||||
'Edg' => 'Edge',
|
||||
'Chrome' => 'Chrome',
|
||||
// Opera 10+ always reports Opera/9.80 and appends Version/<real version> to the user agent string
|
||||
'Opera.*?Version' => 'Opera',
|
||||
'Opera' => 'Opera',
|
||||
'MSIE' => 'Internet Explorer',
|
||||
'Opera.*?Version' => 'Opera',
|
||||
'Opera' => 'Opera',
|
||||
'MSIE' => 'Internet Explorer',
|
||||
'Internet Explorer' => 'Internet Explorer',
|
||||
'Trident.* rv' => 'Internet Explorer',
|
||||
'Shiira' => 'Shiira',
|
||||
'Firefox' => 'Firefox',
|
||||
'Chimera' => 'Chimera',
|
||||
'Phoenix' => 'Phoenix',
|
||||
'Firebird' => 'Firebird',
|
||||
'Camino' => 'Camino',
|
||||
'Netscape' => 'Netscape',
|
||||
'OmniWeb' => 'OmniWeb',
|
||||
'Safari' => 'Safari',
|
||||
'Mozilla' => 'Mozilla',
|
||||
'Konqueror' => 'Konqueror',
|
||||
'icab' => 'iCab',
|
||||
'Lynx' => 'Lynx',
|
||||
'Links' => 'Links',
|
||||
'hotjava' => 'HotJava',
|
||||
'amaya' => 'Amaya',
|
||||
'IBrowse' => 'IBrowse',
|
||||
'Maxthon' => 'Maxthon',
|
||||
'Ubuntu' => 'Ubuntu Web Browser',
|
||||
'Vivaldi' => 'Vivaldi',
|
||||
'Trident.* rv' => 'Internet Explorer',
|
||||
'Shiira' => 'Shiira',
|
||||
'Firefox' => 'Firefox',
|
||||
'Chimera' => 'Chimera',
|
||||
'Phoenix' => 'Phoenix',
|
||||
'Firebird' => 'Firebird',
|
||||
'Camino' => 'Camino',
|
||||
'Netscape' => 'Netscape',
|
||||
'OmniWeb' => 'OmniWeb',
|
||||
'Safari' => 'Safari',
|
||||
'Mozilla' => 'Mozilla',
|
||||
'Konqueror' => 'Konqueror',
|
||||
'icab' => 'iCab',
|
||||
'Lynx' => 'Lynx',
|
||||
'Links' => 'Links',
|
||||
'hotjava' => 'HotJava',
|
||||
'amaya' => 'Amaya',
|
||||
'IBrowse' => 'IBrowse',
|
||||
'Maxthon' => 'Maxthon',
|
||||
'Ubuntu' => 'Ubuntu Web Browser',
|
||||
'Vivaldi' => 'Vivaldi',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -139,86 +139,86 @@ class UserAgents extends BaseConfig
|
|||
// 'motorola' => 'Motorola'
|
||||
|
||||
// Phones and Manufacturers
|
||||
'motorola' => 'Motorola',
|
||||
'nokia' => 'Nokia',
|
||||
'palm' => 'Palm',
|
||||
'iphone' => 'Apple iPhone',
|
||||
'ipad' => 'iPad',
|
||||
'ipod' => 'Apple iPod Touch',
|
||||
'sony' => 'Sony Ericsson',
|
||||
'ericsson' => 'Sony Ericsson',
|
||||
'blackberry' => 'BlackBerry',
|
||||
'cocoon' => 'O2 Cocoon',
|
||||
'blazer' => 'Treo',
|
||||
'lg' => 'LG',
|
||||
'amoi' => 'Amoi',
|
||||
'xda' => 'XDA',
|
||||
'mda' => 'MDA',
|
||||
'vario' => 'Vario',
|
||||
'htc' => 'HTC',
|
||||
'samsung' => 'Samsung',
|
||||
'sharp' => 'Sharp',
|
||||
'sie-' => 'Siemens',
|
||||
'alcatel' => 'Alcatel',
|
||||
'benq' => 'BenQ',
|
||||
'ipaq' => 'HP iPaq',
|
||||
'mot-' => 'Motorola',
|
||||
'motorola' => 'Motorola',
|
||||
'nokia' => 'Nokia',
|
||||
'palm' => 'Palm',
|
||||
'iphone' => 'Apple iPhone',
|
||||
'ipad' => 'iPad',
|
||||
'ipod' => 'Apple iPod Touch',
|
||||
'sony' => 'Sony Ericsson',
|
||||
'ericsson' => 'Sony Ericsson',
|
||||
'blackberry' => 'BlackBerry',
|
||||
'cocoon' => 'O2 Cocoon',
|
||||
'blazer' => 'Treo',
|
||||
'lg' => 'LG',
|
||||
'amoi' => 'Amoi',
|
||||
'xda' => 'XDA',
|
||||
'mda' => 'MDA',
|
||||
'vario' => 'Vario',
|
||||
'htc' => 'HTC',
|
||||
'samsung' => 'Samsung',
|
||||
'sharp' => 'Sharp',
|
||||
'sie-' => 'Siemens',
|
||||
'alcatel' => 'Alcatel',
|
||||
'benq' => 'BenQ',
|
||||
'ipaq' => 'HP iPaq',
|
||||
'mot-' => 'Motorola',
|
||||
'playstation portable' => 'PlayStation Portable',
|
||||
'playstation 3' => 'PlayStation 3',
|
||||
'playstation vita' => 'PlayStation Vita',
|
||||
'hiptop' => 'Danger Hiptop',
|
||||
'nec-' => 'NEC',
|
||||
'panasonic' => 'Panasonic',
|
||||
'philips' => 'Philips',
|
||||
'sagem' => 'Sagem',
|
||||
'sanyo' => 'Sanyo',
|
||||
'spv' => 'SPV',
|
||||
'zte' => 'ZTE',
|
||||
'sendo' => 'Sendo',
|
||||
'nintendo dsi' => 'Nintendo DSi',
|
||||
'nintendo ds' => 'Nintendo DS',
|
||||
'nintendo 3ds' => 'Nintendo 3DS',
|
||||
'wii' => 'Nintendo Wii',
|
||||
'open web' => 'Open Web',
|
||||
'openweb' => 'OpenWeb',
|
||||
'playstation 3' => 'PlayStation 3',
|
||||
'playstation vita' => 'PlayStation Vita',
|
||||
'hiptop' => 'Danger Hiptop',
|
||||
'nec-' => 'NEC',
|
||||
'panasonic' => 'Panasonic',
|
||||
'philips' => 'Philips',
|
||||
'sagem' => 'Sagem',
|
||||
'sanyo' => 'Sanyo',
|
||||
'spv' => 'SPV',
|
||||
'zte' => 'ZTE',
|
||||
'sendo' => 'Sendo',
|
||||
'nintendo dsi' => 'Nintendo DSi',
|
||||
'nintendo ds' => 'Nintendo DS',
|
||||
'nintendo 3ds' => 'Nintendo 3DS',
|
||||
'wii' => 'Nintendo Wii',
|
||||
'open web' => 'Open Web',
|
||||
'openweb' => 'OpenWeb',
|
||||
|
||||
// Operating Systems
|
||||
'android' => 'Android',
|
||||
'symbian' => 'Symbian',
|
||||
'SymbianOS' => 'SymbianOS',
|
||||
'elaine' => 'Palm',
|
||||
'series60' => 'Symbian S60',
|
||||
'android' => 'Android',
|
||||
'symbian' => 'Symbian',
|
||||
'SymbianOS' => 'SymbianOS',
|
||||
'elaine' => 'Palm',
|
||||
'series60' => 'Symbian S60',
|
||||
'windows ce' => 'Windows CE',
|
||||
|
||||
// Browsers
|
||||
'obigo' => 'Obigo',
|
||||
'netfront' => 'Netfront Browser',
|
||||
'openwave' => 'Openwave Browser',
|
||||
'obigo' => 'Obigo',
|
||||
'netfront' => 'Netfront Browser',
|
||||
'openwave' => 'Openwave Browser',
|
||||
'mobilexplorer' => 'Mobile Explorer',
|
||||
'operamini' => 'Opera Mini',
|
||||
'opera mini' => 'Opera Mini',
|
||||
'opera mobi' => 'Opera Mobile',
|
||||
'fennec' => 'Firefox Mobile',
|
||||
'operamini' => 'Opera Mini',
|
||||
'opera mini' => 'Opera Mini',
|
||||
'opera mobi' => 'Opera Mobile',
|
||||
'fennec' => 'Firefox Mobile',
|
||||
|
||||
// Other
|
||||
'digital paths' => 'Digital Paths',
|
||||
'avantgo' => 'AvantGo',
|
||||
'xiino' => 'Xiino',
|
||||
'novarra' => 'Novarra Transcoder',
|
||||
'vodafone' => 'Vodafone',
|
||||
'docomo' => 'NTT DoCoMo',
|
||||
'o2' => 'O2',
|
||||
'avantgo' => 'AvantGo',
|
||||
'xiino' => 'Xiino',
|
||||
'novarra' => 'Novarra Transcoder',
|
||||
'vodafone' => 'Vodafone',
|
||||
'docomo' => 'NTT DoCoMo',
|
||||
'o2' => 'O2',
|
||||
|
||||
// Fallback
|
||||
'mobile' => 'Generic Mobile',
|
||||
'wireless' => 'Generic Mobile',
|
||||
'j2me' => 'Generic Mobile',
|
||||
'midp' => 'Generic Mobile',
|
||||
'cldc' => 'Generic Mobile',
|
||||
'up.link' => 'Generic Mobile',
|
||||
'mobile' => 'Generic Mobile',
|
||||
'wireless' => 'Generic Mobile',
|
||||
'j2me' => 'Generic Mobile',
|
||||
'midp' => 'Generic Mobile',
|
||||
'cldc' => 'Generic Mobile',
|
||||
'up.link' => 'Generic Mobile',
|
||||
'up.browser' => 'Generic Mobile',
|
||||
'smartphone' => 'Generic Mobile',
|
||||
'cellphone' => 'Generic Mobile',
|
||||
'cellphone' => 'Generic Mobile',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -231,24 +231,34 @@ class UserAgents extends BaseConfig
|
|||
* @var array<string, string>
|
||||
*/
|
||||
public array $robots = [
|
||||
'googlebot' => 'Googlebot',
|
||||
'msnbot' => 'MSNBot',
|
||||
'baiduspider' => 'Baiduspider',
|
||||
'bingbot' => 'Bing',
|
||||
'slurp' => 'Inktomi Slurp',
|
||||
'yahoo' => 'Yahoo',
|
||||
'ask jeeves' => 'Ask Jeeves',
|
||||
'fastcrawler' => 'FastCrawler',
|
||||
'infoseek' => 'InfoSeek Robot 1.0',
|
||||
'lycos' => 'Lycos',
|
||||
'yandex' => 'YandexBot',
|
||||
'googlebot' => 'Googlebot',
|
||||
'google-pagerenderer' => 'Google Page Renderer',
|
||||
'google-read-aloud' => 'Google Read Aloud',
|
||||
'google-safety' => 'Google Safety Bot',
|
||||
'msnbot' => 'MSNBot',
|
||||
'baiduspider' => 'Baiduspider',
|
||||
'bingbot' => 'Bing',
|
||||
'bingpreview' => 'BingPreview',
|
||||
'slurp' => 'Inktomi Slurp',
|
||||
'yahoo' => 'Yahoo',
|
||||
'ask jeeves' => 'Ask Jeeves',
|
||||
'fastcrawler' => 'FastCrawler',
|
||||
'infoseek' => 'InfoSeek Robot 1.0',
|
||||
'lycos' => 'Lycos',
|
||||
'yandex' => 'YandexBot',
|
||||
'mediapartners-google' => 'MediaPartners Google',
|
||||
'CRAZYWEBCRAWLER' => 'Crazy Webcrawler',
|
||||
'adsbot-google' => 'AdsBot Google',
|
||||
'feedfetcher-google' => 'Feedfetcher Google',
|
||||
'curious george' => 'Curious George',
|
||||
'ia_archiver' => 'Alexa Crawler',
|
||||
'MJ12bot' => 'Majestic-12',
|
||||
'Uptimebot' => 'Uptimebot',
|
||||
'CRAZYWEBCRAWLER' => 'Crazy Webcrawler',
|
||||
'adsbot-google' => 'AdsBot Google',
|
||||
'feedfetcher-google' => 'Feedfetcher Google',
|
||||
'curious george' => 'Curious George',
|
||||
'ia_archiver' => 'Alexa Crawler',
|
||||
'MJ12bot' => 'Majestic-12',
|
||||
'Uptimebot' => 'Uptimebot',
|
||||
'duckduckbot' => 'DuckDuckBot',
|
||||
'sogou' => 'Sogou Spider',
|
||||
'exabot' => 'Exabot',
|
||||
'bot' => 'Generic Bot',
|
||||
'crawler' => 'Generic Crawler',
|
||||
'spider' => 'Generic Spider',
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,29 +5,27 @@ declare(strict_types=1);
|
|||
namespace Config;
|
||||
|
||||
use App\Validation\FileRules as AppFileRules;
|
||||
use App\Validation\Rules as AppRules;
|
||||
use App\Validation\OtherRules;
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
use CodeIgniter\Validation\CreditCardRules;
|
||||
use CodeIgniter\Validation\FileRules;
|
||||
use CodeIgniter\Validation\FormatRules;
|
||||
use CodeIgniter\Validation\Rules;
|
||||
use Myth\Auth\Authentication\Passwords\ValidationRules as PasswordRules;
|
||||
use CodeIgniter\Validation\StrictRules\CreditCardRules;
|
||||
use CodeIgniter\Validation\StrictRules\FileRules;
|
||||
use CodeIgniter\Validation\StrictRules\FormatRules;
|
||||
use CodeIgniter\Validation\StrictRules\Rules;
|
||||
|
||||
class Validation extends BaseConfig
|
||||
{
|
||||
/**
|
||||
* Stores the classes that contain the rules that are available.
|
||||
*
|
||||
* @var string[]
|
||||
* @var list<string>
|
||||
*/
|
||||
public array $ruleSets = [
|
||||
Rules::class,
|
||||
FormatRules::class,
|
||||
FileRules::class,
|
||||
CreditCardRules::class,
|
||||
AppRules::class,
|
||||
AppFileRules::class,
|
||||
PasswordRules::class,
|
||||
OtherRules::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -36,7 +34,7 @@ class Validation extends BaseConfig
|
|||
* @var array<string, string>
|
||||
*/
|
||||
public array $templates = [
|
||||
'list' => 'CodeIgniter\Validation\Views\list',
|
||||
'list' => 'CodeIgniter\Validation\Views\list',
|
||||
'single' => 'CodeIgniter\Validation\Views\single',
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ use CodeIgniter\Config\View as BaseView;
|
|||
use CodeIgniter\View\ViewDecoratorInterface;
|
||||
use ViewComponents\Decorator;
|
||||
|
||||
/**
|
||||
* @phpstan-type parser_callable (callable(mixed): mixed)
|
||||
* @phpstan-type parser_callable_string (callable(mixed): mixed)&string
|
||||
*/
|
||||
class View extends BaseView
|
||||
{
|
||||
/**
|
||||
|
|
@ -27,7 +31,8 @@ class View extends BaseView
|
|||
*
|
||||
* Examples: { title|esc(js) } { created_on|date(Y-m-d)|esc(attr) }
|
||||
*
|
||||
* @var string[]
|
||||
* @var array<string, string>
|
||||
* @phpstan-var array<string, parser_callable_string>
|
||||
*/
|
||||
public $filters = [];
|
||||
|
||||
|
|
@ -35,7 +40,8 @@ class View extends BaseView
|
|||
* Parser Plugins provide a way to extend the functionality provided by the core Parser by creating aliases that
|
||||
* will be replaced with any callable. Can be single or tag pair.
|
||||
*
|
||||
* @var string[]
|
||||
* @var array<string, callable|list<string>|string>
|
||||
* @phpstan-var array<string, list<parser_callable_string>|parser_callable_string|parser_callable>
|
||||
*/
|
||||
public $plugins = [];
|
||||
|
||||
|
|
@ -45,7 +51,24 @@ class View extends BaseView
|
|||
*
|
||||
* All classes must implement CodeIgniter\View\ViewDecoratorInterface
|
||||
*
|
||||
* @var class-string<ViewDecoratorInterface>[]
|
||||
* @var list<class-string<ViewDecoratorInterface>>
|
||||
*/
|
||||
public array $decorators = [Decorator::class];
|
||||
|
||||
/**
|
||||
* Subdirectory within app/Views for namespaced view overrides.
|
||||
*
|
||||
* Namespaced views will be searched in:
|
||||
*
|
||||
* app/Views/{$appOverridesFolder}/{Namespace}/{view_path}.{php|html...}
|
||||
*
|
||||
* This allows application-level overrides for package or module views
|
||||
* without modifying vendor source files.
|
||||
*
|
||||
* Examples:
|
||||
* 'overrides' -> app/Views/overrides/Example/Blog/post/card.php
|
||||
* 'vendor' -> app/Views/vendor/Example/Blog/post/card.php
|
||||
* '' -> app/Views/Example/Blog/post/card.php (direct mapping)
|
||||
*/
|
||||
public string $appOverridesFolder = 'overrides';
|
||||
}
|
||||
|
|
|
|||
42
app/Config/Vite.php
Normal file
42
app/Config/Vite.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
// app/Config/Vite.php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Config;
|
||||
|
||||
use CodeIgniterVite\Config\Vite as ViteConfig;
|
||||
|
||||
class Vite extends ViteConfig
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$adminGateway = config('Admin')
|
||||
->gateway;
|
||||
$installGateway = config('Install')
|
||||
->gateway;
|
||||
|
||||
$this->routesAssets = [
|
||||
[
|
||||
'routes' => ['*'],
|
||||
'exclude' => [$adminGateway . '*', $installGateway . '*'],
|
||||
'assets' => ['styles/site.css', 'js/app.ts', 'js/podcast.ts', 'js/audio-player.ts'],
|
||||
],
|
||||
[
|
||||
'routes' => ['/map'],
|
||||
'assets' => ['js/map.ts'],
|
||||
],
|
||||
[
|
||||
'routes' => ['/' . $adminGateway . '*'],
|
||||
'assets' => ['styles/admin.css', 'js/admin.ts', 'js/admin-audio-player.ts'],
|
||||
],
|
||||
[
|
||||
'routes' => [$installGateway . '*'],
|
||||
'assets' => ['styles/install.css'],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
52
app/Config/WorkerMode.php
Normal file
52
app/Config/WorkerMode.php
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Config;
|
||||
|
||||
/**
|
||||
* This configuration controls how CodeIgniter behaves when running
|
||||
* in worker mode (with FrankenPHP).
|
||||
*/
|
||||
class WorkerMode
|
||||
{
|
||||
/**
|
||||
* Persistent Services
|
||||
*
|
||||
* List of service names that should persist across requests.
|
||||
* These services will NOT be reset between requests.
|
||||
*
|
||||
* Services not in this list will be reset for each request to prevent
|
||||
* state leakage.
|
||||
*
|
||||
* Recommended persistent services:
|
||||
* - `autoloader`: PSR-4 autoloading configuration
|
||||
* - `locator`: File locator
|
||||
* - `exceptions`: Exception handler
|
||||
* - `commands`: CLI commands registry
|
||||
* - `codeigniter`: Main application instance
|
||||
* - `superglobals`: Superglobals wrapper
|
||||
* - `routes`: Router configuration
|
||||
* - `cache`: Cache instance
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
public array $persistentServices = [
|
||||
'autoloader',
|
||||
'locator',
|
||||
'exceptions',
|
||||
'commands',
|
||||
'codeigniter',
|
||||
'superglobals',
|
||||
'routes',
|
||||
'cache',
|
||||
];
|
||||
|
||||
/**
|
||||
* Force Garbage Collection
|
||||
*
|
||||
* Whether to force garbage collection after each request.
|
||||
* Helps prevent memory leaks at a small performance cost.
|
||||
*/
|
||||
public bool $forceGarbageCollection = true;
|
||||
}
|
||||
|
|
@ -18,22 +18,19 @@ class ActorController extends FediverseActorController
|
|||
use AnalyticsTrait;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $helpers = ['auth', 'svg', 'components', 'misc', 'seo'];
|
||||
protected $helpers = ['svg', 'components', 'misc', 'seo'];
|
||||
|
||||
public function follow(): string
|
||||
public function followView(): string
|
||||
{
|
||||
// Prevent analytics hit when authenticated
|
||||
if (! can_user_interact()) {
|
||||
// @phpstan-ignore-next-line
|
||||
$this->registerPodcastWebpageHit($this->actor->podcast->id);
|
||||
}
|
||||
// @phpstan-ignore-next-line
|
||||
$this->registerPodcastWebpageHit($this->actor->podcast->id);
|
||||
|
||||
helper(['form', 'components', 'svg']);
|
||||
// @phpstan-ignore-next-line
|
||||
set_follow_metatags($this->actor);
|
||||
$data = [
|
||||
// @phpstan-ignore-next-line
|
||||
'metatags' => get_follow_metatags($this->actor),
|
||||
'actor' => $this->actor,
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -7,28 +7,47 @@ namespace App\Controllers;
|
|||
use CodeIgniter\Controller;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Override;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ViewThemes\Theme;
|
||||
|
||||
/**
|
||||
* Class BaseController
|
||||
*
|
||||
* BaseController provides a convenient place for loading components and performing functions that are needed by all
|
||||
* your controllers. Extend this class in any new controllers: class Home extends BaseController
|
||||
* your controllers.
|
||||
*
|
||||
* For security be sure to declare any new methods as protected or private.
|
||||
* Extend this class in any new controllers:
|
||||
* ```
|
||||
* class Home extends BaseController
|
||||
* ```
|
||||
*
|
||||
* For security, be sure to declare any new methods as protected or private.
|
||||
*/
|
||||
abstract class BaseController extends Controller
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
* An array of helpers to be loaded automatically upon
|
||||
* class instantiation. These helpers will be available
|
||||
* to all other controllers that extend BaseController.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $helpers = [];
|
||||
|
||||
/**
|
||||
* Be sure to declare properties for any property fetch you initialized.
|
||||
* The creation of dynamic property is deprecated in PHP 8.2.
|
||||
*/
|
||||
// protected $session;
|
||||
|
||||
#[Override]
|
||||
public function initController(
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response,
|
||||
LoggerInterface $logger
|
||||
LoggerInterface $logger,
|
||||
): void {
|
||||
$this->helpers = array_merge($this->helpers, ['auth', 'svg', 'components', 'misc', 'seo', 'premium_podcasts']);
|
||||
// Load here all helpers you want to be available in your controllers that extend BaseController.
|
||||
// Caution: Do not put the this below the parent::initController() call below.
|
||||
$this->helpers = [...$this->helpers, 'svg', 'components', 'misc', 'seo', 'premium_podcasts'];
|
||||
|
||||
// Do Not Edit This Line
|
||||
parent::initController($request, $response, $logger);
|
||||
|
|
|
|||
|
|
@ -11,14 +11,11 @@ declare(strict_types=1);
|
|||
namespace App\Controllers;
|
||||
|
||||
use CodeIgniter\Controller;
|
||||
use CodeIgniter\HTTP\Response;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
class ColorsController extends Controller
|
||||
{
|
||||
/**
|
||||
* @noRector ReturnTypeDeclarationRector
|
||||
*/
|
||||
public function index(): Response
|
||||
public function index(): ResponseInterface
|
||||
{
|
||||
$cacheName = 'colors.css';
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -23,18 +23,20 @@ class CreditsController extends BaseController
|
|||
|
||||
$cacheName = implode(
|
||||
'_',
|
||||
array_filter(['page', 'credits', $locale, can_user_interact() ? 'authenticated' : null]),
|
||||
array_filter(['page', 'credits', $locale, auth()->loggedIn() ? 'authenticated' : null]),
|
||||
);
|
||||
|
||||
if (! ($found = cache($cacheName))) {
|
||||
$page = new Page([
|
||||
'title' => lang('Person.credits', [], $locale),
|
||||
'slug' => 'credits',
|
||||
'title' => lang('Person.credits', [], $locale),
|
||||
'slug' => 'credits',
|
||||
'content_markdown' => '',
|
||||
]);
|
||||
|
||||
$allPodcasts = (new PodcastModel())->findAll();
|
||||
$allCredits = (new CreditModel())->findAll();
|
||||
$allPodcasts = new PodcastModel()
|
||||
->findAll();
|
||||
$allCredits = new CreditModel()
|
||||
->findAll();
|
||||
|
||||
// Unlike the carpenter, we make a tree from a table:
|
||||
$personGroup = null;
|
||||
|
|
@ -48,17 +50,15 @@ class CreditsController extends BaseController
|
|||
$personRole = $credit->person_role;
|
||||
$credits[$personGroup] = [
|
||||
'group_label' => $credit->group_label,
|
||||
'persons' => [
|
||||
'persons' => [
|
||||
$personId => [
|
||||
'full_name' => $credit->person->full_name,
|
||||
'thumbnail_url' =>
|
||||
$credit->person->avatar->thumbnail_url,
|
||||
'information_url' =>
|
||||
$credit->person->information_url,
|
||||
'roles' => [
|
||||
'full_name' => $credit->person->full_name,
|
||||
'thumbnail_url' => get_avatar_url($credit->person, 'thumbnail'),
|
||||
'information_url' => $credit->person->information_url,
|
||||
'roles' => [
|
||||
$personRole => [
|
||||
'role_label' => $credit->role_label,
|
||||
'is_in' => [
|
||||
'is_in' => [
|
||||
[
|
||||
'link' => $credit->episode_id
|
||||
? $credit->episode->link
|
||||
|
|
@ -88,14 +88,13 @@ class CreditsController extends BaseController
|
|||
$personId = $credit->person_id;
|
||||
$personRole = $credit->person_role;
|
||||
$credits[$personGroup]['persons'][$personId] = [
|
||||
'full_name' => $credit->person->full_name,
|
||||
'thumbnail_url' =>
|
||||
$credit->person->avatar->thumbnail_url,
|
||||
'full_name' => $credit->person->full_name,
|
||||
'thumbnail_url' => get_avatar_url($credit->person, 'thumbnail'),
|
||||
'information_url' => $credit->person->information_url,
|
||||
'roles' => [
|
||||
'roles' => [
|
||||
$personRole => [
|
||||
'role_label' => $credit->role_label,
|
||||
'is_in' => [
|
||||
'is_in' => [
|
||||
[
|
||||
'link' => $credit->episode_id
|
||||
? $credit->episode->link
|
||||
|
|
@ -124,7 +123,7 @@ class CreditsController extends BaseController
|
|||
$personRole
|
||||
] = [
|
||||
'role_label' => $credit->role_label,
|
||||
'is_in' => [
|
||||
'is_in' => [
|
||||
[
|
||||
'link' => $credit->episode_id
|
||||
? $credit->episode->link
|
||||
|
|
@ -167,9 +166,9 @@ class CreditsController extends BaseController
|
|||
}
|
||||
}
|
||||
|
||||
set_page_metatags($page);
|
||||
$data = [
|
||||
'metatags' => get_page_metatags($page),
|
||||
'page' => $page,
|
||||
'page' => $page,
|
||||
'credits' => $credits,
|
||||
];
|
||||
|
||||
|
|
|
|||
174
app/Controllers/EpisodeAudioController.php
Normal file
174
app/Controllers/EpisodeAudioController.php
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Entities\Episode;
|
||||
use App\Entities\Podcast;
|
||||
use App\Models\EpisodeModel;
|
||||
use App\Models\PodcastModel;
|
||||
use CodeIgniter\Controller;
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\HTTP\URI;
|
||||
use Modules\Analytics\Config\Analytics;
|
||||
use Modules\PremiumPodcasts\Entities\Subscription;
|
||||
use Modules\PremiumPodcasts\Models\SubscriptionModel;
|
||||
use Override;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class EpisodeAudioController extends Controller
|
||||
{
|
||||
/**
|
||||
* An array of helpers to be loaded automatically upon class instantiation. These helpers will be available to all
|
||||
* other controllers that extend Analytics.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $helpers = ['analytics'];
|
||||
|
||||
protected Podcast $podcast;
|
||||
|
||||
protected Episode $episode;
|
||||
|
||||
protected Analytics $analyticsConfig;
|
||||
|
||||
#[Override]
|
||||
public function initController(
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response,
|
||||
LoggerInterface $logger,
|
||||
): void {
|
||||
// Do Not Edit This Line
|
||||
parent::initController($request, $response, $logger);
|
||||
|
||||
set_user_session_deny_list_ip();
|
||||
set_user_session_location();
|
||||
set_user_session_player();
|
||||
|
||||
$this->analyticsConfig = config('Analytics');
|
||||
}
|
||||
|
||||
public function _remap(string $method, string ...$params): mixed
|
||||
{
|
||||
if (count($params) < 2) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
if (
|
||||
! ($podcast = new PodcastModel()->getPodcastByHandle($params[0])) instanceof Podcast
|
||||
) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$this->podcast = $podcast;
|
||||
|
||||
if (
|
||||
! ($episode = new EpisodeModel()->getEpisodeBySlug($params[0], $params[1])) instanceof Episode
|
||||
) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$this->episode = $episode;
|
||||
|
||||
unset($params[1]);
|
||||
unset($params[0]);
|
||||
|
||||
return $this->{$method}(...$params);
|
||||
}
|
||||
|
||||
public function index(): RedirectResponse | ResponseInterface
|
||||
{
|
||||
// check if episode is premium?
|
||||
$subscription = null;
|
||||
|
||||
// check if podcast is already unlocked before any token validation
|
||||
if ($this->episode->is_premium && ! ($subscription = service('premium_podcasts')->subscription(
|
||||
$this->episode->podcast->handle,
|
||||
)) instanceof Subscription) {
|
||||
// look for token as GET parameter
|
||||
if (($token = $this->request->getGet('token')) === null) {
|
||||
return $this->response->setStatusCode(401)
|
||||
->setJSON([
|
||||
'errors' => [
|
||||
'status' => 401,
|
||||
'title' => 'Unauthorized',
|
||||
'detail' => 'Episode is premium, you must provide a token to unlock it.',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
// check if there's a valid subscription for the provided token
|
||||
if (! ($subscription = new SubscriptionModel()->validateSubscription(
|
||||
$this->episode->podcast->handle,
|
||||
$token,
|
||||
)) instanceof Subscription) {
|
||||
return $this->response->setStatusCode(401, 'Invalid token!')
|
||||
->setJSON([
|
||||
'errors' => [
|
||||
'status' => 401,
|
||||
'title' => 'Unauthorized',
|
||||
'detail' => 'Invalid token!',
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$session = service('session');
|
||||
|
||||
$serviceName = '';
|
||||
if ($this->request->getGet('_from')) {
|
||||
$serviceName = $this->request->getGet('_from');
|
||||
} elseif ($session->get('embed_domain') !== null) {
|
||||
$serviceName = $session->get('embed_domain');
|
||||
} elseif ($session->get('referer') !== null && $session->get('referer') !== '- Direct -') {
|
||||
$serviceName = parse_url((string) $session->get('referer'), PHP_URL_HOST);
|
||||
}
|
||||
|
||||
$audioFileSize = $this->episode->audio->file_size;
|
||||
$audioFileHeaderSize = $this->episode->audio->header_size;
|
||||
$audioDuration = $this->episode->audio->duration;
|
||||
|
||||
// bytes_threshold: number of bytes that must be downloaded for an episode to be counted in download analytics
|
||||
// - if audio is less than or equal to 60s, then take the audio file_size
|
||||
// - if audio is more than 60s, then take the audio file_header_size + 60s
|
||||
$bytesThreshold = $audioDuration <= 60
|
||||
? $audioFileSize
|
||||
: $audioFileHeaderSize +
|
||||
(int) floor((($audioFileSize - $audioFileHeaderSize) / $audioDuration) * 60);
|
||||
|
||||
podcast_hit(
|
||||
$this->episode->podcast_id,
|
||||
$this->episode->id,
|
||||
$bytesThreshold,
|
||||
$audioFileSize,
|
||||
$audioDuration,
|
||||
$this->episode->published_at->getTimestamp(),
|
||||
$serviceName,
|
||||
$subscription instanceof Subscription ? $subscription->id : null,
|
||||
);
|
||||
|
||||
$audioFileURI = new URI(service('file_manager')->getUrl($this->episode->audio->file_key));
|
||||
|
||||
$queryParams = [];
|
||||
foreach ($this->request->getGet() as $key => $value) {
|
||||
// do not include token in query params
|
||||
if ($key !== 'token') {
|
||||
$queryParams[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$audioFileURI->setQueryArray($queryParams);
|
||||
|
||||
return redirect()->to((string) $audioFileURI);
|
||||
}
|
||||
}
|
||||
|
|
@ -16,11 +16,10 @@ use App\Entities\Podcast;
|
|||
use App\Libraries\CommentObject;
|
||||
use App\Models\EpisodeCommentModel;
|
||||
use App\Models\EpisodeModel;
|
||||
use App\Models\LikeModel;
|
||||
use App\Models\PodcastModel;
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\HTTP\Response;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Modules\Analytics\AnalyticsTrait;
|
||||
use Modules\Fediverse\Entities\Actor;
|
||||
use Modules\Fediverse\Objects\OrderedCollectionObject;
|
||||
|
|
@ -45,7 +44,7 @@ class EpisodeCommentController extends BaseController
|
|||
}
|
||||
|
||||
if (
|
||||
($podcast = (new PodcastModel())->getPodcastByHandle($params[0])) === null
|
||||
! ($podcast = new PodcastModel()->getPodcastByHandle($params[0])) instanceof Podcast
|
||||
) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
|
@ -54,15 +53,15 @@ class EpisodeCommentController extends BaseController
|
|||
$this->actor = $podcast->actor;
|
||||
|
||||
if (
|
||||
($episode = (new EpisodeModel())->getEpisodeBySlug($params[0], $params[1])) === null
|
||||
) {
|
||||
! ($episode = new EpisodeModel()->getEpisodeBySlug($params[0], $params[1])) instanceof Episode
|
||||
) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$this->episode = $episode;
|
||||
|
||||
if (
|
||||
($comment = (new EpisodeCommentModel())->getCommentById($params[2])) === null
|
||||
! ($comment = new EpisodeCommentModel()->getCommentById($params[2])) instanceof EpisodeComment
|
||||
) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
|
@ -78,10 +77,7 @@ class EpisodeCommentController extends BaseController
|
|||
|
||||
public function view(): string
|
||||
{
|
||||
// Prevent analytics hit when authenticated
|
||||
if (! can_user_interact()) {
|
||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||
}
|
||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||
|
||||
$cacheName = implode(
|
||||
'_',
|
||||
|
|
@ -91,27 +87,28 @@ class EpisodeCommentController extends BaseController
|
|||
"comment#{$this->comment->id}",
|
||||
service('request')
|
||||
->getLocale(),
|
||||
can_user_interact() ? 'authenticated' : null,
|
||||
auth()
|
||||
->loggedIn() ? 'authenticated' : null,
|
||||
]),
|
||||
);
|
||||
|
||||
if (! ($cachedView = cache($cacheName))) {
|
||||
set_episode_comment_metatags($this->comment);
|
||||
$data = [
|
||||
'metatags' => get_episode_comment_metatags($this->comment),
|
||||
'podcast' => $this->podcast,
|
||||
'actor' => $this->actor,
|
||||
'actor' => $this->actor,
|
||||
'episode' => $this->episode,
|
||||
'comment' => $this->comment,
|
||||
];
|
||||
|
||||
// if user is logged in then send to the authenticated activity view
|
||||
if (can_user_interact()) {
|
||||
if (auth()->loggedIn()) {
|
||||
helper('form');
|
||||
return view('episode/comment', $data);
|
||||
}
|
||||
|
||||
return view('episode/comment', $data, [
|
||||
'cache' => DECADE,
|
||||
'cache' => DECADE,
|
||||
'cache_name' => $cacheName,
|
||||
]);
|
||||
}
|
||||
|
|
@ -119,10 +116,7 @@ class EpisodeCommentController extends BaseController
|
|||
return $cachedView;
|
||||
}
|
||||
|
||||
/**
|
||||
* @noRector ReturnTypeDeclarationRector
|
||||
*/
|
||||
public function commentObject(): Response
|
||||
public function commentObject(): ResponseInterface
|
||||
{
|
||||
$commentObject = new CommentObject($this->comment);
|
||||
|
||||
|
|
@ -131,10 +125,7 @@ class EpisodeCommentController extends BaseController
|
|||
->setBody($commentObject->toJSON());
|
||||
}
|
||||
|
||||
/**
|
||||
* @noRector ReturnTypeDeclarationRector
|
||||
*/
|
||||
public function replies(): Response
|
||||
public function replies(): ResponseInterface
|
||||
{
|
||||
/**
|
||||
* get comment replies
|
||||
|
|
@ -154,11 +145,9 @@ class EpisodeCommentController extends BaseController
|
|||
$pager = $commentReplies->pager;
|
||||
|
||||
$orderedItems = [];
|
||||
if ($paginatedReplies !== null) {
|
||||
foreach ($paginatedReplies as $reply) {
|
||||
$replyObject = new CommentObject($reply);
|
||||
$orderedItems[] = $replyObject;
|
||||
}
|
||||
foreach ($paginatedReplies as $reply) {
|
||||
$replyObject = new CommentObject($reply);
|
||||
$orderedItems[] = $replyObject;
|
||||
}
|
||||
|
||||
$collection = new OrderedCollectionPage($pager, $orderedItems);
|
||||
|
|
@ -169,18 +158,26 @@ class EpisodeCommentController extends BaseController
|
|||
->setBody($collection->toJSON());
|
||||
}
|
||||
|
||||
public function attemptLike(): RedirectResponse
|
||||
public function likeAction(): RedirectResponse
|
||||
{
|
||||
model(LikeModel::class)
|
||||
->toggleLike(interact_as_actor(), $this->comment);
|
||||
if (! ($interactAsActor = interact_as_actor()) instanceof Actor) {
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
model('LikeModel')
|
||||
->toggleLike($interactAsActor, $this->comment);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function attemptReply(): RedirectResponse
|
||||
public function replyAction(): RedirectResponse
|
||||
{
|
||||
model(LikeModel::class)
|
||||
->toggleLike(interact_as_actor(), $this->comment);
|
||||
if (! ($interactAsActor = interact_as_actor()) instanceof Actor) {
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
model('LikeModel')
|
||||
->toggleLike($interactAsActor, $this->comment);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,15 +16,14 @@ use App\Libraries\NoteObject;
|
|||
use App\Libraries\PodcastEpisode;
|
||||
use App\Models\EpisodeModel;
|
||||
use App\Models\PodcastModel;
|
||||
use App\Models\PostModel;
|
||||
use CodeIgniter\Database\BaseBuilder;
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use CodeIgniter\HTTP\Response;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\Services;
|
||||
use Config\Embed;
|
||||
use Modules\Analytics\AnalyticsTrait;
|
||||
use Modules\Fediverse\Objects\OrderedCollectionObject;
|
||||
use Modules\Fediverse\Objects\OrderedCollectionPage;
|
||||
use Modules\Media\FileManagers\FileManagerInterface;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class EpisodeController extends BaseController
|
||||
|
|
@ -42,7 +41,7 @@ class EpisodeController extends BaseController
|
|||
}
|
||||
|
||||
if (
|
||||
($podcast = (new PodcastModel())->getPodcastByHandle($params[0])) === null
|
||||
! ($podcast = new PodcastModel()->getPodcastByHandle($params[0])) instanceof Podcast
|
||||
) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
|
@ -50,8 +49,8 @@ class EpisodeController extends BaseController
|
|||
$this->podcast = $podcast;
|
||||
|
||||
if (
|
||||
($episode = (new EpisodeModel())->getEpisodeBySlug($params[0], $params[1])) === null
|
||||
) {
|
||||
! ($episode = new EpisodeModel()->getEpisodeBySlug($params[0], $params[1])) instanceof Episode
|
||||
) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
|
|
@ -65,10 +64,7 @@ class EpisodeController extends BaseController
|
|||
|
||||
public function index(): string
|
||||
{
|
||||
// Prevent analytics hit when authenticated
|
||||
if (! can_user_interact()) {
|
||||
$this->registerPodcastWebpageHit($this->episode->podcast_id);
|
||||
}
|
||||
$this->registerPodcastWebpageHit($this->episode->podcast_id);
|
||||
|
||||
$cacheName = implode(
|
||||
'_',
|
||||
|
|
@ -79,22 +75,22 @@ class EpisodeController extends BaseController
|
|||
service('request')
|
||||
->getLocale(),
|
||||
is_unlocked($this->podcast->handle) ? 'unlocked' : null,
|
||||
can_user_interact() ? 'authenticated' : null,
|
||||
auth()
|
||||
->loggedIn() ? 'authenticated' : null,
|
||||
]),
|
||||
);
|
||||
|
||||
if (! ($cachedView = cache($cacheName))) {
|
||||
set_episode_metatags($this->episode);
|
||||
$data = [
|
||||
'metatags' => get_episode_metatags($this->episode),
|
||||
'podcast' => $this->podcast,
|
||||
'episode' => $this->episode,
|
||||
];
|
||||
|
||||
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
|
||||
$this->podcast->id,
|
||||
);
|
||||
$secondsToNextUnpublishedEpisode = new EpisodeModel()
|
||||
->getSecondsToNextUnpublishedEpisode($this->podcast->id);
|
||||
|
||||
if (can_user_interact()) {
|
||||
if (auth()->loggedIn()) {
|
||||
helper('form');
|
||||
|
||||
return view('episode/comments', $data);
|
||||
|
|
@ -102,9 +98,7 @@ class EpisodeController extends BaseController
|
|||
|
||||
// The page cache is set to a decade so it is deleted manually upon podcast update
|
||||
return view('episode/comments', $data, [
|
||||
'cache' => $secondsToNextUnpublishedEpisode
|
||||
? $secondsToNextUnpublishedEpisode
|
||||
: DECADE,
|
||||
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
|
||||
'cache_name' => $cacheName,
|
||||
]);
|
||||
}
|
||||
|
|
@ -114,10 +108,7 @@ class EpisodeController extends BaseController
|
|||
|
||||
public function activity(): string
|
||||
{
|
||||
// Prevent analytics hit when authenticated
|
||||
if (! can_user_interact()) {
|
||||
$this->registerPodcastWebpageHit($this->episode->podcast_id);
|
||||
}
|
||||
$this->registerPodcastWebpageHit($this->episode->podcast_id);
|
||||
|
||||
$cacheName = implode(
|
||||
'_',
|
||||
|
|
@ -129,22 +120,22 @@ class EpisodeController extends BaseController
|
|||
service('request')
|
||||
->getLocale(),
|
||||
is_unlocked($this->podcast->handle) ? 'unlocked' : null,
|
||||
can_user_interact() ? 'authenticated' : null,
|
||||
auth()
|
||||
->loggedIn() ? 'authenticated' : null,
|
||||
]),
|
||||
);
|
||||
|
||||
if (! ($cachedView = cache($cacheName))) {
|
||||
set_episode_metatags($this->episode);
|
||||
$data = [
|
||||
'metatags' => get_episode_metatags($this->episode),
|
||||
'podcast' => $this->podcast,
|
||||
'episode' => $this->episode,
|
||||
];
|
||||
|
||||
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
|
||||
$this->podcast->id,
|
||||
);
|
||||
$secondsToNextUnpublishedEpisode = new EpisodeModel()
|
||||
->getSecondsToNextUnpublishedEpisode($this->podcast->id);
|
||||
|
||||
if (can_user_interact()) {
|
||||
if (auth()->loggedIn()) {
|
||||
helper('form');
|
||||
|
||||
return view('episode/activity', $data);
|
||||
|
|
@ -152,9 +143,122 @@ class EpisodeController extends BaseController
|
|||
|
||||
// The page cache is set to a decade so it is deleted manually upon podcast update
|
||||
return view('episode/activity', $data, [
|
||||
'cache' => $secondsToNextUnpublishedEpisode
|
||||
? $secondsToNextUnpublishedEpisode
|
||||
: DECADE,
|
||||
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
|
||||
'cache_name' => $cacheName,
|
||||
]);
|
||||
}
|
||||
|
||||
return $cachedView;
|
||||
}
|
||||
|
||||
public function chapters(): string
|
||||
{
|
||||
$this->registerPodcastWebpageHit($this->episode->podcast_id);
|
||||
|
||||
$cacheName = implode(
|
||||
'_',
|
||||
array_filter([
|
||||
'page',
|
||||
"podcast#{$this->podcast->id}",
|
||||
"episode#{$this->episode->id}",
|
||||
'chapters',
|
||||
service('request')
|
||||
->getLocale(),
|
||||
is_unlocked($this->podcast->handle) ? 'unlocked' : null,
|
||||
auth()
|
||||
->loggedIn() ? 'authenticated' : null,
|
||||
]),
|
||||
);
|
||||
|
||||
if (! ($cachedView = cache($cacheName))) {
|
||||
set_episode_metatags($this->episode);
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'episode' => $this->episode,
|
||||
];
|
||||
|
||||
// get chapters from json file
|
||||
if (isset($this->episode->chapters->file_key)) {
|
||||
/** @var FileManagerInterface $fileManager */
|
||||
$fileManager = service('file_manager');
|
||||
$episodeChaptersJsonString = (string) $fileManager->getFileContents($this->episode->chapters->file_key);
|
||||
|
||||
$chapters = json_decode($episodeChaptersJsonString, true);
|
||||
$data['chapters'] = $chapters;
|
||||
}
|
||||
|
||||
$secondsToNextUnpublishedEpisode = new EpisodeModel()
|
||||
->getSecondsToNextUnpublishedEpisode($this->podcast->id);
|
||||
|
||||
if (auth()->loggedIn()) {
|
||||
helper('form');
|
||||
|
||||
return view('episode/chapters', $data);
|
||||
}
|
||||
|
||||
// The page cache is set to a decade so it is deleted manually upon podcast update
|
||||
return view('episode/chapters', $data, [
|
||||
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
|
||||
'cache_name' => $cacheName,
|
||||
]);
|
||||
}
|
||||
|
||||
return $cachedView;
|
||||
}
|
||||
|
||||
public function transcript(): string
|
||||
{
|
||||
$this->registerPodcastWebpageHit($this->episode->podcast_id);
|
||||
|
||||
$cacheName = implode(
|
||||
'_',
|
||||
array_filter([
|
||||
'page',
|
||||
"podcast#{$this->podcast->id}",
|
||||
"episode#{$this->episode->id}",
|
||||
'transcript',
|
||||
service('request')
|
||||
->getLocale(),
|
||||
is_unlocked($this->podcast->handle) ? 'unlocked' : null,
|
||||
auth()
|
||||
->loggedIn() ? 'authenticated' : null,
|
||||
]),
|
||||
);
|
||||
|
||||
if (! ($cachedView = cache($cacheName))) {
|
||||
set_episode_metatags($this->episode);
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'episode' => $this->episode,
|
||||
];
|
||||
|
||||
// get transcript from json file
|
||||
if ($this->episode->transcript !== null) {
|
||||
$data['transcript'] = $this->episode->transcript;
|
||||
|
||||
if ($this->episode->transcript->json_key !== null) {
|
||||
/** @var FileManagerInterface $fileManager */
|
||||
$fileManager = service('file_manager');
|
||||
$transcriptJsonString = (string) $fileManager->getFileContents(
|
||||
$this->episode->transcript->json_key,
|
||||
);
|
||||
|
||||
$data['captions'] = json_decode($transcriptJsonString, true);
|
||||
}
|
||||
}
|
||||
|
||||
$secondsToNextUnpublishedEpisode = new EpisodeModel()
|
||||
->getSecondsToNextUnpublishedEpisode($this->podcast->id);
|
||||
|
||||
if (auth()->loggedIn()) {
|
||||
helper('form');
|
||||
|
||||
return view('episode/transcript', $data);
|
||||
}
|
||||
|
||||
// The page cache is set to a decade so it is deleted manually upon podcast update
|
||||
return view('episode/transcript', $data, [
|
||||
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
|
||||
'cache_name' => $cacheName,
|
||||
]);
|
||||
}
|
||||
|
|
@ -166,15 +270,12 @@ class EpisodeController extends BaseController
|
|||
{
|
||||
header('Content-Security-Policy: frame-ancestors http://*:* https://*:*');
|
||||
|
||||
// Prevent analytics hit when authenticated
|
||||
if (! can_user_interact()) {
|
||||
$this->registerPodcastWebpageHit($this->episode->podcast_id);
|
||||
}
|
||||
$this->registerPodcastWebpageHit($this->episode->podcast_id);
|
||||
|
||||
$session = Services::session();
|
||||
$session->start();
|
||||
if (isset($_SERVER['HTTP_REFERER'])) {
|
||||
$session->set('embed_domain', parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST));
|
||||
$session = service('session');
|
||||
|
||||
if (service('superglobals')->server('HTTP_REFERER') !== null) {
|
||||
$session->set('embed_domain', parse_url(service('superglobals')->server('HTTP_REFERER'), PHP_URL_HOST));
|
||||
}
|
||||
|
||||
$cacheName = implode(
|
||||
|
|
@ -195,21 +296,18 @@ class EpisodeController extends BaseController
|
|||
$themeData = EpisodeModel::$themes[$theme];
|
||||
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'episode' => $this->episode,
|
||||
'theme' => $theme,
|
||||
'podcast' => $this->podcast,
|
||||
'episode' => $this->episode,
|
||||
'theme' => $theme,
|
||||
'themeData' => $themeData,
|
||||
];
|
||||
|
||||
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
|
||||
$this->podcast->id,
|
||||
);
|
||||
$secondsToNextUnpublishedEpisode = new EpisodeModel()
|
||||
->getSecondsToNextUnpublishedEpisode($this->podcast->id);
|
||||
|
||||
// The page cache is set to a decade so it is deleted manually upon podcast update
|
||||
return view('embed', $data, [
|
||||
'cache' => $secondsToNextUnpublishedEpisode
|
||||
? $secondsToNextUnpublishedEpisode
|
||||
: DECADE,
|
||||
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
|
||||
'cache_name' => $cacheName,
|
||||
]);
|
||||
}
|
||||
|
|
@ -220,22 +318,21 @@ class EpisodeController extends BaseController
|
|||
public function oembedJSON(): ResponseInterface
|
||||
{
|
||||
return $this->response->setJSON([
|
||||
'type' => 'rich',
|
||||
'version' => '1.0',
|
||||
'title' => $this->episode->title,
|
||||
'type' => 'rich',
|
||||
'version' => '1.0',
|
||||
'title' => $this->episode->title,
|
||||
'provider_name' => $this->podcast->title,
|
||||
'provider_url' => $this->podcast->link,
|
||||
'author_name' => $this->podcast->title,
|
||||
'author_url' => $this->podcast->link,
|
||||
'html' =>
|
||||
'<iframe src="' .
|
||||
'provider_url' => $this->podcast->link,
|
||||
'author_name' => $this->podcast->title,
|
||||
'author_url' => $this->podcast->link,
|
||||
'html' => '<iframe src="' .
|
||||
$this->episode->embed_url .
|
||||
'" width="100%" height="' . config('Embed')->height . '" frameborder="0" scrolling="no"></iframe>',
|
||||
'width' => config('Embed')
|
||||
->width,
|
||||
'height' => config('Embed')
|
||||
->height,
|
||||
'thumbnail_url' => $this->episode->cover->og_url,
|
||||
'thumbnail_url' => $this->episode->cover->og_url,
|
||||
'thumbnail_width' => config('Images')
|
||||
->podcastCoverSizes['og']['width'],
|
||||
'thumbnail_height' => config('Images')
|
||||
|
|
@ -262,7 +359,9 @@ class EpisodeController extends BaseController
|
|||
htmlspecialchars(
|
||||
'<iframe src="' .
|
||||
$this->episode->embed_url .
|
||||
'" width="100%" height="' . config('Embed')->height . '" frameborder="0" scrolling="no"></iframe>',
|
||||
'" width="100%" height="' . config(
|
||||
Embed::class,
|
||||
)->height . '" frameborder="0" scrolling="no"></iframe>',
|
||||
),
|
||||
);
|
||||
$oembed->addChild('width', (string) config('Embed')->width);
|
||||
|
|
@ -272,10 +371,7 @@ class EpisodeController extends BaseController
|
|||
return $this->response->setXML($oembed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @noRector ReturnTypeDeclarationRector
|
||||
*/
|
||||
public function episodeObject(): Response
|
||||
public function episodeObject(): ResponseInterface
|
||||
{
|
||||
$podcastObject = new PodcastEpisode($this->episode);
|
||||
|
||||
|
|
@ -284,20 +380,15 @@ class EpisodeController extends BaseController
|
|||
->setBody($podcastObject->toJSON());
|
||||
}
|
||||
|
||||
/**
|
||||
* @noRector ReturnTypeDeclarationRector
|
||||
*/
|
||||
public function comments(): Response
|
||||
public function comments(): ResponseInterface
|
||||
{
|
||||
/**
|
||||
* get comments: aggregated replies from posts referring to the episode
|
||||
*/
|
||||
$episodeComments = model(PostModel::class)
|
||||
->whereIn('in_reply_to_id', function (BaseBuilder $builder): BaseBuilder {
|
||||
return $builder->select('id')
|
||||
->from(config('Fediverse')->tablesPrefix . 'posts')
|
||||
->where('episode_id', $this->episode->id);
|
||||
})
|
||||
$episodeComments = model('PostModel')
|
||||
->whereIn('in_reply_to_id', fn (BaseBuilder $builder): BaseBuilder => $builder->select('id')
|
||||
->from('fediverse_posts')
|
||||
->where('episode_id', $this->episode->id))
|
||||
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
|
||||
->orderBy('published_at', 'ASC');
|
||||
|
||||
|
|
@ -312,10 +403,8 @@ class EpisodeController extends BaseController
|
|||
$pager = $episodeComments->pager;
|
||||
|
||||
$orderedItems = [];
|
||||
if ($paginatedComments !== null) {
|
||||
foreach ($paginatedComments as $comment) {
|
||||
$orderedItems[] = (new NoteObject($comment))->toArray();
|
||||
}
|
||||
foreach ($paginatedComments as $comment) {
|
||||
$orderedItems[] = new NoteObject($comment)->toArray();
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
|
|
|
|||
113
app/Controllers/EpisodePreviewController.php
Normal file
113
app/Controllers/EpisodePreviewController.php
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2023 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Entities\Episode;
|
||||
use App\Models\EpisodeModel;
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use Modules\Media\FileManagers\FileManagerInterface;
|
||||
|
||||
class EpisodePreviewController extends BaseController
|
||||
{
|
||||
protected Episode $episode;
|
||||
|
||||
public function _remap(string $method, string ...$params): mixed
|
||||
{
|
||||
if (count($params) < 1) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
// find episode by previewUUID
|
||||
$episode = new EpisodeModel()
|
||||
->getEpisodeByPreviewId($params[0]);
|
||||
|
||||
if (! $episode instanceof Episode) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$this->episode = $episode;
|
||||
|
||||
if ($episode->publication_status === 'published') {
|
||||
// redirect to episode page
|
||||
return redirect()->route('episode', [$episode->podcast->handle, $episode->slug]);
|
||||
}
|
||||
|
||||
unset($params[0]);
|
||||
|
||||
return $this->{$method}(...$params);
|
||||
}
|
||||
|
||||
public function index(): string
|
||||
{
|
||||
helper('form');
|
||||
|
||||
return view('episode/preview-comments', [
|
||||
'podcast' => $this->episode->podcast,
|
||||
'episode' => $this->episode,
|
||||
]);
|
||||
}
|
||||
|
||||
public function activity(): string
|
||||
{
|
||||
helper('form');
|
||||
|
||||
return view('episode/preview-activity', [
|
||||
'podcast' => $this->episode->podcast,
|
||||
'episode' => $this->episode,
|
||||
]);
|
||||
}
|
||||
|
||||
public function chapters(): string
|
||||
{
|
||||
$data = [
|
||||
'podcast' => $this->episode->podcast,
|
||||
'episode' => $this->episode,
|
||||
];
|
||||
|
||||
if (isset($this->episode->chapters->file_key)) {
|
||||
/** @var FileManagerInterface $fileManager */
|
||||
$fileManager = service('file_manager');
|
||||
$episodeChaptersJsonString = (string) $fileManager->getFileContents($this->episode->chapters->file_key);
|
||||
|
||||
$chapters = json_decode($episodeChaptersJsonString, true);
|
||||
$data['chapters'] = $chapters;
|
||||
}
|
||||
|
||||
helper('form');
|
||||
return view('episode/preview-chapters', $data);
|
||||
}
|
||||
|
||||
public function transcript(): string
|
||||
{
|
||||
// get transcript from json file
|
||||
$data = [
|
||||
'podcast' => $this->episode->podcast,
|
||||
'episode' => $this->episode,
|
||||
];
|
||||
|
||||
if ($this->episode->transcript !== null) {
|
||||
$data['transcript'] = $this->episode->transcript;
|
||||
|
||||
if ($this->episode->transcript->json_key !== null) {
|
||||
/** @var FileManagerInterface $fileManager */
|
||||
$fileManager = service('file_manager');
|
||||
$transcriptJsonString = (string) $fileManager->getFileContents(
|
||||
$this->episode->transcript->json_key,
|
||||
);
|
||||
|
||||
$data['captions'] = json_decode($transcriptJsonString, true);
|
||||
}
|
||||
}
|
||||
|
||||
helper('form');
|
||||
return view('episode/preview-transcript', $data);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @copyright 2022 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
|
@ -15,26 +15,47 @@ use App\Models\EpisodeModel;
|
|||
use App\Models\PodcastModel;
|
||||
use CodeIgniter\Controller;
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use CodeIgniter\HTTP\IncomingRequest;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Exception;
|
||||
use Modules\PremiumPodcasts\Entities\Subscription;
|
||||
use Modules\PremiumPodcasts\Models\SubscriptionModel;
|
||||
use Opawg\UserAgentsPhp\UserAgentsRSS;
|
||||
use Opawg\UserAgentsV2Php\UserAgentsRSS;
|
||||
|
||||
class FeedController extends Controller
|
||||
{
|
||||
/**
|
||||
* Instance of the main Request object.
|
||||
*
|
||||
* @var IncomingRequest
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
public function index(string $podcastHandle): ResponseInterface
|
||||
{
|
||||
helper(['rss', 'premium_podcasts']);
|
||||
|
||||
$podcast = (new PodcastModel())->where('handle', $podcastHandle)
|
||||
$podcast = new PodcastModel()
|
||||
->where('handle', $podcastHandle)
|
||||
->first();
|
||||
if (! $podcast) {
|
||||
if (! $podcast instanceof Podcast) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
// 301 redirect to new feed?
|
||||
$redirectToNewFeed = service('settings')
|
||||
->get('Podcast.redirect_to_new_feed', 'podcast:' . $podcast->id);
|
||||
|
||||
if ($redirectToNewFeed && $podcast->new_feed_url !== null && filter_var(
|
||||
$podcast->new_feed_url,
|
||||
FILTER_VALIDATE_URL,
|
||||
) && $podcast->new_feed_url !== current_url()) {
|
||||
return redirect()->to($podcast->new_feed_url, 301);
|
||||
}
|
||||
|
||||
helper(['rss', 'premium_podcasts', 'misc']);
|
||||
|
||||
$service = null;
|
||||
try {
|
||||
$service = UserAgentsRSS::find($_SERVER['HTTP_USER_AGENT']);
|
||||
$service = UserAgentsRSS::find(service('superglobals')->server('HTTP_USER_AGENT'));
|
||||
} catch (Exception $exception) {
|
||||
// If things go wrong the show must go on and the user must be able to download the file
|
||||
log_message('critical', $exception->getMessage());
|
||||
|
|
@ -48,7 +69,8 @@ class FeedController extends Controller
|
|||
$subscription = null;
|
||||
$token = $this->request->getGet('token');
|
||||
if ($token) {
|
||||
$subscription = (new SubscriptionModel())->validateSubscription($podcastHandle, $token);
|
||||
$subscription = new SubscriptionModel()
|
||||
->validateSubscription($podcastHandle, $token);
|
||||
}
|
||||
|
||||
$cacheName = implode(
|
||||
|
|
@ -57,7 +79,7 @@ class FeedController extends Controller
|
|||
"podcast#{$podcast->id}",
|
||||
'feed',
|
||||
$service ? $serviceSlug : null,
|
||||
$subscription !== null ? 'unlocked' : null,
|
||||
$subscription instanceof Subscription ? "subscription#{$subscription->id}" : null,
|
||||
]),
|
||||
);
|
||||
|
||||
|
|
@ -65,18 +87,11 @@ class FeedController extends Controller
|
|||
$found = get_rss_feed($podcast, $serviceSlug, $subscription, $token);
|
||||
|
||||
// The page cache is set to expire after next episode publication or a decade by default so it is deleted manually upon podcast update
|
||||
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
|
||||
$podcast->id,
|
||||
);
|
||||
$secondsToNextUnpublishedEpisode = new EpisodeModel()
|
||||
->getSecondsToNextUnpublishedEpisode($podcast->id);
|
||||
|
||||
cache()
|
||||
->save(
|
||||
$cacheName,
|
||||
$found,
|
||||
$secondsToNextUnpublishedEpisode
|
||||
? $secondsToNextUnpublishedEpisode
|
||||
: DECADE,
|
||||
);
|
||||
->save($cacheName, $found, $secondsToNextUnpublishedEpisode ?: DECADE);
|
||||
}
|
||||
|
||||
return $this->response->setXML($found);
|
||||
|
|
|
|||
|
|
@ -11,41 +11,74 @@ declare(strict_types=1);
|
|||
namespace App\Controllers;
|
||||
|
||||
use App\Models\PodcastModel;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use Config\Services;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Modules\Media\FileManagers\FileManagerInterface;
|
||||
|
||||
class HomeController extends BaseController
|
||||
{
|
||||
public function index(): RedirectResponse | string
|
||||
{
|
||||
$db = db_connect();
|
||||
if ($db->getDatabase() === '' || ! $db->tableExists('podcasts')) {
|
||||
// Database connection has not been set or could not find the podcasts table
|
||||
// Redirecting to install page because it is likely that Castopod has not been installed yet.
|
||||
// NB: as base_url wouldn't have been defined here, redirect to install wizard manually
|
||||
$route = Services::routes()->reverseRoute('install');
|
||||
return redirect()->to(rtrim(host_url(), '/') . $route);
|
||||
}
|
||||
|
||||
$sortOptions = ['activity', 'created_desc', 'created_asc'];
|
||||
$sortBy = in_array($this->request->getGet('sort'), $sortOptions, true) ? $this->request->getGet(
|
||||
'sort'
|
||||
'sort',
|
||||
) : 'activity';
|
||||
|
||||
$allPodcasts = (new PodcastModel())->getAllPodcasts($sortBy);
|
||||
$allPodcasts = new PodcastModel()
|
||||
->getAllPodcasts($sortBy);
|
||||
|
||||
// check if there's only one podcast to redirect user to it
|
||||
if (count($allPodcasts) === 1) {
|
||||
return redirect()->route('podcast-activity', [$allPodcasts[0]->handle]);
|
||||
}
|
||||
|
||||
set_home_metatags();
|
||||
// default behavior: list all podcasts on home page
|
||||
$data = [
|
||||
'metatags' => get_home_metatags(),
|
||||
'podcasts' => $allPodcasts,
|
||||
'sortBy' => $sortBy,
|
||||
'sortBy' => $sortBy,
|
||||
];
|
||||
|
||||
return view('home', $data);
|
||||
}
|
||||
|
||||
public function health(): ResponseInterface
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
try {
|
||||
db_connect();
|
||||
} catch (DatabaseException) {
|
||||
$errors[] = 'Unable to connect to the database.';
|
||||
}
|
||||
|
||||
// --- Can Castopod connect to the cache handler
|
||||
if (config('Cache')->handler !== 'dummy' && cache()->getCacheInfo() === null) {
|
||||
$errors[] = 'Unable connect to the cache handler.';
|
||||
}
|
||||
|
||||
// --- Can Castopod write to storage?
|
||||
|
||||
/** @var FileManagerInterface $fileManager */
|
||||
$fileManager = service('file_manager', false);
|
||||
|
||||
if (! $fileManager->isHealthy()) {
|
||||
$errors[] = 'Problem with file manager.';
|
||||
}
|
||||
|
||||
if ($errors !== []) {
|
||||
return $this->response->setStatusCode(503)
|
||||
->setJSON([
|
||||
'code' => 503,
|
||||
'errors' => $errors,
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->response->setStatusCode(200)
|
||||
->setJSON([
|
||||
'code' => 200,
|
||||
'message' => '✨ All good!',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,13 +24,14 @@ class MapController extends BaseController
|
|||
'map',
|
||||
service('request')
|
||||
->getLocale(),
|
||||
can_user_interact() ? 'authenticated' : null,
|
||||
auth()
|
||||
->loggedIn() ? 'authenticated' : null,
|
||||
]),
|
||||
);
|
||||
|
||||
if (! ($found = cache($cacheName))) {
|
||||
return view('pages/map', [], [
|
||||
'cache' => DECADE,
|
||||
'cache' => DECADE,
|
||||
'cache_name' => $cacheName,
|
||||
]);
|
||||
}
|
||||
|
|
@ -42,20 +43,20 @@ class MapController extends BaseController
|
|||
{
|
||||
$cacheName = 'episodes_markers';
|
||||
if (! ($found = cache($cacheName))) {
|
||||
$episodes = (new EpisodeModel())
|
||||
$episodes = new EpisodeModel()
|
||||
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
|
||||
->where('location_geo is not', null)
|
||||
->where('location_geo is not')
|
||||
->findAll();
|
||||
$found = [];
|
||||
foreach ($episodes as $episode) {
|
||||
$found[] = [
|
||||
'latitude' => $episode->location->latitude,
|
||||
'longitude' => $episode->location->longitude,
|
||||
'latitude' => $episode->location->latitude,
|
||||
'longitude' => $episode->location->longitude,
|
||||
'location_name' => esc($episode->location->name),
|
||||
'location_url' => $episode->location->url,
|
||||
'episode_link' => $episode->link,
|
||||
'podcast_link' => $episode->podcast->link,
|
||||
'cover_url' => $episode->cover->thumbnail_url,
|
||||
'location_url' => $episode->location->url,
|
||||
'episode_link' => $episode->link,
|
||||
'podcast_link' => $episode->podcast->link,
|
||||
'cover_url' => $episode->cover->thumbnail_url,
|
||||
'podcast_title' => esc($episode->podcast->title),
|
||||
'episode_title' => esc($episode->title),
|
||||
];
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ class PageController extends BaseController
|
|||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
if (
|
||||
($page = (new PageModel())->where('slug', $params[0])->first()) === null
|
||||
) {
|
||||
$page = new PageModel()
|
||||
->where('slug', $params[0])->first();
|
||||
if (! $page instanceof Page) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
|
|
@ -44,13 +44,14 @@ class PageController extends BaseController
|
|||
$this->page->slug,
|
||||
service('request')
|
||||
->getLocale(),
|
||||
can_user_interact() ? 'authenticated' : null,
|
||||
auth()
|
||||
->loggedIn() ? 'authenticated' : null,
|
||||
]),
|
||||
);
|
||||
|
||||
if (! ($found = cache($cacheName))) {
|
||||
set_page_metatags($this->page);
|
||||
$data = [
|
||||
'metatags' => get_page_metatags($this->page),
|
||||
'page' => $this->page,
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\PlatformModel;
|
||||
use CodeIgniter\Controller;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/*
|
||||
* Provide public access to all platforms so that they can be exported
|
||||
*/
|
||||
class PlatformController extends Controller
|
||||
{
|
||||
public function index(): ResponseInterface
|
||||
{
|
||||
$model = new PlatformModel();
|
||||
|
||||
return $this->response->setJSON($model->getPlatforms());
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ use App\Models\EpisodeModel;
|
|||
use App\Models\PodcastModel;
|
||||
use App\Models\PostModel;
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use CodeIgniter\HTTP\Response;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Modules\Analytics\AnalyticsTrait;
|
||||
use Modules\Fediverse\Objects\OrderedCollectionObject;
|
||||
use Modules\Fediverse\Objects\OrderedCollectionPage;
|
||||
|
|
@ -35,7 +35,7 @@ class PodcastController extends BaseController
|
|||
}
|
||||
|
||||
if (
|
||||
($podcast = (new PodcastModel())->getPodcastByHandle($params[0])) === null
|
||||
! ($podcast = new PodcastModel()->getPodcastByHandle($params[0])) instanceof Podcast
|
||||
) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
|
@ -47,10 +47,7 @@ class PodcastController extends BaseController
|
|||
return $this->{$method}(...$params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @noRector ReturnTypeDeclarationRector
|
||||
*/
|
||||
public function podcastActor(): Response
|
||||
public function podcastActor(): ResponseInterface
|
||||
{
|
||||
$podcastActor = new PodcastActor($this->podcast);
|
||||
|
||||
|
|
@ -61,10 +58,7 @@ class PodcastController extends BaseController
|
|||
|
||||
public function activity(): string
|
||||
{
|
||||
// Prevent analytics hit when authenticated
|
||||
if (! can_user_interact()) {
|
||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||
}
|
||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||
|
||||
$cacheName = implode(
|
||||
'_',
|
||||
|
|
@ -75,32 +69,31 @@ class PodcastController extends BaseController
|
|||
service('request')
|
||||
->getLocale(),
|
||||
is_unlocked($this->podcast->handle) ? 'unlocked' : null,
|
||||
can_user_interact() ? 'authenticated' : null,
|
||||
auth()
|
||||
->loggedIn() ? 'authenticated' : null,
|
||||
]),
|
||||
);
|
||||
|
||||
if (! ($cachedView = cache($cacheName))) {
|
||||
set_podcast_metatags($this->podcast, 'activity');
|
||||
$data = [
|
||||
'metatags' => get_podcast_metatags($this->podcast, 'activity'),
|
||||
'podcast' => $this->podcast,
|
||||
'posts' => (new PostModel())->getActorPublishedPosts($this->podcast->actor_id),
|
||||
'posts' => new PostModel()
|
||||
->getActorPublishedPosts($this->podcast->actor_id),
|
||||
];
|
||||
|
||||
// if user is logged in then send to the authenticated activity view
|
||||
if (can_user_interact()) {
|
||||
if (auth()->loggedIn()) {
|
||||
helper('form');
|
||||
|
||||
return view('podcast/activity', $data);
|
||||
}
|
||||
|
||||
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
|
||||
$this->podcast->id,
|
||||
);
|
||||
$secondsToNextUnpublishedEpisode = new EpisodeModel()
|
||||
->getSecondsToNextUnpublishedEpisode($this->podcast->id);
|
||||
|
||||
return view('podcast/activity', $data, [
|
||||
'cache' => $secondsToNextUnpublishedEpisode
|
||||
? $secondsToNextUnpublishedEpisode
|
||||
: DECADE,
|
||||
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
|
||||
'cache_name' => $cacheName,
|
||||
]);
|
||||
}
|
||||
|
|
@ -110,10 +103,7 @@ class PodcastController extends BaseController
|
|||
|
||||
public function about(): string
|
||||
{
|
||||
// Prevent analytics hit when authenticated
|
||||
if (! can_user_interact()) {
|
||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||
}
|
||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||
|
||||
$cacheName = implode(
|
||||
'_',
|
||||
|
|
@ -124,34 +114,33 @@ class PodcastController extends BaseController
|
|||
service('request')
|
||||
->getLocale(),
|
||||
is_unlocked($this->podcast->handle) ? 'unlocked' : null,
|
||||
can_user_interact() ? 'authenticated' : null,
|
||||
auth()
|
||||
->loggedIn() ? 'authenticated' : null,
|
||||
]),
|
||||
);
|
||||
|
||||
if (! ($cachedView = cache($cacheName))) {
|
||||
$stats = (new EpisodeModel())->getPodcastStats($this->podcast->id);
|
||||
$stats = new EpisodeModel()
|
||||
->getPodcastStats($this->podcast->id);
|
||||
|
||||
set_podcast_metatags($this->podcast, 'about');
|
||||
$data = [
|
||||
'metatags' => get_podcast_metatags($this->podcast, 'about'),
|
||||
'podcast' => $this->podcast,
|
||||
'stats' => $stats,
|
||||
'stats' => $stats,
|
||||
];
|
||||
|
||||
// // if user is logged in then send to the authenticated activity view
|
||||
if (can_user_interact()) {
|
||||
if (auth()->loggedIn()) {
|
||||
helper('form');
|
||||
|
||||
return view('podcast/about', $data);
|
||||
}
|
||||
|
||||
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
|
||||
$this->podcast->id,
|
||||
);
|
||||
$secondsToNextUnpublishedEpisode = new EpisodeModel()
|
||||
->getSecondsToNextUnpublishedEpisode($this->podcast->id);
|
||||
|
||||
return view('podcast/about', $data, [
|
||||
'cache' => $secondsToNextUnpublishedEpisode
|
||||
? $secondsToNextUnpublishedEpisode
|
||||
: DECADE,
|
||||
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
|
||||
'cache_name' => $cacheName,
|
||||
]);
|
||||
}
|
||||
|
|
@ -161,16 +150,14 @@ class PodcastController extends BaseController
|
|||
|
||||
public function episodes(): string
|
||||
{
|
||||
// Prevent analytics hit when authenticated
|
||||
if (! can_user_interact()) {
|
||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||
}
|
||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||
|
||||
$yearQuery = $this->request->getGet('year');
|
||||
$seasonQuery = $this->request->getGet('season');
|
||||
|
||||
if (! $yearQuery && ! $seasonQuery) {
|
||||
$defaultQuery = (new PodcastModel())->getDefaultQuery($this->podcast->id);
|
||||
$defaultQuery = new PodcastModel()
|
||||
->getDefaultQuery($this->podcast->id);
|
||||
if ($defaultQuery) {
|
||||
if ($defaultQuery['type'] === 'season') {
|
||||
$seasonQuery = $defaultQuery['data']['season_number'];
|
||||
|
|
@ -191,7 +178,8 @@ class PodcastController extends BaseController
|
|||
service('request')
|
||||
->getLocale(),
|
||||
is_unlocked($this->podcast->handle) ? 'unlocked' : null,
|
||||
can_user_interact() ? 'authenticated' : null,
|
||||
auth()
|
||||
->loggedIn() ? 'authenticated' : null,
|
||||
]),
|
||||
);
|
||||
|
||||
|
|
@ -207,18 +195,17 @@ class PodcastController extends BaseController
|
|||
$isActive = $yearQuery === $year['year'];
|
||||
if ($isActive) {
|
||||
$activeQuery = [
|
||||
'type' => 'year',
|
||||
'value' => $year['year'],
|
||||
'label' => $year['year'],
|
||||
'type' => 'year',
|
||||
'value' => $year['year'],
|
||||
'label' => $year['year'],
|
||||
'number_of_episodes' => $year['number_of_episodes'],
|
||||
];
|
||||
}
|
||||
|
||||
$episodesNavigation[] = [
|
||||
'label' => $year['year'],
|
||||
'label' => $year['year'],
|
||||
'number_of_episodes' => $year['number_of_episodes'],
|
||||
'route' =>
|
||||
route_to('podcast-episodes', $this->podcast->handle) .
|
||||
'route' => route_to('podcast-episodes', $this->podcast->handle) .
|
||||
'?year=' .
|
||||
$year['year'],
|
||||
'is_active' => $isActive,
|
||||
|
|
@ -229,7 +216,7 @@ class PodcastController extends BaseController
|
|||
$isActive = $seasonQuery === $season['season_number'];
|
||||
if ($isActive) {
|
||||
$activeQuery = [
|
||||
'type' => 'season',
|
||||
'type' => 'season',
|
||||
'value' => $season['season_number'],
|
||||
'label' => lang('Podcast.season', [
|
||||
'seasonNumber' => $season['season_number'],
|
||||
|
|
@ -243,38 +230,30 @@ class PodcastController extends BaseController
|
|||
'seasonNumber' => $season['season_number'],
|
||||
]),
|
||||
'number_of_episodes' => $season['number_of_episodes'],
|
||||
'route' =>
|
||||
route_to('podcast-episodes', $this->podcast->handle) .
|
||||
'route' => route_to('podcast-episodes', $this->podcast->handle) .
|
||||
'?season=' .
|
||||
$season['season_number'],
|
||||
'is_active' => $isActive,
|
||||
];
|
||||
}
|
||||
|
||||
set_podcast_metatags($this->podcast, 'episodes');
|
||||
$data = [
|
||||
'metatags' => get_podcast_metatags($this->podcast, 'episodes'),
|
||||
'podcast' => $this->podcast,
|
||||
'podcast' => $this->podcast,
|
||||
'episodesNav' => $episodesNavigation,
|
||||
'activeQuery' => $activeQuery,
|
||||
'episodes' => (new EpisodeModel())->getPodcastEpisodes(
|
||||
$this->podcast->id,
|
||||
$this->podcast->type,
|
||||
$yearQuery,
|
||||
$seasonQuery,
|
||||
),
|
||||
'episodes' => new EpisodeModel()
|
||||
->getPodcastEpisodes($this->podcast->id, $this->podcast->type, $yearQuery, $seasonQuery),
|
||||
];
|
||||
|
||||
if (can_user_interact()) {
|
||||
if (auth()->loggedIn()) {
|
||||
return view('podcast/episodes', $data);
|
||||
}
|
||||
|
||||
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
|
||||
$this->podcast->id,
|
||||
);
|
||||
$secondsToNextUnpublishedEpisode = new EpisodeModel()
|
||||
->getSecondsToNextUnpublishedEpisode($this->podcast->id);
|
||||
return view('podcast/episodes', $data, [
|
||||
'cache' => $secondsToNextUnpublishedEpisode
|
||||
? $secondsToNextUnpublishedEpisode
|
||||
: DECADE,
|
||||
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
|
||||
'cache_name' => $cacheName,
|
||||
]);
|
||||
}
|
||||
|
|
@ -282,18 +261,15 @@ class PodcastController extends BaseController
|
|||
return $cachedView;
|
||||
}
|
||||
|
||||
/**
|
||||
* @noRector ReturnTypeDeclarationRector
|
||||
*/
|
||||
public function episodeCollection(): Response
|
||||
public function episodeCollection(): ResponseInterface
|
||||
{
|
||||
if ($this->podcast->type === 'serial') {
|
||||
// podcast is serial
|
||||
$episodes = model(EpisodeModel::class)
|
||||
$episodes = model('EpisodeModel')
|
||||
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
|
||||
->orderBy('season_number DESC, number ASC');
|
||||
} else {
|
||||
$episodes = model(EpisodeModel::class)
|
||||
$episodes = model('EpisodeModel')
|
||||
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
|
||||
->orderBy('published_at', 'DESC');
|
||||
}
|
||||
|
|
@ -309,10 +285,8 @@ class PodcastController extends BaseController
|
|||
$pager = $episodes->pager;
|
||||
|
||||
$orderedItems = [];
|
||||
if ($paginatedEpisodes !== null) {
|
||||
foreach ($paginatedEpisodes as $episode) {
|
||||
$orderedItems[] = (new PodcastEpisode($episode))->toArray();
|
||||
}
|
||||
foreach ($paginatedEpisodes as $episode) {
|
||||
$orderedItems[] = new PodcastEpisode($episode)->toArray();
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
|
|
@ -323,4 +297,12 @@ class PodcastController extends BaseController
|
|||
->setContentType('application/activity+json')
|
||||
->setBody($collection->toJSON());
|
||||
}
|
||||
|
||||
public function links(): string
|
||||
{
|
||||
set_podcast_metatags($this->podcast, 'links');
|
||||
return view('podcast/links', [
|
||||
'podcast' => $this->podcast,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ use CodeIgniter\HTTP\URI;
|
|||
use CodeIgniter\I18n\Time;
|
||||
use Modules\Analytics\AnalyticsTrait;
|
||||
use Modules\Fediverse\Controllers\PostController as FediversePostController;
|
||||
use Modules\Fediverse\Models\FavouriteModel;
|
||||
use Override;
|
||||
|
||||
class PostController extends FediversePostController
|
||||
{
|
||||
|
|
@ -38,14 +38,16 @@ class PostController extends FediversePostController
|
|||
protected $post;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $helpers = ['auth', 'fediverse', 'svg', 'components', 'misc', 'seo', 'premium_podcasts'];
|
||||
|
||||
#[Override]
|
||||
public function _remap(string $method, string ...$params): mixed
|
||||
{
|
||||
|
||||
if (
|
||||
($podcast = (new PodcastModel())->getPodcastByHandle($params[0],)) === null
|
||||
! ($podcast = new PodcastModel()->getPodcastByHandle($params[0])) instanceof Podcast
|
||||
) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
|
@ -53,30 +55,34 @@ class PostController extends FediversePostController
|
|||
$this->podcast = $podcast;
|
||||
$this->actor = $this->podcast->actor;
|
||||
|
||||
if (
|
||||
count($params) > 1 &&
|
||||
($post = (new PostModel())->getPostById($params[1])) !== null
|
||||
) {
|
||||
/** @var CastopodPost $post */
|
||||
$this->post = $post;
|
||||
|
||||
if (count($params) <= 1) {
|
||||
unset($params[0]);
|
||||
unset($params[1]);
|
||||
|
||||
return $this->{$method}(...$params);
|
||||
}
|
||||
|
||||
if (
|
||||
! ($post = new PostModel()->getPostById($params[1])) instanceof CastopodPost
|
||||
) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$this->post = $post;
|
||||
|
||||
// show 404 if post is private
|
||||
if ($this->post->is_private && ! can_user_interact()) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
unset($params[0]);
|
||||
unset($params[1]);
|
||||
|
||||
return $this->{$method}(...$params);
|
||||
}
|
||||
|
||||
public function view(): string
|
||||
{
|
||||
// Prevent analytics hit when authenticated
|
||||
if (! can_user_interact()) {
|
||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||
}
|
||||
|
||||
if ($this->post === null) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||
|
||||
$cacheName = implode(
|
||||
'_',
|
||||
|
|
@ -85,25 +91,26 @@ class PostController extends FediversePostController
|
|||
"post#{$this->post->id}",
|
||||
service('request')
|
||||
->getLocale(),
|
||||
can_user_interact() ? 'authenticated' : null,
|
||||
auth()
|
||||
->loggedIn() ? 'authenticated' : null,
|
||||
]),
|
||||
);
|
||||
|
||||
if (! ($cachedView = cache($cacheName))) {
|
||||
set_post_metatags($this->post);
|
||||
$data = [
|
||||
'metatags' => get_post_metatags($this->post),
|
||||
'post' => $this->post,
|
||||
'post' => $this->post,
|
||||
'podcast' => $this->podcast,
|
||||
];
|
||||
|
||||
// if user is logged in then send to the authenticated activity view
|
||||
if (can_user_interact()) {
|
||||
if (auth()->loggedIn()) {
|
||||
helper('form');
|
||||
return view('post/post', $data);
|
||||
}
|
||||
|
||||
return view('post/post', $data, [
|
||||
'cache' => DECADE,
|
||||
'cache' => DECADE,
|
||||
'cache_name' => $cacheName,
|
||||
]);
|
||||
}
|
||||
|
|
@ -111,10 +118,11 @@ class PostController extends FediversePostController
|
|||
return $cachedView;
|
||||
}
|
||||
|
||||
public function attemptCreate(): RedirectResponse
|
||||
#[Override]
|
||||
public function createAction(): RedirectResponse
|
||||
{
|
||||
$rules = [
|
||||
'message' => 'required|max_length[500]',
|
||||
'message' => 'required|max_length[500]',
|
||||
'episode_url' => 'valid_url_strict|permit_empty',
|
||||
];
|
||||
|
||||
|
|
@ -125,20 +133,22 @@ class PostController extends FediversePostController
|
|||
->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
$message = $this->request->getPost('message');
|
||||
$validData = $this->validator->getValidated();
|
||||
|
||||
$message = $validData['message'];
|
||||
|
||||
$newPost = new CastopodPost([
|
||||
'actor_id' => interact_as_actor_id(),
|
||||
'actor_id' => interact_as_actor_id(),
|
||||
'published_at' => Time::now(),
|
||||
'created_by' => user_id(),
|
||||
'created_by' => user_id(),
|
||||
]);
|
||||
|
||||
// get episode if episodeUrl has been set
|
||||
$episodeUri = $this->request->getPost('episode_url');
|
||||
$episodeUri = $validData['episode_url'];
|
||||
if (
|
||||
$episodeUri &&
|
||||
($params = extract_params_from_episode_uri(new URI($episodeUri))) &&
|
||||
($episode = (new EpisodeModel())->getEpisodeBySlug($params['podcastHandle'], $params['episodeSlug']))
|
||||
($episode = new EpisodeModel()->getEpisodeBySlug($params['podcastHandle'], $params['episodeSlug']))
|
||||
) {
|
||||
$newPost->episode_id = $episode->id;
|
||||
}
|
||||
|
|
@ -160,7 +170,8 @@ class PostController extends FediversePostController
|
|||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function attemptReply(): RedirectResponse
|
||||
#[Override]
|
||||
public function replyAction(): RedirectResponse
|
||||
{
|
||||
$rules = [
|
||||
'message' => 'required|max_length[500]',
|
||||
|
|
@ -173,12 +184,15 @@ class PostController extends FediversePostController
|
|||
->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
$validData = $this->validator->getValidated();
|
||||
|
||||
$newPost = new CastopodPost([
|
||||
'actor_id' => interact_as_actor_id(),
|
||||
'actor_id' => interact_as_actor_id(),
|
||||
'in_reply_to_id' => $this->post->id,
|
||||
'message' => $this->request->getPost('message'),
|
||||
'published_at' => Time::now(),
|
||||
'created_by' => user_id(),
|
||||
'message' => $validData['message'],
|
||||
'is_private' => $this->post->is_private,
|
||||
'published_at' => Time::now(),
|
||||
'created_by' => user_id(),
|
||||
]);
|
||||
|
||||
if ($this->post->episode_id !== null) {
|
||||
|
|
@ -197,21 +211,24 @@ class PostController extends FediversePostController
|
|||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function attemptFavourite(): RedirectResponse
|
||||
#[Override]
|
||||
public function favouriteAction(): RedirectResponse
|
||||
{
|
||||
model(FavouriteModel::class)->toggleFavourite(interact_as_actor(), $this->post);
|
||||
model('FavouriteModel')->toggleFavourite(interact_as_actor(), $this->post);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function attemptReblog(): RedirectResponse
|
||||
#[Override]
|
||||
public function reblogAction(): RedirectResponse
|
||||
{
|
||||
(new PostModel())->toggleReblog(interact_as_actor(), $this->post);
|
||||
new PostModel()
|
||||
->toggleReblog(interact_as_actor(), $this->post);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function attemptAction(): RedirectResponse
|
||||
public function action(): RedirectResponse
|
||||
{
|
||||
$rules = [
|
||||
'action' => 'required|in_list[favourite,reblog,reply]',
|
||||
|
|
@ -224,47 +241,35 @@ class PostController extends FediversePostController
|
|||
->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
$action = $this->request->getPost('action');
|
||||
$validData = $this->validator->getValidated();
|
||||
|
||||
$action = $validData['action'];
|
||||
return match ($action) {
|
||||
'favourite' => $this->attemptFavourite(),
|
||||
'reblog' => $this->attemptReblog(),
|
||||
'reply' => $this->attemptReply(),
|
||||
default => redirect()
|
||||
'favourite' => $this->favouriteAction(),
|
||||
'reblog' => $this->reblogAction(),
|
||||
'reply' => $this->replyAction(),
|
||||
default => redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', 'error'),
|
||||
};
|
||||
}
|
||||
|
||||
public function remoteAction(string $action): string
|
||||
public function remoteActionView(string $action): string
|
||||
{
|
||||
// Prevent analytics hit when authenticated
|
||||
if (! can_user_interact()) {
|
||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||
}
|
||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||
|
||||
$cacheName = implode(
|
||||
'_',
|
||||
array_filter(['page', "post#{$this->post->id}", "remote_{$action}", service('request') ->getLocale()]),
|
||||
);
|
||||
set_remote_actions_metatags($this->post, $action);
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'actor' => $this->actor,
|
||||
'post' => $this->post,
|
||||
'action' => $action,
|
||||
];
|
||||
|
||||
if (! ($cachedView = cache($cacheName))) {
|
||||
$data = [
|
||||
'metatags' => get_remote_actions_metatags($this->post, $action),
|
||||
'podcast' => $this->podcast,
|
||||
'actor' => $this->actor,
|
||||
'post' => $this->post,
|
||||
'action' => $action,
|
||||
];
|
||||
helper('form');
|
||||
|
||||
helper('form');
|
||||
|
||||
return view('post/remote_action', $data, [
|
||||
'cache' => DECADE,
|
||||
'cache_name' => $cacheName,
|
||||
]);
|
||||
}
|
||||
|
||||
return (string) $cachedView;
|
||||
// NO VIEW CACHING: form has a CSRF token which should change on each request
|
||||
return view('post/remote_action', $data);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Entities\Podcast;
|
||||
use App\Models\PodcastModel;
|
||||
use CodeIgniter\Controller;
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
|
|
@ -20,56 +21,56 @@ class WebmanifestController extends Controller
|
|||
/**
|
||||
* @var array<string, array<string, string>>
|
||||
*/
|
||||
public const THEME_COLORS = [
|
||||
final public const array THEME_COLORS = [
|
||||
'pine' => [
|
||||
'theme' => '#009486',
|
||||
'theme' => '#009486',
|
||||
'background' => '#F0F9F8',
|
||||
],
|
||||
'lake' => [
|
||||
'theme' => '#00ACE0',
|
||||
'theme' => '#00ACE0',
|
||||
'background' => '#F0F7F9',
|
||||
],
|
||||
'jacaranda' => [
|
||||
'theme' => '#562CDD',
|
||||
'theme' => '#562CDD',
|
||||
'background' => '#F2F0F9',
|
||||
],
|
||||
'crimson' => [
|
||||
'theme' => '#F24562',
|
||||
'theme' => '#F24562',
|
||||
'background' => '#F9F0F2',
|
||||
],
|
||||
'amber' => [
|
||||
'theme' => '#FF6224',
|
||||
'theme' => '#FF6224',
|
||||
'background' => '#F9F3F0',
|
||||
],
|
||||
'onyx' => [
|
||||
'theme' => '#040406',
|
||||
'theme' => '#040406',
|
||||
'background' => '#F3F3F7',
|
||||
],
|
||||
];
|
||||
|
||||
public function index(): ResponseInterface
|
||||
{
|
||||
helper('misc');
|
||||
|
||||
$webmanifest = [
|
||||
'name' => esc(service('settings') ->get('App.siteName')),
|
||||
'name' => esc(service('settings') ->get('App.siteName')),
|
||||
'description' => esc(service('settings') ->get('App.siteDescription')),
|
||||
'lang' => service('request')
|
||||
'lang' => service('request')
|
||||
->getLocale(),
|
||||
'start_url' => base_url(),
|
||||
'display' => 'standalone',
|
||||
'orientation' => 'portrait',
|
||||
'theme_color' => self::THEME_COLORS[service('settings')->get('App.theme')]['theme'],
|
||||
'start_url' => base_url(),
|
||||
'display' => 'standalone',
|
||||
'orientation' => 'portrait',
|
||||
'theme_color' => self::THEME_COLORS[service('settings')->get('App.theme')]['theme'],
|
||||
'background_color' => self::THEME_COLORS[service('settings')->get('App.theme')]['background'],
|
||||
'icons' => [
|
||||
'icons' => [
|
||||
[
|
||||
'src' => service('settings')
|
||||
->get('App.siteIcon')['192'],
|
||||
'type' => 'image/png',
|
||||
'src' => get_site_icon_url('192'),
|
||||
'type' => 'image/png',
|
||||
'sizes' => '192x192',
|
||||
],
|
||||
[
|
||||
'src' => service('settings')
|
||||
->get('App.siteIcon')['512'],
|
||||
'type' => 'image/png',
|
||||
'src' => get_site_icon_url('512'),
|
||||
'type' => 'image/png',
|
||||
'sizes' => '512x512',
|
||||
],
|
||||
],
|
||||
|
|
@ -81,31 +82,31 @@ class WebmanifestController extends Controller
|
|||
public function podcastManifest(string $podcastHandle): ResponseInterface
|
||||
{
|
||||
if (
|
||||
($podcast = (new PodcastModel())->getPodcastByHandle($podcastHandle)) === null
|
||||
! ($podcast = new PodcastModel()->getPodcastByHandle($podcastHandle)) instanceof Podcast
|
||||
) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$webmanifest = [
|
||||
'name' => esc($podcast->title),
|
||||
'short_name' => '@' . esc($podcast->handle),
|
||||
'description' => $podcast->description,
|
||||
'lang' => $podcast->language_code,
|
||||
'start_url' => $podcast->link,
|
||||
'scope' => '/@' . esc($podcast->handle),
|
||||
'display' => 'standalone',
|
||||
'orientation' => 'portrait',
|
||||
'theme_color' => self::THEME_COLORS[service('settings')->get('App.theme')]['theme'],
|
||||
'name' => esc($podcast->title),
|
||||
'short_name' => $podcast->at_handle,
|
||||
'description' => $podcast->description,
|
||||
'lang' => $podcast->language_code,
|
||||
'start_url' => $podcast->link,
|
||||
'scope' => '/' . $podcast->at_handle,
|
||||
'display' => 'standalone',
|
||||
'orientation' => 'portrait',
|
||||
'theme_color' => self::THEME_COLORS[service('settings')->get('App.theme')]['theme'],
|
||||
'background_color' => self::THEME_COLORS[service('settings')->get('App.theme')]['background'],
|
||||
'icons' => [
|
||||
'icons' => [
|
||||
[
|
||||
'src' => $podcast->cover->webmanifest192_url,
|
||||
'type' => $podcast->cover->webmanifest192_mimetype,
|
||||
'src' => $podcast->cover->webmanifest192_url,
|
||||
'type' => $podcast->cover->webmanifest192_mimetype,
|
||||
'sizes' => '192x192',
|
||||
],
|
||||
[
|
||||
'src' => $podcast->cover->webmanifest512_url,
|
||||
'type' => $podcast->cover->webmanifest512_mimetype,
|
||||
'src' => $podcast->cover->webmanifest512_url,
|
||||
'type' => $podcast->cover->webmanifest512_mimetype,
|
||||
'sizes' => '512x512',
|
||||
],
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddEpisodeIdToPosts Adds episode_id field to posts table in database
|
||||
*
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AddEpisodeIdToPosts extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$prefix = $this->db->getPrefix();
|
||||
$fediverseTablesPrefix = config('Fediverse')
|
||||
->tablesPrefix;
|
||||
|
||||
$this->forge->addColumn("{$fediverseTablesPrefix}posts", [
|
||||
'episode_id' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
'after' => 'replies_count',
|
||||
],
|
||||
]);
|
||||
|
||||
$alterQuery = <<<CODE_SAMPLE
|
||||
ALTER TABLE {$prefix}{$fediverseTablesPrefix}posts
|
||||
ADD FOREIGN KEY {$prefix}{$fediverseTablesPrefix}posts_episode_id_foreign(episode_id) REFERENCES {$prefix}episodes(id) ON DELETE CASCADE;
|
||||
CODE_SAMPLE;
|
||||
$this->db->query($alterQuery);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$fediverseTablesPrefix = config('Fediverse')
|
||||
->tablesPrefix;
|
||||
|
||||
$this->forge->dropForeignKey(
|
||||
$fediverseTablesPrefix . 'posts',
|
||||
$fediverseTablesPrefix . 'posts_episode_id_foreign'
|
||||
);
|
||||
$this->forge->dropColumn($fediverseTablesPrefix . 'posts', 'episode_id');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddCreatedByToPosts Adds created_by field to posts table in database
|
||||
*
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AddCreatedByToPosts extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$prefix = $this->db->getPrefix();
|
||||
$fediverseTablesPrefix = config('Fediverse')
|
||||
->tablesPrefix;
|
||||
|
||||
$this->forge->addColumn("{$fediverseTablesPrefix}posts", [
|
||||
'created_by' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
'after' => 'episode_id',
|
||||
],
|
||||
]);
|
||||
|
||||
$alterQuery = <<<CODE_SAMPLE
|
||||
ALTER TABLE {$prefix}{$fediverseTablesPrefix}posts
|
||||
ADD FOREIGN KEY {$prefix}{$fediverseTablesPrefix}posts_created_by_foreign(created_by) REFERENCES {$prefix}users(id) ON DELETE CASCADE;
|
||||
CODE_SAMPLE;
|
||||
$this->db->query($alterQuery);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$fediverseTablesPrefix = config('Fediverse')
|
||||
->tablesPrefix;
|
||||
|
||||
$this->forge->dropForeignKey(
|
||||
$fediverseTablesPrefix . 'posts',
|
||||
$fediverseTablesPrefix . 'posts_created_by_foreign'
|
||||
);
|
||||
$this->forge->dropColumn($fediverseTablesPrefix . 'posts', 'created_by');
|
||||
}
|
||||
}
|
||||
|
|
@ -12,32 +12,33 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
use Override;
|
||||
|
||||
class AddCategories extends Migration
|
||||
class AddCategories extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'parent_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'code' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 32,
|
||||
],
|
||||
'apple_category' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 32,
|
||||
],
|
||||
'google_category' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 32,
|
||||
],
|
||||
]);
|
||||
|
|
@ -47,6 +48,7 @@ class AddCategories extends Migration
|
|||
$this->forge->createTable('categories');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('categories');
|
||||
|
|
@ -12,20 +12,21 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
use Override;
|
||||
|
||||
class AddLanguages extends Migration
|
||||
class AddLanguages extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->addField([
|
||||
'code' => [
|
||||
'type' => 'VARCHAR',
|
||||
'comment' => 'ISO 639-1 language code',
|
||||
'type' => 'VARCHAR',
|
||||
'comment' => 'ISO 639-1 language code',
|
||||
'constraint' => 2,
|
||||
],
|
||||
'native_name' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 128,
|
||||
],
|
||||
]);
|
||||
|
|
@ -33,6 +34,7 @@ class AddLanguages extends Migration
|
|||
$this->forge->createTable('languages');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('languages');
|
||||
|
|
@ -12,32 +12,33 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
use Override;
|
||||
|
||||
class AddPodcasts extends Migration
|
||||
class AddPodcasts extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'guid' => [
|
||||
'type' => 'CHAR',
|
||||
'type' => 'CHAR',
|
||||
'constraint' => 36,
|
||||
],
|
||||
'actor_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'handle' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 32,
|
||||
],
|
||||
'title' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 128,
|
||||
],
|
||||
'description_markdown' => [
|
||||
|
|
@ -47,50 +48,50 @@ class AddPodcasts extends Migration
|
|||
'type' => 'TEXT',
|
||||
],
|
||||
'cover_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'banner_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'language_code' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 2,
|
||||
],
|
||||
'category_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'default' => 0,
|
||||
'default' => 0,
|
||||
],
|
||||
'parental_advisory' => [
|
||||
'type' => 'ENUM',
|
||||
'type' => 'ENUM',
|
||||
'constraint' => ['clean', 'explicit'],
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'owner_name' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 128,
|
||||
],
|
||||
'owner_email' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 255,
|
||||
],
|
||||
'publisher' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 128,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'type' => [
|
||||
'type' => 'ENUM',
|
||||
'type' => 'ENUM',
|
||||
'constraint' => ['episodic', 'serial'],
|
||||
'default' => 'episodic',
|
||||
'default' => 'episodic',
|
||||
],
|
||||
'copyright' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 128,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'episode_description_footer_markdown' => [
|
||||
'type' => 'TEXT',
|
||||
|
|
@ -101,85 +102,83 @@ class AddPodcasts extends Migration
|
|||
'null' => true,
|
||||
],
|
||||
'is_blocked' => [
|
||||
'type' => 'TINYINT',
|
||||
'type' => 'TINYINT',
|
||||
'constraint' => 1,
|
||||
'default' => 0,
|
||||
'default' => 0,
|
||||
],
|
||||
'is_completed' => [
|
||||
'type' => 'TINYINT',
|
||||
'type' => 'TINYINT',
|
||||
'constraint' => 1,
|
||||
'default' => 0,
|
||||
'default' => 0,
|
||||
],
|
||||
'is_locked' => [
|
||||
'type' => 'TINYINT',
|
||||
'type' => 'TINYINT',
|
||||
'constraint' => 1,
|
||||
'default' => 1,
|
||||
'default' => 1,
|
||||
],
|
||||
'imported_feed_url' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 512,
|
||||
'comment' =>
|
||||
'The RSS feed URL if this podcast was imported, NULL otherwise.',
|
||||
'null' => true,
|
||||
'comment' => 'The RSS feed URL if this podcast was imported, NULL otherwise.',
|
||||
'null' => true,
|
||||
],
|
||||
'new_feed_url' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 512,
|
||||
'comment' =>
|
||||
'The RSS new feed URL if this podcast is moving out, NULL otherwise.',
|
||||
'null' => true,
|
||||
'comment' => 'The RSS new feed URL if this podcast is moving out, NULL otherwise.',
|
||||
'null' => true,
|
||||
],
|
||||
'payment_pointer' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 128,
|
||||
'comment' => 'Wallet address for Web Monetization payments',
|
||||
'null' => true,
|
||||
'comment' => 'Wallet address for Web Monetization payments',
|
||||
'null' => true,
|
||||
],
|
||||
'location_name' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 128,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'location_geo' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 32,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'location_osm' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 12,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'custom_rss' => [
|
||||
'type' => 'JSON',
|
||||
'null' => true,
|
||||
],
|
||||
'partner_id' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 32,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'partner_link_url' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 512,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'partner_image_url' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 512,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'is_premium_by_default' => [
|
||||
'type' => 'TINYINT',
|
||||
'type' => 'TINYINT',
|
||||
'constraint' => 1,
|
||||
'default' => 0,
|
||||
'default' => 0,
|
||||
],
|
||||
'created_by' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'updated_by' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'published_at' => [
|
||||
|
|
@ -199,7 +198,7 @@ class AddPodcasts extends Migration
|
|||
$this->forge->addUniqueKey('handle');
|
||||
$this->forge->addUniqueKey('guid');
|
||||
$this->forge->addUniqueKey('actor_id');
|
||||
$this->forge->addForeignKey('actor_id', config('Fediverse')->tablesPrefix . 'actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('cover_id', 'media', 'id');
|
||||
$this->forge->addForeignKey('banner_id', 'media', 'id', '', 'SET NULL');
|
||||
$this->forge->addForeignKey('category_id', 'categories', 'id');
|
||||
|
|
@ -209,6 +208,7 @@ class AddPodcasts extends Migration
|
|||
$this->forge->createTable('podcasts');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('podcasts');
|
||||
|
|
@ -12,36 +12,37 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
use Override;
|
||||
|
||||
class AddEpisodes extends Migration
|
||||
class AddEpisodes extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'podcast_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'guid' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 255,
|
||||
],
|
||||
'title' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 128,
|
||||
],
|
||||
'slug' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 128,
|
||||
],
|
||||
'audio_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'description_markdown' => [
|
||||
|
|
@ -51,95 +52,95 @@ class AddEpisodes extends Migration
|
|||
'type' => 'TEXT',
|
||||
],
|
||||
'cover_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'transcript_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'transcript_remote_url' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 512,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'chapters_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'chapters_remote_url' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 512,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'parental_advisory' => [
|
||||
'type' => 'ENUM',
|
||||
'type' => 'ENUM',
|
||||
'constraint' => ['clean', 'explicit'],
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'number' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'season_number' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'type' => [
|
||||
'type' => 'ENUM',
|
||||
'type' => 'ENUM',
|
||||
'constraint' => ['trailer', 'full', 'bonus'],
|
||||
'default' => 'full',
|
||||
'default' => 'full',
|
||||
],
|
||||
'is_blocked' => [
|
||||
'type' => 'TINYINT',
|
||||
'type' => 'TINYINT',
|
||||
'constraint' => 1,
|
||||
'default' => 0,
|
||||
'default' => 0,
|
||||
],
|
||||
'location_name' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 128,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'location_geo' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 32,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'location_osm' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 12,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'custom_rss' => [
|
||||
'type' => 'JSON',
|
||||
'null' => true,
|
||||
],
|
||||
'posts_count' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'default' => 0,
|
||||
'default' => 0,
|
||||
],
|
||||
'comments_count' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'default' => 0,
|
||||
'default' => 0,
|
||||
],
|
||||
'is_premium' => [
|
||||
'type' => 'TINYINT',
|
||||
'type' => 'TINYINT',
|
||||
'constraint' => 1,
|
||||
'default' => 0,
|
||||
'default' => 0,
|
||||
],
|
||||
'created_by' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'updated_by' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'published_at' => [
|
||||
|
|
@ -166,13 +167,14 @@ class AddEpisodes extends Migration
|
|||
|
||||
// Add Full-Text Search index on title and description_markdown
|
||||
$prefix = $this->db->getPrefix();
|
||||
$createQuery = <<<CODE_SAMPLE
|
||||
$createQuery = <<<SQL
|
||||
ALTER TABLE {$prefix}episodes
|
||||
ADD FULLTEXT(title, description_markdown);
|
||||
CODE_SAMPLE;
|
||||
ADD FULLTEXT title (title, description_markdown);
|
||||
SQL;
|
||||
$this->db->query($createQuery);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('episodes');
|
||||
|
|
@ -12,43 +12,45 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
use Override;
|
||||
|
||||
class AddPlatforms extends Migration
|
||||
class AddPlatforms extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->addField([
|
||||
'slug' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 32,
|
||||
],
|
||||
'type' => [
|
||||
'type' => 'ENUM',
|
||||
'type' => 'ENUM',
|
||||
'constraint' => ['podcasting', 'social', 'funding'],
|
||||
],
|
||||
'label' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 32,
|
||||
],
|
||||
'home_url' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 255,
|
||||
],
|
||||
'submit_url' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 512,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
]);
|
||||
$this->forge->addField('`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP()');
|
||||
$this->forge->addField(
|
||||
'`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP()'
|
||||
'`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP()',
|
||||
);
|
||||
$this->forge->addPrimaryKey('slug');
|
||||
$this->forge->createTable('platforms');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('platforms');
|
||||
|
|
@ -12,39 +12,40 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
use Override;
|
||||
|
||||
class AddPodcastsPlatforms extends Migration
|
||||
class AddPodcastsPlatforms extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->addField([
|
||||
'podcast_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'platform_slug' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 32,
|
||||
],
|
||||
'link_url' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 512,
|
||||
],
|
||||
'account_id' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 128,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'is_visible' => [
|
||||
'type' => 'TINYINT',
|
||||
'type' => 'TINYINT',
|
||||
'constraint' => 1,
|
||||
'default' => 0,
|
||||
'default' => 0,
|
||||
],
|
||||
'is_on_embed' => [
|
||||
'type' => 'TINYINT',
|
||||
'type' => 'TINYINT',
|
||||
'constraint' => 1,
|
||||
'default' => 0,
|
||||
'default' => 0,
|
||||
],
|
||||
]);
|
||||
|
||||
|
|
@ -54,6 +55,7 @@ class AddPodcastsPlatforms extends Migration
|
|||
$this->forge->createTable('podcasts_platforms');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('podcasts_platforms');
|
||||
|
|
@ -12,70 +12,69 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
use Override;
|
||||
|
||||
class AddEpisodeComments extends Migration
|
||||
class AddEpisodeComments extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'BINARY',
|
||||
'type' => 'BINARY',
|
||||
'constraint' => 16,
|
||||
],
|
||||
'uri' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 255,
|
||||
],
|
||||
'episode_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'actor_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'in_reply_to_id' => [
|
||||
'type' => 'BINARY',
|
||||
'type' => 'BINARY',
|
||||
'constraint' => 16,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'message' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 5000,
|
||||
],
|
||||
'message_html' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 6000,
|
||||
],
|
||||
'likes_count' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'replies_count' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'created_at' => [
|
||||
'type' => 'DATETIME',
|
||||
],
|
||||
'created_by' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
$fediverseTablesPrefix = config('Fediverse')
|
||||
->tablesPrefix;
|
||||
|
||||
$this->forge->addPrimaryKey('id');
|
||||
$this->forge->addForeignKey('episode_id', 'episodes', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('actor_id', $fediverseTablesPrefix . 'actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('created_by', 'users', 'id');
|
||||
$this->forge->createTable('episode_comments');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('episode_comments');
|
||||
|
|
|
|||
|
|
@ -12,33 +12,32 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
use Override;
|
||||
|
||||
class AddLikes extends Migration
|
||||
class AddLikes extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->addField([
|
||||
'actor_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'comment_id' => [
|
||||
'type' => 'BINARY',
|
||||
'type' => 'BINARY',
|
||||
'constraint' => 16,
|
||||
],
|
||||
]);
|
||||
|
||||
$fediverseTablesPrefix = config('Fediverse')
|
||||
->tablesPrefix;
|
||||
|
||||
$this->forge->addField('`created_at` timestamp NOT NULL DEFAULT current_timestamp()');
|
||||
$this->forge->addPrimaryKey(['actor_id', 'comment_id']);
|
||||
$this->forge->addForeignKey('actor_id', $fediverseTablesPrefix . 'actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('comment_id', 'episode_comments', 'id', '', 'CASCADE');
|
||||
$this->forge->createTable('likes');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('likes');
|
||||
|
|
|
|||
|
|
@ -12,26 +12,27 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
use Override;
|
||||
|
||||
class AddPages extends Migration
|
||||
class AddPages extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'title' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 255,
|
||||
],
|
||||
'slug' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 128,
|
||||
'unique' => true,
|
||||
'unique' => true,
|
||||
],
|
||||
'content_markdown' => [
|
||||
'type' => 'TEXT',
|
||||
|
|
@ -50,6 +51,7 @@ class AddPages extends Migration
|
|||
$this->forge->createTable('pages');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('pages');
|
||||
|
|
@ -12,19 +12,20 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
use Override;
|
||||
|
||||
class AddPodcastsCategories extends Migration
|
||||
class AddPodcastsCategories extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->addField([
|
||||
'podcast_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'category_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
]);
|
||||
|
|
@ -34,6 +35,7 @@ class AddPodcastsCategories extends Migration
|
|||
$this->forge->createTable('podcasts_categories');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('podcasts_categories');
|
||||
|
|
@ -10,65 +10,66 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
use Override;
|
||||
|
||||
class AddClips extends Migration
|
||||
class AddClips extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'podcast_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'episode_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'start_time' => [
|
||||
'type' => 'DECIMAL(8,3)',
|
||||
'type' => 'DECIMAL(8,3)',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'duration' => [
|
||||
// clip duration cannot be higher than 9999,999 seconds ~ 2.77 hours
|
||||
'type' => 'DECIMAL(7,3)',
|
||||
'type' => 'DECIMAL(7,3)',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'title' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 128,
|
||||
],
|
||||
'type' => [
|
||||
'type' => 'ENUM',
|
||||
'type' => 'ENUM',
|
||||
'constraint' => ['audio', 'video'],
|
||||
],
|
||||
'media_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'metadata' => [
|
||||
'type' => 'JSON',
|
||||
'null' => true,
|
||||
],
|
||||
'status' => [
|
||||
'type' => 'ENUM',
|
||||
'type' => 'ENUM',
|
||||
'constraint' => ['queued', 'pending', 'running', 'passed', 'failed'],
|
||||
],
|
||||
'logs' => [
|
||||
'type' => 'TEXT',
|
||||
],
|
||||
'created_by' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'updated_by' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'job_started_at' => [
|
||||
|
|
@ -96,6 +97,7 @@ class AddClips extends Migration
|
|||
$this->forge->createTable('clips');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('clips');
|
||||
|
|
|
|||
|
|
@ -12,47 +12,47 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
use Override;
|
||||
|
||||
class AddPersons extends Migration
|
||||
class AddPersons extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'full_name' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 192,
|
||||
'comment' => 'This is the full name or alias of the person.',
|
||||
'comment' => 'This is the full name or alias of the person.',
|
||||
],
|
||||
'unique_name' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 192,
|
||||
'comment' => 'This is the slug name or alias of the person.',
|
||||
'unique' => true,
|
||||
'comment' => 'This is the slug name or alias of the person.',
|
||||
'unique' => true,
|
||||
],
|
||||
'information_url' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 512,
|
||||
'comment' =>
|
||||
'The url to a relevant resource of information about the person, such as a homepage or third-party profile platform.',
|
||||
'null' => true,
|
||||
'comment' => 'The url to a relevant resource of information about the person, such as a homepage or third-party profile platform.',
|
||||
'null' => true,
|
||||
],
|
||||
'avatar_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'created_by' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'updated_by' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'created_at' => [
|
||||
|
|
@ -70,6 +70,7 @@ class AddPersons extends Migration
|
|||
$this->forge->createTable('persons');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('persons');
|
||||
|
|
@ -12,32 +12,33 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
use Override;
|
||||
|
||||
class AddPodcastsPersons extends Migration
|
||||
class AddPodcastsPersons extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'podcast_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'person_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'person_group' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 32,
|
||||
],
|
||||
'person_role' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 32,
|
||||
],
|
||||
]);
|
||||
|
|
@ -48,6 +49,7 @@ class AddPodcastsPersons extends Migration
|
|||
$this->forge->createTable('podcasts_persons');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('podcasts_persons');
|
||||
|
|
@ -12,36 +12,37 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
use Override;
|
||||
|
||||
class AddEpisodesPersons extends Migration
|
||||
class AddEpisodesPersons extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'podcast_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'episode_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'person_id' => [
|
||||
'type' => 'INT',
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'person_group' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 32,
|
||||
],
|
||||
'person_role' => [
|
||||
'type' => 'VARCHAR',
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 32,
|
||||
],
|
||||
]);
|
||||
|
|
@ -53,6 +54,7 @@ class AddEpisodesPersons extends Migration
|
|||
$this->forge->createTable('episodes_persons');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('episodes_persons');
|
||||
|
|
@ -10,10 +10,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
use Override;
|
||||
|
||||
class AddCreditsView extends Migration
|
||||
class AddCreditsView extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
// Creates View for credit UNION query
|
||||
|
|
@ -22,7 +23,7 @@ class AddCreditsView extends Migration
|
|||
$podcastPersonsTable = $this->db->prefixTable('podcasts_persons');
|
||||
$episodePersonsTable = $this->db->prefixTable('episodes_persons');
|
||||
$episodesTable = $this->db->prefixTable('episodes');
|
||||
$createQuery = <<<CODE_SAMPLE
|
||||
$createQuery = <<<SQL
|
||||
CREATE VIEW `{$viewName}` AS
|
||||
SELECT `person_group`, `person_id`, `full_name`, `person_role`, `podcast_id`, NULL AS `episode_id` FROM `{$podcastPersonsTable}`
|
||||
INNER JOIN `{$personsTable}`
|
||||
|
|
@ -35,10 +36,11 @@ class AddCreditsView extends Migration
|
|||
ON (`episode_id`=`{$episodesTable}`.`id`)
|
||||
WHERE `{$episodesTable}`.published_at <= UTC_TIMESTAMP()
|
||||
ORDER BY `person_group`, `full_name`, `person_role`, `podcast_id`, `episode_id`;
|
||||
CODE_SAMPLE;
|
||||
SQL;
|
||||
$this->db->query($createQuery);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$viewName = $this->db->prefixTable('credits');
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddEpisodeIdToPosts Adds episode_id field to posts table in database
|
||||
*
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use Override;
|
||||
|
||||
class AddEpisodeIdToPosts extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$prefix = $this->db->getPrefix();
|
||||
|
||||
$this->forge->addColumn('fediverse_posts', [
|
||||
'episode_id' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
'after' => 'replies_count',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->forge->addForeignKey(
|
||||
'episode_id',
|
||||
'episodes',
|
||||
'id',
|
||||
'',
|
||||
'CASCADE',
|
||||
$prefix . 'fediverse_posts_episode_id_foreign',
|
||||
);
|
||||
$this->forge->processIndexes('fediverse_posts');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$prefix = $this->db->getPrefix();
|
||||
|
||||
$this->forge->dropForeignKey('fediverse_posts', $prefix . 'fediverse_posts_episode_id_foreign');
|
||||
$this->forge->dropColumn('fediverse_posts', 'episode_id');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddCreatedByToPosts Adds created_by field to posts table in database
|
||||
*
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use Override;
|
||||
|
||||
class AddCreatedByToPosts extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$prefix = $this->db->getPrefix();
|
||||
|
||||
$this->forge->addColumn('fediverse_posts', [
|
||||
'created_by' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
'after' => 'episode_id',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->forge->addForeignKey(
|
||||
'created_by',
|
||||
'users',
|
||||
'id',
|
||||
'',
|
||||
'CASCADE',
|
||||
$prefix . 'fediverse_posts_created_by_foreign',
|
||||
);
|
||||
$this->forge->processIndexes('fediverse_posts');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$prefix = $this->db->getPrefix();
|
||||
|
||||
$this->forge->dropForeignKey('fediverse_posts', $prefix . 'fediverse_posts_created_by_foreign');
|
||||
$this->forge->dropColumn('fediverse_posts', 'created_by');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use Override;
|
||||
|
||||
class AddFullTextSearchIndexes extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$prefix = $this->db->getPrefix();
|
||||
|
||||
$createQuery = <<<SQL
|
||||
ALTER TABLE {$prefix}episodes DROP INDEX title;
|
||||
SQL;
|
||||
|
||||
$this->db->query($createQuery);
|
||||
|
||||
$createQuery = <<<SQL
|
||||
ALTER TABLE {$prefix}episodes
|
||||
ADD FULLTEXT episodes_search (title, description_markdown, slug, location_name);
|
||||
SQL;
|
||||
|
||||
$this->db->query($createQuery);
|
||||
|
||||
$createQuery = <<<SQL
|
||||
ALTER TABLE {$prefix}podcasts
|
||||
ADD FULLTEXT podcasts_search (title, description_markdown, handle, location_name);
|
||||
SQL;
|
||||
|
||||
$this->db->query($createQuery);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$prefix = $this->db->getPrefix();
|
||||
|
||||
$createQuery = <<<SQL
|
||||
ALTER TABLE {$prefix}episodes
|
||||
DROP INDEX episodes_search;
|
||||
SQL;
|
||||
|
||||
$this->db->query($createQuery);
|
||||
|
||||
$createQuery = <<<SQL
|
||||
ALTER TABLE {$prefix}podcasts
|
||||
DROP INDEX podcasts_search;
|
||||
SQL;
|
||||
|
||||
$this->db->query($createQuery);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use Override;
|
||||
|
||||
class AddEpisodePreviewId extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$fields = [
|
||||
'preview_id' => [
|
||||
'type' => 'BINARY',
|
||||
'constraint' => 16,
|
||||
'after' => 'podcast_id',
|
||||
],
|
||||
];
|
||||
|
||||
$this->forge->addColumn('episodes', $fields);
|
||||
|
||||
// set preview_id as unique key
|
||||
$prefix = $this->db->getPrefix();
|
||||
$uniquePreviewId = <<<CODE_SAMPLE
|
||||
ALTER TABLE `{$prefix}episodes`
|
||||
ADD CONSTRAINT `preview_id` UNIQUE (`preview_id`);
|
||||
CODE_SAMPLE;
|
||||
|
||||
$this->db->query($uniquePreviewId);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$fields = ['preview_id'];
|
||||
$this->forge->dropColumn('episodes', $fields);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddPodcastsOwnerEmailRemovedFromFeed adds is_owner_email_removed_from_feed field to podcast table in database
|
||||
*
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use Override;
|
||||
|
||||
class AddPodcastsOwnerEmailRemovedFromFeed extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$fields = [
|
||||
'is_owner_email_removed_from_feed' => [
|
||||
'type' => 'BOOLEAN',
|
||||
'null' => false,
|
||||
'default' => 0,
|
||||
'after' => 'owner_email',
|
||||
],
|
||||
];
|
||||
|
||||
$this->forge->addColumn('podcasts', $fields);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$fields = ['is_owner_email_removed_from_feed'];
|
||||
$this->forge->dropColumn('podcasts', $fields);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddPodcastsMediumField adds medium field to podcast table in database
|
||||
*
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use Override;
|
||||
|
||||
class AddPodcastsMediumField extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$fields = [
|
||||
'medium' => [
|
||||
'type' => "ENUM('podcast','music','audiobook')",
|
||||
'null' => false,
|
||||
'default' => 'podcast',
|
||||
'after' => 'type',
|
||||
],
|
||||
];
|
||||
|
||||
$this->forge->addColumn('podcasts', $fields);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$fields = ['medium'];
|
||||
$this->forge->dropColumn('podcasts', $fields);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddPodcastsVerifyTxtField adds 1 field to podcast table in database to support podcast:txt tag
|
||||
*
|
||||
* @copyright 2024 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use Override;
|
||||
|
||||
class AddPodcastsVerifyTxtField extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$fields = [
|
||||
'verify_txt' => [
|
||||
'type' => 'TEXT',
|
||||
'null' => true,
|
||||
'after' => 'location_osm',
|
||||
],
|
||||
];
|
||||
|
||||
$this->forge->addColumn('podcasts', $fields);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropColumn('podcasts', 'verify_txt');
|
||||
}
|
||||
}
|
||||
157
app/Database/Migrations/2024-04-18-180000_refactor_platforms.php
Normal file
157
app/Database/Migrations/2024-04-18-180000_refactor_platforms.php
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
use Override;
|
||||
|
||||
class RefactorPlatforms extends Migration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'podcast_id' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'type' => [
|
||||
'type' => 'ENUM',
|
||||
'constraint' => ['podcasting', 'social', 'funding'],
|
||||
'after' => 'podcast_id',
|
||||
],
|
||||
'slug' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 32,
|
||||
],
|
||||
'link_url' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 512,
|
||||
],
|
||||
'account_id' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 128,
|
||||
'null' => true,
|
||||
],
|
||||
'is_visible' => [
|
||||
'type' => 'TINYINT',
|
||||
'constraint' => 1,
|
||||
'default' => 0,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->forge->addPrimaryKey('id');
|
||||
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE', 'platforms_podcast_id_foreign');
|
||||
$this->forge->addUniqueKey(['podcast_id', 'type', 'slug']);
|
||||
$this->forge->createTable('platforms_temp');
|
||||
|
||||
$platformsData = $this->db->table('podcasts_platforms')
|
||||
->select('podcasts_platforms.*, type')
|
||||
->join('platforms', 'platforms.slug = podcasts_platforms.platform_slug')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$data = [];
|
||||
foreach ($platformsData as $platformData) {
|
||||
$data[] = [
|
||||
'podcast_id' => $platformData['podcast_id'],
|
||||
'type' => $platformData['type'],
|
||||
'slug' => $platformData['platform_slug'],
|
||||
'link_url' => $platformData['link_url'],
|
||||
'account_id' => $platformData['account_id'],
|
||||
'is_visible' => $platformData['is_visible'],
|
||||
];
|
||||
}
|
||||
|
||||
if ($data !== []) {
|
||||
$this->db->table('platforms_temp')
|
||||
->insertBatch($data);
|
||||
}
|
||||
|
||||
$this->forge->dropTable('platforms');
|
||||
|
||||
$this->forge->dropTable('podcasts_platforms');
|
||||
|
||||
$this->forge->renameTable('platforms_temp', 'platforms');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
// delete platforms
|
||||
$this->forge->dropTable('platforms');
|
||||
|
||||
// recreate platforms and podcasts_platforms tables
|
||||
$this->forge->addField([
|
||||
'slug' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 32,
|
||||
],
|
||||
'type' => [
|
||||
'type' => 'ENUM',
|
||||
'constraint' => ['podcasting', 'social', 'funding'],
|
||||
],
|
||||
'label' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 32,
|
||||
],
|
||||
'home_url' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 255,
|
||||
],
|
||||
'submit_url' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 512,
|
||||
'null' => true,
|
||||
],
|
||||
]);
|
||||
$this->forge->addField('`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP()');
|
||||
$this->forge->addField(
|
||||
'`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP()',
|
||||
);
|
||||
$this->forge->addPrimaryKey('slug');
|
||||
$this->forge->createTable('platforms');
|
||||
|
||||
$this->forge->addField([
|
||||
'podcast_id' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'platform_slug' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 32,
|
||||
],
|
||||
'link_url' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 512,
|
||||
],
|
||||
'account_id' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 128,
|
||||
'null' => true,
|
||||
],
|
||||
'is_visible' => [
|
||||
'type' => 'TINYINT',
|
||||
'constraint' => 1,
|
||||
'default' => 0,
|
||||
],
|
||||
'is_on_embed' => [
|
||||
'type' => 'TINYINT',
|
||||
'constraint' => 1,
|
||||
'default' => 0,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->forge->addPrimaryKey(['podcast_id', 'platform_slug']);
|
||||
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('platform_slug', 'platforms', 'slug', 'CASCADE');
|
||||
$this->forge->createTable('podcasts_platforms');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
use Override;
|
||||
|
||||
/**
|
||||
* CodeIgniter 4.5.1 introduces new DataCaster class that breaks deserialization of import queue tasks.
|
||||
* This just removes them altogether.
|
||||
*/
|
||||
class ClearImportQueue extends Migration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
service('settings')->forget('Import.queue');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AddEpisodeDownloadsCount extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$fields = [
|
||||
'downloads_count' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'default' => 0,
|
||||
'after' => 'is_published_on_hubs',
|
||||
],
|
||||
];
|
||||
|
||||
$this->forge->addColumn('episodes', $fields);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropColumn('episodes', 'downloads_count');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddPodcastsMediumField adds medium field to podcast table in database
|
||||
*
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use Override;
|
||||
|
||||
class DropDeprecatedPodcastsFields extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
// TODO: migrate data
|
||||
|
||||
$this->forge->dropColumn(
|
||||
'podcasts',
|
||||
'episode_description_footer_markdown,episode_description_footer_html,is_owner_email_removed_from_feed,medium,payment_pointer,verify_txt,custom_rss,partner_id,partner_link_url,partner_image_url',
|
||||
);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$fields = [
|
||||
'episode_description_footer_markdown' => [
|
||||
'type' => 'TEXT',
|
||||
'null' => true,
|
||||
],
|
||||
'episode_description_footer_html' => [
|
||||
'type' => 'TEXT',
|
||||
'null' => true,
|
||||
],
|
||||
'is_owner_email_removed_from_feed' => [
|
||||
'type' => 'BOOLEAN',
|
||||
'null' => false,
|
||||
'default' => 0,
|
||||
'after' => 'owner_email',
|
||||
],
|
||||
'medium' => [
|
||||
'type' => "ENUM('podcast','music','audiobook')",
|
||||
'null' => false,
|
||||
'default' => 'podcast',
|
||||
'after' => 'type',
|
||||
],
|
||||
'payment_pointer' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 128,
|
||||
'comment' => 'Wallet address for Web Monetization payments',
|
||||
'null' => true,
|
||||
],
|
||||
'verify_txt' => [
|
||||
'type' => 'TEXT',
|
||||
'null' => true,
|
||||
'after' => 'location_osm',
|
||||
],
|
||||
'custom_rss' => [
|
||||
'type' => 'JSON',
|
||||
'null' => true,
|
||||
],
|
||||
'partner_id' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 32,
|
||||
'null' => true,
|
||||
],
|
||||
'partner_link_url' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 512,
|
||||
'null' => true,
|
||||
],
|
||||
'partner_image_url' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 512,
|
||||
'null' => true,
|
||||
],
|
||||
];
|
||||
|
||||
$this->forge->addColumn('podcasts', $fields);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddPodcastsMediumField adds medium field to podcast table in database
|
||||
*
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use Override;
|
||||
|
||||
class DropDeprecatedEpisodesFields extends BaseMigration
|
||||
{
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->dropColumn('episodes', 'custom_rss');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
$fields = [
|
||||
'custom_rss' => [
|
||||
'type' => 'JSON',
|
||||
'null' => true,
|
||||
],
|
||||
];
|
||||
|
||||
$this->forge->addColumn('episodes', $fields);
|
||||
}
|
||||
}
|
||||
37
app/Database/Migrations/BaseMigration.php
Normal file
37
app/Database/Migrations/BaseMigration.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddCreatedByToPosts Adds created_by field to posts table in database
|
||||
*
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
use CodeIgniter\Database\Migration;
|
||||
use Override;
|
||||
|
||||
class BaseMigration extends Migration
|
||||
{
|
||||
/**
|
||||
* Database Connection instance
|
||||
*
|
||||
* @var BaseConnection
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
#[Override]
|
||||
public function up(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function down(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -13,14 +13,14 @@ declare(strict_types=1);
|
|||
namespace App\Database\Seeds;
|
||||
|
||||
use CodeIgniter\Database\Seeder;
|
||||
use Override;
|
||||
|
||||
class AppSeeder extends Seeder
|
||||
{
|
||||
#[Override]
|
||||
public function run(): void
|
||||
{
|
||||
$this->call('AuthSeeder');
|
||||
$this->call('CategorySeeder');
|
||||
$this->call('LanguageSeeder');
|
||||
$this->call('PlatformSeeder');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,328 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class PermissionSeeder Inserts permissions
|
||||
*
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Database\Seeds;
|
||||
|
||||
use CodeIgniter\Database\Seeder;
|
||||
|
||||
class AuthSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* @var array<string, string>[]
|
||||
*/
|
||||
protected array $groups = [
|
||||
[
|
||||
'name' => 'superadmin',
|
||||
'description' =>
|
||||
'Somebody who has access to all the castopod instance features',
|
||||
],
|
||||
[
|
||||
'name' => 'podcast_admin',
|
||||
'description' =>
|
||||
'Somebody who has access to all the features within a given podcast',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Build permissions array as a list of:
|
||||
*
|
||||
* ``` context => [ [action, description], [action, description], ... ] ```
|
||||
*
|
||||
* @var array<string, array<string, string|string[]>[]>
|
||||
*/
|
||||
protected array $permissions = [
|
||||
'settings' => [
|
||||
[
|
||||
'name' => 'view',
|
||||
'description' => 'View settings options',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'manage',
|
||||
'description' => 'Update general settings',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
],
|
||||
'users' => [
|
||||
[
|
||||
'name' => 'create',
|
||||
'description' => 'Create a user',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'list',
|
||||
'description' => 'List all users',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'view',
|
||||
'description' => 'View any user info',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'manage_authorizations',
|
||||
'description' => 'Add or remove roles/permissions to a user',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'manage_bans',
|
||||
'description' => 'Ban / unban a user',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'force_pass_reset',
|
||||
'description' =>
|
||||
'Force a user to update his password upon next login',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'delete',
|
||||
'description' =>
|
||||
'Delete user without removing him from database',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'delete_permanently',
|
||||
'description' =>
|
||||
'Delete all occurrences of a user from the database',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
],
|
||||
'pages' => [
|
||||
[
|
||||
'name' => 'manage',
|
||||
'description' => 'List / create / edit / delete pages',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
],
|
||||
'podcasts' => [
|
||||
[
|
||||
'name' => 'create',
|
||||
'description' => 'Add a new podcast',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'import',
|
||||
'description' => 'Import a new podcast from an external feed',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'list',
|
||||
'description' => 'List all podcasts and their episodes',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'view',
|
||||
'description' => 'View any podcast and their contributors list',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'delete',
|
||||
'description' => 'Delete any podcast from the database',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
],
|
||||
'episodes' => [
|
||||
[
|
||||
'name' => 'list',
|
||||
'description' => 'List all episodes of any podcast',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'view',
|
||||
'description' => 'View any episode of any podcast',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
],
|
||||
'podcast' => [
|
||||
[
|
||||
'name' => 'view',
|
||||
'description' => 'View a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'edit',
|
||||
'description' => 'Edit a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'manage_subscriptions',
|
||||
'description' =>
|
||||
'Add / edit / remove podcast subscriptions',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'manage_contributors',
|
||||
'description' =>
|
||||
'Add / remove contributors to a podcast and edit their roles',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'manage_platforms',
|
||||
'description' => 'Set / remove platform links of a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'manage_publications',
|
||||
'description' =>
|
||||
'Publish a podcast and publish / unpublish its episodes & posts',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'interact_as',
|
||||
'description' =>
|
||||
'Interact as the podcast to favourite / share or reply to posts.',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
],
|
||||
'podcast_episodes' => [
|
||||
[
|
||||
'name' => 'list',
|
||||
'description' => 'List all episodes of a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'view',
|
||||
'description' => 'View any episode of a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'create',
|
||||
'description' => 'Add new episodes for a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'edit',
|
||||
'description' => 'Edit an episode of a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'delete',
|
||||
'description' =>
|
||||
'Delete all occurrences of an episode of a podcast from the database',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
],
|
||||
'person' => [
|
||||
[
|
||||
'name' => 'create',
|
||||
'description' => 'Add a new person',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'list',
|
||||
'description' => 'List all persons',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'view',
|
||||
'description' => 'View any person',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'edit',
|
||||
'description' => 'Edit a person',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'delete',
|
||||
'description' =>
|
||||
'Delete permanently any person from the database',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
],
|
||||
'fediverse' => [
|
||||
[
|
||||
'name' => 'block_actors',
|
||||
'description' =>
|
||||
'Block fediverse actors from interacting with the instance.',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
[
|
||||
'name' => 'block_domains',
|
||||
'description' =>
|
||||
'Block fediverse domains from interacting with the instance.',
|
||||
'has_permission' => ['superadmin'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$groupId = 0;
|
||||
$dataGroups = [];
|
||||
foreach ($this->groups as $group) {
|
||||
$dataGroups[] = [
|
||||
'id' => ++$groupId,
|
||||
'name' => $group['name'],
|
||||
'description' => $group['description'],
|
||||
];
|
||||
}
|
||||
|
||||
// Map permissions to a format the `auth_permissions` table expects
|
||||
$dataPermissions = [];
|
||||
$dataGroupsPermissions = [];
|
||||
$permissionId = 0;
|
||||
foreach ($this->permissions as $context => $actions) {
|
||||
foreach ($actions as $action) {
|
||||
$dataPermissions[] = [
|
||||
'id' => ++$permissionId,
|
||||
'name' => $context . '-' . $action['name'],
|
||||
'description' => $action['description'],
|
||||
];
|
||||
|
||||
foreach ($action['has_permission'] as $role) {
|
||||
// link permission to specified groups
|
||||
$dataGroupsPermissions[] = [
|
||||
'group_id' => $this->getGroupIdByName($role, $dataGroups),
|
||||
'permission_id' => $permissionId,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->db->table('auth_groups')->countAll() < count($dataPermissions)) {
|
||||
$this->db
|
||||
->table('auth_permissions')
|
||||
->ignore(true)
|
||||
->insertBatch($dataPermissions);
|
||||
}
|
||||
|
||||
if ($this->db->table('auth_groups')->countAll() < count($dataGroups)) {
|
||||
$this->db
|
||||
->table('auth_groups')
|
||||
->ignore(true)
|
||||
->insertBatch($dataGroups);
|
||||
}
|
||||
|
||||
if ($this->db->table('auth_groups_permissions')->countAll() < count($dataGroupsPermissions)) {
|
||||
$this->db
|
||||
->table('auth_groups_permissions')
|
||||
->ignore(true)
|
||||
->insertBatch($dataGroupsPermissions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string|int>[] $dataGroups
|
||||
*/
|
||||
public static function getGroupIdByName(string $name, array $dataGroups): ?int
|
||||
{
|
||||
foreach ($dataGroups as $group) {
|
||||
if ($group['name'] === $name) {
|
||||
return $group['id'];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -13,780 +13,782 @@ declare(strict_types=1);
|
|||
namespace App\Database\Seeds;
|
||||
|
||||
use CodeIgniter\Database\Seeder;
|
||||
use Override;
|
||||
|
||||
class CategorySeeder extends Seeder
|
||||
{
|
||||
#[Override]
|
||||
public function run(): void
|
||||
{
|
||||
$data = [
|
||||
[
|
||||
'id' => 1,
|
||||
'parent_id' => null,
|
||||
'code' => 'arts',
|
||||
'apple_category' => 'Arts',
|
||||
'id' => 1,
|
||||
'parent_id' => null,
|
||||
'code' => 'arts',
|
||||
'apple_category' => 'Arts',
|
||||
'google_category' => 'Arts',
|
||||
],
|
||||
[
|
||||
'id' => 2,
|
||||
'parent_id' => null,
|
||||
'code' => 'business',
|
||||
'apple_category' => 'Business',
|
||||
'id' => 2,
|
||||
'parent_id' => null,
|
||||
'code' => 'business',
|
||||
'apple_category' => 'Business',
|
||||
'google_category' => 'Business',
|
||||
],
|
||||
[
|
||||
'id' => 3,
|
||||
'parent_id' => null,
|
||||
'code' => 'comedy',
|
||||
'apple_category' => 'Comedy',
|
||||
'id' => 3,
|
||||
'parent_id' => null,
|
||||
'code' => 'comedy',
|
||||
'apple_category' => 'Comedy',
|
||||
'google_category' => 'Comedy',
|
||||
],
|
||||
[
|
||||
'id' => 4,
|
||||
'parent_id' => null,
|
||||
'code' => 'education',
|
||||
'apple_category' => 'Education',
|
||||
'id' => 4,
|
||||
'parent_id' => null,
|
||||
'code' => 'education',
|
||||
'apple_category' => 'Education',
|
||||
'google_category' => 'Education',
|
||||
],
|
||||
[
|
||||
'id' => 5,
|
||||
'parent_id' => null,
|
||||
'code' => 'fiction',
|
||||
'apple_category' => 'Fiction',
|
||||
'id' => 5,
|
||||
'parent_id' => null,
|
||||
'code' => 'fiction',
|
||||
'apple_category' => 'Fiction',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 6,
|
||||
'parent_id' => null,
|
||||
'code' => 'government',
|
||||
'apple_category' => 'Government',
|
||||
'id' => 6,
|
||||
'parent_id' => null,
|
||||
'code' => 'government',
|
||||
'apple_category' => 'Government',
|
||||
'google_category' => 'Government & Organizations',
|
||||
],
|
||||
[
|
||||
'id' => 7,
|
||||
'parent_id' => null,
|
||||
'code' => 'health_and_fitness',
|
||||
'apple_category' => 'Health & Fitness',
|
||||
'id' => 7,
|
||||
'parent_id' => null,
|
||||
'code' => 'health_and_fitness',
|
||||
'apple_category' => 'Health & Fitness',
|
||||
'google_category' => 'Health',
|
||||
],
|
||||
[
|
||||
'id' => 8,
|
||||
'parent_id' => null,
|
||||
'code' => 'history',
|
||||
'apple_category' => 'History',
|
||||
'id' => 8,
|
||||
'parent_id' => null,
|
||||
'code' => 'history',
|
||||
'apple_category' => 'History',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 9,
|
||||
'parent_id' => null,
|
||||
'code' => 'kids_and_family',
|
||||
'apple_category' => 'Kids & Family',
|
||||
'id' => 9,
|
||||
'parent_id' => null,
|
||||
'code' => 'kids_and_family',
|
||||
'apple_category' => 'Kids & Family',
|
||||
'google_category' => 'Kids & Family',
|
||||
],
|
||||
[
|
||||
'id' => 10,
|
||||
'parent_id' => null,
|
||||
'code' => 'leisure',
|
||||
'apple_category' => 'Leisure',
|
||||
'id' => 10,
|
||||
'parent_id' => null,
|
||||
'code' => 'leisure',
|
||||
'apple_category' => 'Leisure',
|
||||
'google_category' => 'Games & Hobbies',
|
||||
],
|
||||
[
|
||||
'id' => 11,
|
||||
'parent_id' => null,
|
||||
'code' => 'music',
|
||||
'apple_category' => 'Music',
|
||||
'id' => 11,
|
||||
'parent_id' => null,
|
||||
'code' => 'music',
|
||||
'apple_category' => 'Music',
|
||||
'google_category' => 'Music',
|
||||
],
|
||||
[
|
||||
'id' => 12,
|
||||
'parent_id' => null,
|
||||
'code' => 'news',
|
||||
'apple_category' => 'News',
|
||||
'id' => 12,
|
||||
'parent_id' => null,
|
||||
'code' => 'news',
|
||||
'apple_category' => 'News',
|
||||
'google_category' => 'News & Politics',
|
||||
],
|
||||
[
|
||||
'id' => 13,
|
||||
'parent_id' => null,
|
||||
'code' => 'religion_and_spirituality',
|
||||
'apple_category' => 'Religion & Spirituality',
|
||||
'id' => 13,
|
||||
'parent_id' => null,
|
||||
'code' => 'religion_and_spirituality',
|
||||
'apple_category' => 'Religion & Spirituality',
|
||||
'google_category' => 'Religion & Spirituality',
|
||||
],
|
||||
[
|
||||
'id' => 14,
|
||||
'parent_id' => null,
|
||||
'code' => 'science',
|
||||
'apple_category' => 'Science',
|
||||
'id' => 14,
|
||||
'parent_id' => null,
|
||||
'code' => 'science',
|
||||
'apple_category' => 'Science',
|
||||
'google_category' => 'Science & Medicine',
|
||||
],
|
||||
[
|
||||
'id' => 15,
|
||||
'parent_id' => null,
|
||||
'code' => 'society_and_culture',
|
||||
'apple_category' => 'Society & Culture',
|
||||
'id' => 15,
|
||||
'parent_id' => null,
|
||||
'code' => 'society_and_culture',
|
||||
'apple_category' => 'Society & Culture',
|
||||
'google_category' => 'Society & Culture',
|
||||
],
|
||||
[
|
||||
'id' => 16,
|
||||
'parent_id' => null,
|
||||
'code' => 'sports',
|
||||
'apple_category' => 'Sports',
|
||||
'id' => 16,
|
||||
'parent_id' => null,
|
||||
'code' => 'sports',
|
||||
'apple_category' => 'Sports',
|
||||
'google_category' => 'Sports & Recreation',
|
||||
],
|
||||
[
|
||||
'id' => 17,
|
||||
'parent_id' => null,
|
||||
'code' => 'technology',
|
||||
'apple_category' => 'Technology',
|
||||
'id' => 17,
|
||||
'parent_id' => null,
|
||||
'code' => 'technology',
|
||||
'apple_category' => 'Technology',
|
||||
'google_category' => 'Technology',
|
||||
],
|
||||
[
|
||||
'id' => 18,
|
||||
'parent_id' => null,
|
||||
'code' => 'true_crime',
|
||||
'apple_category' => 'True Crime',
|
||||
'id' => 18,
|
||||
'parent_id' => null,
|
||||
'code' => 'true_crime',
|
||||
'apple_category' => 'True Crime',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 19,
|
||||
'parent_id' => null,
|
||||
'code' => 'tv_and_film',
|
||||
'apple_category' => 'TV & Film',
|
||||
'id' => 19,
|
||||
'parent_id' => null,
|
||||
'code' => 'tv_and_film',
|
||||
'apple_category' => 'TV & Film',
|
||||
'google_category' => 'TV & Film',
|
||||
],
|
||||
[
|
||||
'id' => 20,
|
||||
'parent_id' => 1,
|
||||
'code' => 'books',
|
||||
'apple_category' => 'Books',
|
||||
'id' => 20,
|
||||
'parent_id' => 1,
|
||||
'code' => 'books',
|
||||
'apple_category' => 'Books',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 21,
|
||||
'parent_id' => 1,
|
||||
'code' => 'design',
|
||||
'apple_category' => 'Design',
|
||||
'id' => 21,
|
||||
'parent_id' => 1,
|
||||
'code' => 'design',
|
||||
'apple_category' => 'Design',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 22,
|
||||
'parent_id' => 1,
|
||||
'code' => 'fashion_and_beauty',
|
||||
'apple_category' => 'Fashion & Beauty',
|
||||
'id' => 22,
|
||||
'parent_id' => 1,
|
||||
'code' => 'fashion_and_beauty',
|
||||
'apple_category' => 'Fashion & Beauty',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 23,
|
||||
'parent_id' => 1,
|
||||
'code' => 'food',
|
||||
'apple_category' => 'Food',
|
||||
'id' => 23,
|
||||
'parent_id' => 1,
|
||||
'code' => 'food',
|
||||
'apple_category' => 'Food',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 24,
|
||||
'parent_id' => 1,
|
||||
'code' => 'performing_arts',
|
||||
'apple_category' => 'Performing Arts',
|
||||
'id' => 24,
|
||||
'parent_id' => 1,
|
||||
'code' => 'performing_arts',
|
||||
'apple_category' => 'Performing Arts',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 25,
|
||||
'parent_id' => 1,
|
||||
'code' => 'visual_arts',
|
||||
'apple_category' => 'Visual Arts',
|
||||
'id' => 25,
|
||||
'parent_id' => 1,
|
||||
'code' => 'visual_arts',
|
||||
'apple_category' => 'Visual Arts',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 26,
|
||||
'parent_id' => 2,
|
||||
'code' => 'careers',
|
||||
'apple_category' => 'Careers',
|
||||
'id' => 26,
|
||||
'parent_id' => 2,
|
||||
'code' => 'careers',
|
||||
'apple_category' => 'Careers',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 27,
|
||||
'parent_id' => 2,
|
||||
'code' => 'entrepreneurship',
|
||||
'apple_category' => 'Entrepreneurship',
|
||||
'id' => 27,
|
||||
'parent_id' => 2,
|
||||
'code' => 'entrepreneurship',
|
||||
'apple_category' => 'Entrepreneurship',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 28,
|
||||
'parent_id' => 2,
|
||||
'code' => 'investing',
|
||||
'apple_category' => 'Investing',
|
||||
'id' => 28,
|
||||
'parent_id' => 2,
|
||||
'code' => 'investing',
|
||||
'apple_category' => 'Investing',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 29,
|
||||
'parent_id' => 2,
|
||||
'code' => 'management',
|
||||
'apple_category' => 'Management',
|
||||
'id' => 29,
|
||||
'parent_id' => 2,
|
||||
'code' => 'management',
|
||||
'apple_category' => 'Management',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 30,
|
||||
'parent_id' => 2,
|
||||
'code' => 'marketing',
|
||||
'apple_category' => 'Marketing',
|
||||
'id' => 30,
|
||||
'parent_id' => 2,
|
||||
'code' => 'marketing',
|
||||
'apple_category' => 'Marketing',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 31,
|
||||
'parent_id' => 2,
|
||||
'code' => 'non_profit',
|
||||
'apple_category' => 'Non-Profit',
|
||||
'id' => 31,
|
||||
'parent_id' => 2,
|
||||
'code' => 'non_profit',
|
||||
'apple_category' => 'Non-Profit',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 32,
|
||||
'parent_id' => 3,
|
||||
'code' => 'comedy_interviews',
|
||||
'apple_category' => 'Comedy Interviews',
|
||||
'id' => 32,
|
||||
'parent_id' => 3,
|
||||
'code' => 'comedy_interviews',
|
||||
'apple_category' => 'Comedy Interviews',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 33,
|
||||
'parent_id' => 3,
|
||||
'code' => 'improv',
|
||||
'apple_category' => 'Improv',
|
||||
'id' => 33,
|
||||
'parent_id' => 3,
|
||||
'code' => 'improv',
|
||||
'apple_category' => 'Improv',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 34,
|
||||
'parent_id' => 3,
|
||||
'code' => 'stand_up',
|
||||
'apple_category' => 'Stand-Up',
|
||||
'id' => 34,
|
||||
'parent_id' => 3,
|
||||
'code' => 'stand_up',
|
||||
'apple_category' => 'Stand-Up',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 35,
|
||||
'parent_id' => 4,
|
||||
'code' => 'courses',
|
||||
'apple_category' => 'Courses',
|
||||
'id' => 35,
|
||||
'parent_id' => 4,
|
||||
'code' => 'courses',
|
||||
'apple_category' => 'Courses',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 36,
|
||||
'parent_id' => 4,
|
||||
'code' => 'how_to',
|
||||
'apple_category' => 'How To',
|
||||
'id' => 36,
|
||||
'parent_id' => 4,
|
||||
'code' => 'how_to',
|
||||
'apple_category' => 'How To',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 37,
|
||||
'parent_id' => 4,
|
||||
'code' => 'language_learning',
|
||||
'apple_category' => 'Language Learning',
|
||||
'id' => 37,
|
||||
'parent_id' => 4,
|
||||
'code' => 'language_learning',
|
||||
'apple_category' => 'Language Learning',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 38,
|
||||
'parent_id' => 4,
|
||||
'code' => 'self_improvement',
|
||||
'apple_category' => 'Self-Improvement',
|
||||
'id' => 38,
|
||||
'parent_id' => 4,
|
||||
'code' => 'self_improvement',
|
||||
'apple_category' => 'Self-Improvement',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 39,
|
||||
'parent_id' => 5,
|
||||
'code' => 'comedy_fiction',
|
||||
'apple_category' => 'Comedy Fiction',
|
||||
'id' => 39,
|
||||
'parent_id' => 5,
|
||||
'code' => 'comedy_fiction',
|
||||
'apple_category' => 'Comedy Fiction',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 40,
|
||||
'parent_id' => 5,
|
||||
'code' => 'drama',
|
||||
'apple_category' => 'Drama',
|
||||
'id' => 40,
|
||||
'parent_id' => 5,
|
||||
'code' => 'drama',
|
||||
'apple_category' => 'Drama',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 41,
|
||||
'parent_id' => 5,
|
||||
'code' => 'science_fiction',
|
||||
'apple_category' => 'Science Fiction',
|
||||
'id' => 41,
|
||||
'parent_id' => 5,
|
||||
'code' => 'science_fiction',
|
||||
'apple_category' => 'Science Fiction',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 42,
|
||||
'parent_id' => 7,
|
||||
'code' => 'alternative_health',
|
||||
'apple_category' => 'Alternative Health',
|
||||
'id' => 42,
|
||||
'parent_id' => 7,
|
||||
'code' => 'alternative_health',
|
||||
'apple_category' => 'Alternative Health',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 43,
|
||||
'parent_id' => 7,
|
||||
'code' => 'fitness',
|
||||
'apple_category' => 'Fitness',
|
||||
'id' => 43,
|
||||
'parent_id' => 7,
|
||||
'code' => 'fitness',
|
||||
'apple_category' => 'Fitness',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 44,
|
||||
'parent_id' => 7,
|
||||
'code' => 'medicine',
|
||||
'apple_category' => 'Medicine',
|
||||
'id' => 44,
|
||||
'parent_id' => 7,
|
||||
'code' => 'medicine',
|
||||
'apple_category' => 'Medicine',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 45,
|
||||
'parent_id' => 7,
|
||||
'code' => 'mental_health',
|
||||
'apple_category' => 'Mental Health',
|
||||
'id' => 45,
|
||||
'parent_id' => 7,
|
||||
'code' => 'mental_health',
|
||||
'apple_category' => 'Mental Health',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 46,
|
||||
'parent_id' => 7,
|
||||
'code' => 'nutrition',
|
||||
'apple_category' => 'Nutrition',
|
||||
'id' => 46,
|
||||
'parent_id' => 7,
|
||||
'code' => 'nutrition',
|
||||
'apple_category' => 'Nutrition',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 47,
|
||||
'parent_id' => 7,
|
||||
'code' => 'sexuality',
|
||||
'apple_category' => 'Sexuality',
|
||||
'id' => 47,
|
||||
'parent_id' => 7,
|
||||
'code' => 'sexuality',
|
||||
'apple_category' => 'Sexuality',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 48,
|
||||
'parent_id' => 9,
|
||||
'code' => 'education_for_kids',
|
||||
'apple_category' => 'Education for Kids',
|
||||
'id' => 48,
|
||||
'parent_id' => 9,
|
||||
'code' => 'education_for_kids',
|
||||
'apple_category' => 'Education for Kids',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 49,
|
||||
'parent_id' => 9,
|
||||
'code' => 'parenting',
|
||||
'apple_category' => 'Parenting',
|
||||
'id' => 49,
|
||||
'parent_id' => 9,
|
||||
'code' => 'parenting',
|
||||
'apple_category' => 'Parenting',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 50,
|
||||
'parent_id' => 9,
|
||||
'code' => 'pets_and_animals',
|
||||
'apple_category' => 'Pets & Animals',
|
||||
'id' => 50,
|
||||
'parent_id' => 9,
|
||||
'code' => 'pets_and_animals',
|
||||
'apple_category' => 'Pets & Animals',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 51,
|
||||
'parent_id' => 9,
|
||||
'code' => 'stories_for_kids',
|
||||
'apple_category' => 'Stories for Kids',
|
||||
'id' => 51,
|
||||
'parent_id' => 9,
|
||||
'code' => 'stories_for_kids',
|
||||
'apple_category' => 'Stories for Kids',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 52,
|
||||
'parent_id' => 10,
|
||||
'code' => 'animation_and_manga',
|
||||
'apple_category' => 'Animation & Manga',
|
||||
'id' => 52,
|
||||
'parent_id' => 10,
|
||||
'code' => 'animation_and_manga',
|
||||
'apple_category' => 'Animation & Manga',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 53,
|
||||
'parent_id' => 10,
|
||||
'code' => 'automotive',
|
||||
'apple_category' => 'Automotive',
|
||||
'id' => 53,
|
||||
'parent_id' => 10,
|
||||
'code' => 'automotive',
|
||||
'apple_category' => 'Automotive',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 54,
|
||||
'parent_id' => 10,
|
||||
'code' => 'aviation',
|
||||
'apple_category' => 'Aviation',
|
||||
'id' => 54,
|
||||
'parent_id' => 10,
|
||||
'code' => 'aviation',
|
||||
'apple_category' => 'Aviation',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 55,
|
||||
'parent_id' => 10,
|
||||
'code' => 'crafts',
|
||||
'apple_category' => 'Crafts',
|
||||
'id' => 55,
|
||||
'parent_id' => 10,
|
||||
'code' => 'crafts',
|
||||
'apple_category' => 'Crafts',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 56,
|
||||
'parent_id' => 10,
|
||||
'code' => 'games',
|
||||
'apple_category' => 'Games',
|
||||
'id' => 56,
|
||||
'parent_id' => 10,
|
||||
'code' => 'games',
|
||||
'apple_category' => 'Games',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 57,
|
||||
'parent_id' => 10,
|
||||
'code' => 'hobbies',
|
||||
'apple_category' => 'Hobbies',
|
||||
'id' => 57,
|
||||
'parent_id' => 10,
|
||||
'code' => 'hobbies',
|
||||
'apple_category' => 'Hobbies',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 58,
|
||||
'parent_id' => 10,
|
||||
'code' => 'home_and_garden',
|
||||
'apple_category' => 'Home & Garden',
|
||||
'id' => 58,
|
||||
'parent_id' => 10,
|
||||
'code' => 'home_and_garden',
|
||||
'apple_category' => 'Home & Garden',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 59,
|
||||
'parent_id' => 10,
|
||||
'code' => 'video_games',
|
||||
'apple_category' => 'Video Games',
|
||||
'id' => 59,
|
||||
'parent_id' => 10,
|
||||
'code' => 'video_games',
|
||||
'apple_category' => 'Video Games',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 60,
|
||||
'parent_id' => 11,
|
||||
'code' => 'music_commentary',
|
||||
'apple_category' => 'Music Commentary',
|
||||
'id' => 60,
|
||||
'parent_id' => 11,
|
||||
'code' => 'music_commentary',
|
||||
'apple_category' => 'Music Commentary',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 61,
|
||||
'parent_id' => 11,
|
||||
'code' => 'music_history',
|
||||
'apple_category' => 'Music History',
|
||||
'id' => 61,
|
||||
'parent_id' => 11,
|
||||
'code' => 'music_history',
|
||||
'apple_category' => 'Music History',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 62,
|
||||
'parent_id' => 11,
|
||||
'code' => 'music_interviews',
|
||||
'apple_category' => 'Music Interviews',
|
||||
'id' => 62,
|
||||
'parent_id' => 11,
|
||||
'code' => 'music_interviews',
|
||||
'apple_category' => 'Music Interviews',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 63,
|
||||
'parent_id' => 12,
|
||||
'code' => 'business_news',
|
||||
'apple_category' => 'Business News',
|
||||
'id' => 63,
|
||||
'parent_id' => 12,
|
||||
'code' => 'business_news',
|
||||
'apple_category' => 'Business News',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 64,
|
||||
'parent_id' => 12,
|
||||
'code' => 'daily_news',
|
||||
'apple_category' => 'Daily News',
|
||||
'id' => 64,
|
||||
'parent_id' => 12,
|
||||
'code' => 'daily_news',
|
||||
'apple_category' => 'Daily News',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 65,
|
||||
'parent_id' => 12,
|
||||
'code' => 'entertainment_news',
|
||||
'apple_category' => 'Entertainment News',
|
||||
'id' => 65,
|
||||
'parent_id' => 12,
|
||||
'code' => 'entertainment_news',
|
||||
'apple_category' => 'Entertainment News',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 66,
|
||||
'parent_id' => 12,
|
||||
'code' => 'news_commentary',
|
||||
'apple_category' => 'News Commentary',
|
||||
'id' => 66,
|
||||
'parent_id' => 12,
|
||||
'code' => 'news_commentary',
|
||||
'apple_category' => 'News Commentary',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 67,
|
||||
'parent_id' => 12,
|
||||
'code' => 'politics',
|
||||
'apple_category' => 'Politics',
|
||||
'id' => 67,
|
||||
'parent_id' => 12,
|
||||
'code' => 'politics',
|
||||
'apple_category' => 'Politics',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 68,
|
||||
'parent_id' => 12,
|
||||
'code' => 'sports_news',
|
||||
'apple_category' => 'Sports News',
|
||||
'id' => 68,
|
||||
'parent_id' => 12,
|
||||
'code' => 'sports_news',
|
||||
'apple_category' => 'Sports News',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 69,
|
||||
'parent_id' => 12,
|
||||
'code' => 'tech_news',
|
||||
'apple_category' => 'Tech News',
|
||||
'id' => 69,
|
||||
'parent_id' => 12,
|
||||
'code' => 'tech_news',
|
||||
'apple_category' => 'Tech News',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 70,
|
||||
'parent_id' => 13,
|
||||
'code' => 'buddhism',
|
||||
'apple_category' => 'Buddhism',
|
||||
'id' => 70,
|
||||
'parent_id' => 13,
|
||||
'code' => 'buddhism',
|
||||
'apple_category' => 'Buddhism',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 71,
|
||||
'parent_id' => 13,
|
||||
'code' => 'christianity',
|
||||
'apple_category' => 'Christianity',
|
||||
'id' => 71,
|
||||
'parent_id' => 13,
|
||||
'code' => 'christianity',
|
||||
'apple_category' => 'Christianity',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 72,
|
||||
'parent_id' => 13,
|
||||
'code' => 'hinduism',
|
||||
'apple_category' => 'Hinduism',
|
||||
'id' => 72,
|
||||
'parent_id' => 13,
|
||||
'code' => 'hinduism',
|
||||
'apple_category' => 'Hinduism',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 73,
|
||||
'parent_id' => 13,
|
||||
'code' => 'islam',
|
||||
'apple_category' => 'Islam',
|
||||
'id' => 73,
|
||||
'parent_id' => 13,
|
||||
'code' => 'islam',
|
||||
'apple_category' => 'Islam',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 74,
|
||||
'parent_id' => 13,
|
||||
'code' => 'judaism',
|
||||
'apple_category' => 'Judaism',
|
||||
'id' => 74,
|
||||
'parent_id' => 13,
|
||||
'code' => 'judaism',
|
||||
'apple_category' => 'Judaism',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 75,
|
||||
'parent_id' => 13,
|
||||
'code' => 'religion',
|
||||
'apple_category' => 'Religion',
|
||||
'id' => 75,
|
||||
'parent_id' => 13,
|
||||
'code' => 'religion',
|
||||
'apple_category' => 'Religion',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 76,
|
||||
'parent_id' => 13,
|
||||
'code' => 'spirituality',
|
||||
'apple_category' => 'Spirituality',
|
||||
'id' => 76,
|
||||
'parent_id' => 13,
|
||||
'code' => 'spirituality',
|
||||
'apple_category' => 'Spirituality',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 77,
|
||||
'parent_id' => 14,
|
||||
'code' => 'astronomy',
|
||||
'apple_category' => 'Astronomy',
|
||||
'id' => 77,
|
||||
'parent_id' => 14,
|
||||
'code' => 'astronomy',
|
||||
'apple_category' => 'Astronomy',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 78,
|
||||
'parent_id' => 14,
|
||||
'code' => 'chemistry',
|
||||
'apple_category' => 'Chemistry',
|
||||
'id' => 78,
|
||||
'parent_id' => 14,
|
||||
'code' => 'chemistry',
|
||||
'apple_category' => 'Chemistry',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 79,
|
||||
'parent_id' => 14,
|
||||
'code' => 'earth_sciences',
|
||||
'apple_category' => 'Earth Sciences',
|
||||
'id' => 79,
|
||||
'parent_id' => 14,
|
||||
'code' => 'earth_sciences',
|
||||
'apple_category' => 'Earth Sciences',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 80,
|
||||
'parent_id' => 14,
|
||||
'code' => 'life_sciences',
|
||||
'apple_category' => 'Life Sciences',
|
||||
'id' => 80,
|
||||
'parent_id' => 14,
|
||||
'code' => 'life_sciences',
|
||||
'apple_category' => 'Life Sciences',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 81,
|
||||
'parent_id' => 14,
|
||||
'code' => 'mathematics',
|
||||
'apple_category' => 'Mathematics',
|
||||
'id' => 81,
|
||||
'parent_id' => 14,
|
||||
'code' => 'mathematics',
|
||||
'apple_category' => 'Mathematics',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 82,
|
||||
'parent_id' => 14,
|
||||
'code' => 'natural_sciences',
|
||||
'apple_category' => 'Natural Sciences',
|
||||
'id' => 82,
|
||||
'parent_id' => 14,
|
||||
'code' => 'natural_sciences',
|
||||
'apple_category' => 'Natural Sciences',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 83,
|
||||
'parent_id' => 14,
|
||||
'code' => 'nature',
|
||||
'apple_category' => 'Nature',
|
||||
'id' => 83,
|
||||
'parent_id' => 14,
|
||||
'code' => 'nature',
|
||||
'apple_category' => 'Nature',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 84,
|
||||
'parent_id' => 14,
|
||||
'code' => 'physics',
|
||||
'apple_category' => 'Physics',
|
||||
'id' => 84,
|
||||
'parent_id' => 14,
|
||||
'code' => 'physics',
|
||||
'apple_category' => 'Physics',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 85,
|
||||
'parent_id' => 14,
|
||||
'code' => 'social_sciences',
|
||||
'apple_category' => 'Social Sciences',
|
||||
'id' => 85,
|
||||
'parent_id' => 14,
|
||||
'code' => 'social_sciences',
|
||||
'apple_category' => 'Social Sciences',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 86,
|
||||
'parent_id' => 15,
|
||||
'code' => 'documentary',
|
||||
'apple_category' => 'Documentary',
|
||||
'id' => 86,
|
||||
'parent_id' => 15,
|
||||
'code' => 'documentary',
|
||||
'apple_category' => 'Documentary',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 87,
|
||||
'parent_id' => 15,
|
||||
'code' => 'personal_journals',
|
||||
'apple_category' => 'Personal Journals',
|
||||
'id' => 87,
|
||||
'parent_id' => 15,
|
||||
'code' => 'personal_journals',
|
||||
'apple_category' => 'Personal Journals',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 88,
|
||||
'parent_id' => 15,
|
||||
'code' => 'philosophy',
|
||||
'apple_category' => 'Philosophy',
|
||||
'id' => 88,
|
||||
'parent_id' => 15,
|
||||
'code' => 'philosophy',
|
||||
'apple_category' => 'Philosophy',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 89,
|
||||
'parent_id' => 15,
|
||||
'code' => 'places_and_travel',
|
||||
'apple_category' => 'Places & Travel',
|
||||
'id' => 89,
|
||||
'parent_id' => 15,
|
||||
'code' => 'places_and_travel',
|
||||
'apple_category' => 'Places & Travel',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 90,
|
||||
'parent_id' => 15,
|
||||
'code' => 'relationships',
|
||||
'apple_category' => 'Relationships',
|
||||
'id' => 90,
|
||||
'parent_id' => 15,
|
||||
'code' => 'relationships',
|
||||
'apple_category' => 'Relationships',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 91,
|
||||
'parent_id' => 16,
|
||||
'code' => 'baseball',
|
||||
'apple_category' => 'Baseball',
|
||||
'id' => 91,
|
||||
'parent_id' => 16,
|
||||
'code' => 'baseball',
|
||||
'apple_category' => 'Baseball',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 92,
|
||||
'parent_id' => 16,
|
||||
'code' => 'basketball',
|
||||
'apple_category' => 'Basketball',
|
||||
'id' => 92,
|
||||
'parent_id' => 16,
|
||||
'code' => 'basketball',
|
||||
'apple_category' => 'Basketball',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 93,
|
||||
'parent_id' => 16,
|
||||
'code' => 'cricket',
|
||||
'apple_category' => 'Cricket',
|
||||
'id' => 93,
|
||||
'parent_id' => 16,
|
||||
'code' => 'cricket',
|
||||
'apple_category' => 'Cricket',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 94,
|
||||
'parent_id' => 16,
|
||||
'code' => 'fantasy_sports',
|
||||
'apple_category' => 'Fantasy Sports',
|
||||
'id' => 94,
|
||||
'parent_id' => 16,
|
||||
'code' => 'fantasy_sports',
|
||||
'apple_category' => 'Fantasy Sports',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 95,
|
||||
'parent_id' => 16,
|
||||
'code' => 'football',
|
||||
'apple_category' => 'Football',
|
||||
'id' => 95,
|
||||
'parent_id' => 16,
|
||||
'code' => 'football',
|
||||
'apple_category' => 'Football',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 96,
|
||||
'parent_id' => 16,
|
||||
'code' => 'golf',
|
||||
'apple_category' => 'Golf',
|
||||
'id' => 96,
|
||||
'parent_id' => 16,
|
||||
'code' => 'golf',
|
||||
'apple_category' => 'Golf',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 97,
|
||||
'parent_id' => 16,
|
||||
'code' => 'hockey',
|
||||
'apple_category' => 'Hockey',
|
||||
'id' => 97,
|
||||
'parent_id' => 16,
|
||||
'code' => 'hockey',
|
||||
'apple_category' => 'Hockey',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 98,
|
||||
'parent_id' => 16,
|
||||
'code' => 'rugby',
|
||||
'apple_category' => 'Rugby',
|
||||
'id' => 98,
|
||||
'parent_id' => 16,
|
||||
'code' => 'rugby',
|
||||
'apple_category' => 'Rugby',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 99,
|
||||
'parent_id' => 16,
|
||||
'code' => 'running',
|
||||
'apple_category' => 'Running',
|
||||
'id' => 99,
|
||||
'parent_id' => 16,
|
||||
'code' => 'running',
|
||||
'apple_category' => 'Running',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 100,
|
||||
'parent_id' => 16,
|
||||
'code' => 'soccer',
|
||||
'apple_category' => 'Soccer',
|
||||
'id' => 100,
|
||||
'parent_id' => 16,
|
||||
'code' => 'soccer',
|
||||
'apple_category' => 'Soccer',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 101,
|
||||
'parent_id' => 16,
|
||||
'code' => 'swimming',
|
||||
'apple_category' => 'Swimming',
|
||||
'id' => 101,
|
||||
'parent_id' => 16,
|
||||
'code' => 'swimming',
|
||||
'apple_category' => 'Swimming',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 102,
|
||||
'parent_id' => 16,
|
||||
'code' => 'tennis',
|
||||
'apple_category' => 'Tennis',
|
||||
'id' => 102,
|
||||
'parent_id' => 16,
|
||||
'code' => 'tennis',
|
||||
'apple_category' => 'Tennis',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 103,
|
||||
'parent_id' => 16,
|
||||
'code' => 'volleyball',
|
||||
'apple_category' => 'Volleyball',
|
||||
'id' => 103,
|
||||
'parent_id' => 16,
|
||||
'code' => 'volleyball',
|
||||
'apple_category' => 'Volleyball',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 104,
|
||||
'parent_id' => 16,
|
||||
'code' => 'wilderness',
|
||||
'apple_category' => 'Wilderness',
|
||||
'id' => 104,
|
||||
'parent_id' => 16,
|
||||
'code' => 'wilderness',
|
||||
'apple_category' => 'Wilderness',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 105,
|
||||
'parent_id' => 16,
|
||||
'code' => 'wrestling',
|
||||
'apple_category' => 'Wrestling',
|
||||
'id' => 105,
|
||||
'parent_id' => 16,
|
||||
'code' => 'wrestling',
|
||||
'apple_category' => 'Wrestling',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 106,
|
||||
'parent_id' => 19,
|
||||
'code' => 'after_shows',
|
||||
'apple_category' => 'After Shows',
|
||||
'id' => 106,
|
||||
'parent_id' => 19,
|
||||
'code' => 'after_shows',
|
||||
'apple_category' => 'After Shows',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 107,
|
||||
'parent_id' => 19,
|
||||
'code' => 'film_history',
|
||||
'apple_category' => 'Film History',
|
||||
'id' => 107,
|
||||
'parent_id' => 19,
|
||||
'code' => 'film_history',
|
||||
'apple_category' => 'Film History',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 108,
|
||||
'parent_id' => 19,
|
||||
'code' => 'film_interviews',
|
||||
'apple_category' => 'Film Interviews',
|
||||
'id' => 108,
|
||||
'parent_id' => 19,
|
||||
'code' => 'film_interviews',
|
||||
'apple_category' => 'Film Interviews',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 109,
|
||||
'parent_id' => 19,
|
||||
'code' => 'film_reviews',
|
||||
'apple_category' => 'Film Reviews',
|
||||
'id' => 109,
|
||||
'parent_id' => 19,
|
||||
'code' => 'film_reviews',
|
||||
'apple_category' => 'Film Reviews',
|
||||
'google_category' => '',
|
||||
],
|
||||
[
|
||||
'id' => 110,
|
||||
'parent_id' => 19,
|
||||
'code' => 'tv_reviews',
|
||||
'apple_category' => 'TV Reviews',
|
||||
'id' => 110,
|
||||
'parent_id' => 19,
|
||||
'code' => 'tv_reviews',
|
||||
'apple_category' => 'TV Reviews',
|
||||
'google_category' => '',
|
||||
],
|
||||
];
|
||||
|
|
|
|||
27
app/Database/Seeds/DevSeeder.php
Normal file
27
app/Database/Seeds/DevSeeder.php
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AppSeeder Calls all required seeders for castopod to work properly
|
||||
*
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Database\Seeds;
|
||||
|
||||
use CodeIgniter\Database\Seeder;
|
||||
use Override;
|
||||
|
||||
class DevSeeder extends Seeder
|
||||
{
|
||||
#[Override]
|
||||
public function run(): void
|
||||
{
|
||||
$this->call('CategorySeeder');
|
||||
$this->call('LanguageSeeder');
|
||||
$this->call('DevSuperadminSeeder');
|
||||
}
|
||||
}
|
||||
50
app/Database/Seeds/DevSuperadminSeeder.php
Normal file
50
app/Database/Seeds/DevSuperadminSeeder.php
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class TestSeeder Inserts a superadmin user in the database
|
||||
*
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Database\Seeds;
|
||||
|
||||
use CodeIgniter\Database\Seeder;
|
||||
use CodeIgniter\Shield\Entities\User;
|
||||
use Modules\Auth\Models\UserModel;
|
||||
use Override;
|
||||
|
||||
class DevSuperadminSeeder extends Seeder
|
||||
{
|
||||
#[Override]
|
||||
public function run(): void
|
||||
{
|
||||
if (new UserModel()->where('is_owner', true)->first() instanceof User) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts an owner with the following credentials: admin: `admin@example.com` password: `castopod`
|
||||
*/
|
||||
|
||||
// Get the User Provider (UserModel by default)
|
||||
$users = auth()
|
||||
->getProvider();
|
||||
|
||||
$user = new User([
|
||||
'username' => 'admin',
|
||||
'email' => 'admin@castopod.local',
|
||||
'password' => 'castopod',
|
||||
'is_owner' => true,
|
||||
]);
|
||||
$users->save($user);
|
||||
|
||||
// To get the complete user object with ID, we need to get from the database
|
||||
$user = $users->findById($users->getInsertID());
|
||||
|
||||
$user->addGroup(setting('AuthGroups.mostPowerfulGroup'));
|
||||
}
|
||||
}
|
||||
|
|
@ -12,15 +12,19 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Database\Seeds;
|
||||
|
||||
use App\Entities\Episode;
|
||||
use App\Entities\Podcast;
|
||||
use App\Models\EpisodeModel;
|
||||
use App\Models\PodcastModel;
|
||||
use CodeIgniter\Database\Seeder;
|
||||
use Exception;
|
||||
use GeoIp2\Database\Reader;
|
||||
|
||||
use GeoIp2\Exception\AddressNotFoundException;
|
||||
use Override;
|
||||
|
||||
class FakePodcastsAnalyticsSeeder extends Seeder
|
||||
{
|
||||
#[Override]
|
||||
public function run(): void
|
||||
{
|
||||
$jsonUserAgents = json_decode(
|
||||
|
|
@ -39,164 +43,165 @@ class FakePodcastsAnalyticsSeeder extends Seeder
|
|||
JSON_THROW_ON_ERROR,
|
||||
);
|
||||
|
||||
$podcast = (new PodcastModel())->first();
|
||||
$podcast = new PodcastModel()
|
||||
->first();
|
||||
|
||||
if ($podcast !== null) {
|
||||
$firstEpisode = (new EpisodeModel())
|
||||
->selectMin('published_at')
|
||||
->first();
|
||||
if (! $podcast instanceof Podcast) {
|
||||
throw new Exception("COULD NOT POPULATE DATABASE:\n\tCreate a podcast with episodes first.\n");
|
||||
}
|
||||
|
||||
for (
|
||||
$date = strtotime((string) $firstEpisode->published_at);
|
||||
$date < strtotime('now');
|
||||
$date = strtotime(date('Y-m-d', $date) . ' +1 day')
|
||||
) {
|
||||
$analyticsPodcasts = [];
|
||||
$analyticsPodcastsByHour = [];
|
||||
$analyticsPodcastsByCountry = [];
|
||||
$analyticsPodcastsByEpisode = [];
|
||||
$analyticsPodcastsByPlayer = [];
|
||||
$analyticsPodcastsByRegion = [];
|
||||
$firstEpisode = new EpisodeModel()
|
||||
->selectMin('published_at')
|
||||
->first();
|
||||
|
||||
$episodes = (new EpisodeModel())
|
||||
->where('podcast_id', $podcast->id)
|
||||
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
|
||||
->findAll();
|
||||
foreach ($episodes as $episode) {
|
||||
$age = floor(($date - strtotime((string) $episode->published_at)) / 86400);
|
||||
$probability1 = floor(exp(3 - $age / 40)) + 1;
|
||||
if (! $firstEpisode instanceof Episode) {
|
||||
throw new Exception("COULD NOT POPULATE DATABASE:\n\tCreate an episode first.");
|
||||
}
|
||||
|
||||
for (
|
||||
$lineNumber = 0;
|
||||
$lineNumber < rand(1, (int) $probability1);
|
||||
++$lineNumber
|
||||
) {
|
||||
$probability2 = floor(exp(6 - $age / 20)) + 10;
|
||||
for (
|
||||
$date = strtotime((string) $firstEpisode->published_at);
|
||||
$date < strtotime('now');
|
||||
$date = strtotime(date('Y-m-d', $date) . ' +1 day')
|
||||
) {
|
||||
$analyticsPodcasts = [];
|
||||
$analyticsPodcastsByHour = [];
|
||||
$analyticsPodcastsByCountry = [];
|
||||
$analyticsPodcastsByEpisode = [];
|
||||
$analyticsPodcastsByPlayer = [];
|
||||
$analyticsPodcastsByRegion = [];
|
||||
|
||||
$player =
|
||||
$jsonUserAgents[
|
||||
rand(1, count($jsonUserAgents) - 1)
|
||||
];
|
||||
$service =
|
||||
$jsonRSSUserAgents[
|
||||
rand(1, count($jsonRSSUserAgents) - 1)
|
||||
]['slug'];
|
||||
$app = isset($player['app']) ? $player['app'] : '';
|
||||
$device = isset($player['device'])
|
||||
? $player['device']
|
||||
: '';
|
||||
$os = isset($player['os']) ? $player['os'] : '';
|
||||
$isBot = isset($player['bot']) ? $player['bot'] : 0;
|
||||
$episodes = new EpisodeModel()
|
||||
->where('podcast_id', $podcast->id)
|
||||
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
|
||||
->findAll();
|
||||
foreach ($episodes as $episode) {
|
||||
$age = floor(($date - strtotime((string) $episode->published_at)) / 86400);
|
||||
$probability1 = floor(exp(3 - $age / 40)) + 1;
|
||||
|
||||
$fakeIp =
|
||||
rand(0, 255) .
|
||||
'.' .
|
||||
rand(0, 255) .
|
||||
'.' .
|
||||
rand(0, 255) .
|
||||
'.' .
|
||||
rand(0, 255);
|
||||
for (
|
||||
$lineNumber = 0;
|
||||
$lineNumber < random_int(1, (int) $probability1);
|
||||
++$lineNumber
|
||||
) {
|
||||
$probability2 = floor(exp(6 - $age / 20)) + 10;
|
||||
|
||||
$cityReader = new Reader(WRITEPATH . 'uploads/GeoLite2-City/GeoLite2-City.mmdb');
|
||||
|
||||
$countryCode = 'N/A';
|
||||
$regionCode = 'N/A';
|
||||
$latitude = null;
|
||||
$longitude = null;
|
||||
try {
|
||||
$city = $cityReader->city($fakeIp);
|
||||
|
||||
$countryCode = $city->country->isoCode === null
|
||||
? 'N/A'
|
||||
: $city->country->isoCode;
|
||||
|
||||
$regionCode = $city->subdivisions === []
|
||||
? 'N/A'
|
||||
: $city->subdivisions[0]->isoCode;
|
||||
$latitude = round((float) $city->location->latitude, 3);
|
||||
$longitude = round((float) $city->location->longitude, 3);
|
||||
} catch (AddressNotFoundException) {
|
||||
//Bad luck, bad IP, nothing to do.
|
||||
}
|
||||
|
||||
$hits = rand(0, (int) $probability2);
|
||||
|
||||
$analyticsPodcasts[] = [
|
||||
'podcast_id' => $podcast->id,
|
||||
'date' => date('Y-m-d', $date),
|
||||
'duration' => rand(60, 3600),
|
||||
'bandwidth' => rand(1000000, 10000000),
|
||||
'hits' => $hits,
|
||||
'unique_listeners' => $hits,
|
||||
];
|
||||
$analyticsPodcastsByHour[] = [
|
||||
'podcast_id' => $podcast->id,
|
||||
'date' => date('Y-m-d', $date),
|
||||
'hour' => rand(0, 23),
|
||||
'hits' => $hits,
|
||||
];
|
||||
$analyticsPodcastsByCountry[] = [
|
||||
'podcast_id' => $podcast->id,
|
||||
'date' => date('Y-m-d', $date),
|
||||
'country_code' => $countryCode,
|
||||
'hits' => $hits,
|
||||
];
|
||||
$analyticsPodcastsByEpisode[] = [
|
||||
'podcast_id' => $podcast->id,
|
||||
'date' => date('Y-m-d', $date),
|
||||
'episode_id' => $episode->id,
|
||||
'age' => $age,
|
||||
'hits' => $hits,
|
||||
];
|
||||
$analyticsPodcastsByPlayer[] = [
|
||||
'podcast_id' => $podcast->id,
|
||||
'date' => date('Y-m-d', $date),
|
||||
'service' => $service,
|
||||
'app' => $app,
|
||||
'device' => $device,
|
||||
'os' => $os,
|
||||
'is_bot' => $isBot,
|
||||
'hits' => $hits,
|
||||
];
|
||||
$analyticsPodcastsByRegion[] = [
|
||||
'podcast_id' => $podcast->id,
|
||||
'date' => date('Y-m-d', $date),
|
||||
'country_code' => $countryCode,
|
||||
'region_code' => $regionCode,
|
||||
'latitude' => $latitude,
|
||||
'longitude' => $longitude,
|
||||
'hits' => $hits,
|
||||
$player =
|
||||
$jsonUserAgents[
|
||||
random_int(1, count($jsonUserAgents) - 1)
|
||||
];
|
||||
$service =
|
||||
$jsonRSSUserAgents[
|
||||
random_int(1, count($jsonRSSUserAgents) - 1)
|
||||
]['slug'];
|
||||
$app = $player['app'] ?? '';
|
||||
$device = $player['device'] ?? '';
|
||||
$os = $player['os'] ?? '';
|
||||
$isBot = $player['bot'] ?? 0;
|
||||
|
||||
$fakeIp =
|
||||
random_int(0, 255) .
|
||||
'.' .
|
||||
random_int(0, 255) .
|
||||
'.' .
|
||||
random_int(0, 255) .
|
||||
'.' .
|
||||
random_int(0, 255);
|
||||
|
||||
$cityReader = new Reader(WRITEPATH . 'uploads/GeoLite2-City/GeoLite2-City.mmdb');
|
||||
|
||||
$countryCode = 'N/A';
|
||||
$regionCode = 'N/A';
|
||||
$latitude = null;
|
||||
$longitude = null;
|
||||
try {
|
||||
$city = $cityReader->city($fakeIp);
|
||||
|
||||
$countryCode = $city->country->isoCode ?? 'N/A';
|
||||
|
||||
$regionCode = $city->subdivisions === []
|
||||
? 'N/A'
|
||||
: $city->subdivisions[0]->isoCode;
|
||||
$latitude = round((float) $city->location->latitude, 3);
|
||||
$longitude = round((float) $city->location->longitude, 3);
|
||||
} catch (AddressNotFoundException) {
|
||||
//Bad luck, bad IP, nothing to do.
|
||||
}
|
||||
}
|
||||
|
||||
$this->db
|
||||
->table('analytics_podcasts')
|
||||
->ignore(true)
|
||||
->insertBatch($analyticsPodcasts);
|
||||
$this->db
|
||||
->table('analytics_podcasts_by_hour')
|
||||
->ignore(true)
|
||||
->insertBatch($analyticsPodcastsByHour);
|
||||
$this->db
|
||||
->table('analytics_podcasts_by_country')
|
||||
->ignore(true)
|
||||
->insertBatch($analyticsPodcastsByCountry);
|
||||
$this->db
|
||||
->table('analytics_podcasts_by_episode')
|
||||
->ignore(true)
|
||||
->insertBatch($analyticsPodcastsByEpisode);
|
||||
$this->db
|
||||
->table('analytics_podcasts_by_player')
|
||||
->ignore(true)
|
||||
->insertBatch($analyticsPodcastsByPlayer);
|
||||
$this->db
|
||||
->table('analytics_podcasts_by_region')
|
||||
->ignore(true)
|
||||
->insertBatch($analyticsPodcastsByRegion);
|
||||
$hits = random_int(0, (int) $probability2);
|
||||
|
||||
$analyticsPodcasts[] = [
|
||||
'podcast_id' => $podcast->id,
|
||||
'date' => date('Y-m-d', $date),
|
||||
'duration' => random_int(60, 3600),
|
||||
'bandwidth' => random_int(1000000, 10000000),
|
||||
'hits' => $hits,
|
||||
'unique_listeners' => $hits,
|
||||
];
|
||||
$analyticsPodcastsByHour[] = [
|
||||
'podcast_id' => $podcast->id,
|
||||
'date' => date('Y-m-d', $date),
|
||||
'hour' => random_int(0, 23),
|
||||
'hits' => $hits,
|
||||
];
|
||||
$analyticsPodcastsByCountry[] = [
|
||||
'podcast_id' => $podcast->id,
|
||||
'date' => date('Y-m-d', $date),
|
||||
'country_code' => $countryCode,
|
||||
'hits' => $hits,
|
||||
];
|
||||
$analyticsPodcastsByEpisode[] = [
|
||||
'podcast_id' => $podcast->id,
|
||||
'date' => date('Y-m-d', $date),
|
||||
'episode_id' => $episode->id,
|
||||
'age' => $age,
|
||||
'hits' => $hits,
|
||||
];
|
||||
$analyticsPodcastsByPlayer[] = [
|
||||
'podcast_id' => $podcast->id,
|
||||
'date' => date('Y-m-d', $date),
|
||||
'service' => $service,
|
||||
'app' => $app,
|
||||
'device' => $device,
|
||||
'os' => $os,
|
||||
'is_bot' => $isBot,
|
||||
'hits' => $hits,
|
||||
];
|
||||
$analyticsPodcastsByRegion[] = [
|
||||
'podcast_id' => $podcast->id,
|
||||
'date' => date('Y-m-d', $date),
|
||||
'country_code' => $countryCode,
|
||||
'region_code' => $regionCode,
|
||||
'latitude' => $latitude,
|
||||
'longitude' => $longitude,
|
||||
'hits' => $hits,
|
||||
];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
echo "COULD NOT POPULATE DATABASE:\n\tCreate a podcast with episodes first.\n";
|
||||
|
||||
$this->db
|
||||
->table('analytics_podcasts')
|
||||
->ignore(true)
|
||||
->insertBatch($analyticsPodcasts);
|
||||
$this->db
|
||||
->table('analytics_podcasts_by_hour')
|
||||
->ignore(true)
|
||||
->insertBatch($analyticsPodcastsByHour);
|
||||
$this->db
|
||||
->table('analytics_podcasts_by_country')
|
||||
->ignore(true)
|
||||
->insertBatch($analyticsPodcastsByCountry);
|
||||
$this->db
|
||||
->table('analytics_podcasts_by_episode')
|
||||
->ignore(true)
|
||||
->insertBatch($analyticsPodcastsByEpisode);
|
||||
$this->db
|
||||
->table('analytics_podcasts_by_player')
|
||||
->ignore(true)
|
||||
->insertBatch($analyticsPodcastsByPlayer);
|
||||
$this->db
|
||||
->table('analytics_podcasts_by_region')
|
||||
->ignore(true)
|
||||
->insertBatch($analyticsPodcastsByRegion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,141 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Database\Seeds;
|
||||
|
||||
use CodeIgniter\Database\Seeder;
|
||||
|
||||
class FakeSinglePodcastApiSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* @return array{id: int, file_path: string, file_size: int, file_mimetype: string, file_metadata: string, type: string, description: null, language_code: null, uploaded_by: int, updated_by: int, uploaded_at: string, updated_at: string}
|
||||
*/
|
||||
public static function cover(): array
|
||||
{
|
||||
return [
|
||||
'id' => 1,
|
||||
'file_path' => 'podcasts/Handle/cover.jpg',
|
||||
'file_size' => 400000,
|
||||
'file_mimetype' => 'image/jpeg',
|
||||
'file_metadata' => '{"FILE":{"FileName":"cover.jpg","FileDateTime":1654861723,"FileSize":468541,"FileType":2,"MimeType":"image\/jpeg","SectionsFound":"COMMENT"},"COMPUTED":{"html":"width=\"1400\" height=\"1400\"","Height":1400,"Width":1400,"IsColor":1},"COMMENT":["CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), quality = 90\n"],"sizes":{"tiny":{"width":40,"height":40,"mimetype":"image\/webp","extension":"webp"},"thumbnail":{"width":150,"height":150,"mimetype":"image\/webp","extension":"webp"},"medium":{"width":320,"height":320,"mimetype":"image\/webp","extension":"webp"},"large":{"width":1024,"height":1024,"mimetype":"image\/webp","extension":"webp"},"feed":{"width":1400,"height":1400},"id3":{"width":500,"height":500},"og":{"width":1200,"height":1200},"federation":{"width":400,"height":400},"webmanifest192":{"width":192,"height":192,"mimetype":"image\/png","extension":"png"},"webmanifest512":{"width":512,"height":512,"mimetype":"image\/png","extension":"png"}}}',
|
||||
'type' => 'image',
|
||||
'description' => null,
|
||||
'language_code' => null,
|
||||
'uploaded_by' => 1,
|
||||
'updated_by' => 1,
|
||||
'uploaded_at' => '2022-06-13 8:00:00',
|
||||
'updated_at' => '2022-06-13 8:00:00',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{id: int, file_path: string, file_size: int, file_mimetype: string, file_metadata: string, type: string, description: null, language_code: null, uploaded_by: int, updated_by: int, uploaded_at: string, updated_at: string}
|
||||
*/
|
||||
public static function banner(): array
|
||||
{
|
||||
return [
|
||||
'id' => 2,
|
||||
'file_path' => 'podcasts/Handle/banner.jpg',
|
||||
'file_size' => 400000,
|
||||
'file_mimetype' => 'image/jpeg',
|
||||
'file_metadata' => '{"FILE":{"FileName":"banner.jpg","FileDateTime":1654861724,"FileSize":98209,"FileType":2,"MimeType":"image\/jpeg","SectionsFound":""},"COMPUTED":{"html":"width=\"1500\" height=\"500\"","Height":500,"Width":1500,"IsColor":1},"sizes":{"small":{"width":320,"height":128,"mimetype":"image\/webp","extension":"webp"},"medium":{"width":960,"height":320,"mimetype":"image\/webp","extension":"webp"},"federation":{"width":1500,"height":500}}}',
|
||||
'type' => 'image',
|
||||
'description' => null,
|
||||
'language_code' => null,
|
||||
'uploaded_by' => 1,
|
||||
'updated_by' => 1,
|
||||
'uploaded_at' => '2022-06-13 8:00:00',
|
||||
'updated_at' => '2022-06-13 8:00:00',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{id: int, uri: string, username: string, domain: string|false, private_key: string, public_key: string, display_name: string, summary: string, avatar_image_url: string, avatar_image_mimetype: string, cover_image_url: null, cover_image_mimetype: null, inbox_url: string, outbox_url: string, followers_url: string, followers_count: int, posts_count: int, is_blocked: int, created_at: string, updated_at: string}
|
||||
*/
|
||||
public static function actor(): array
|
||||
{
|
||||
return [
|
||||
'id' => 1,
|
||||
'uri' => getenv('app_baseURL') . '@Handle',
|
||||
'username' => 'Handle',
|
||||
'domain' => getenv('app_baseURL'),
|
||||
'private_key' => 'private_key',
|
||||
'public_key' => 'public_key',
|
||||
'display_name' => 'Title',
|
||||
'summary' => '<p>description</p>',
|
||||
'avatar_image_url' => getenv('app_baseURL') . 'media/podcasts/Handle',
|
||||
'avatar_image_mimetype' => 'image/webp',
|
||||
'cover_image_url' => null,
|
||||
'cover_image_mimetype' => null,
|
||||
'inbox_url' => getenv('app_baseURL') . '@Handle/inbox',
|
||||
'outbox_url' => getenv('app_baseURL') . '@Handle/outbox',
|
||||
'followers_url' => getenv('app_baseURL') . '@Handle/followers',
|
||||
'followers_count' => 0,
|
||||
'posts_count' => 0,
|
||||
'is_blocked' => 0,
|
||||
'created_at' => '2022-06-13 8:00:00',
|
||||
'updated_at' => '2022-06-13 8:00:00',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{id: int, guid: string, actor_id: int, handle: string, title: string, description_markdown: string, description_html: string, cover_id: int, banner_id: int, language_code: string, category_id: int, parental_advisory: null, owner_name: string, owner_email: string, publisher: string, type: string, copyright: string, episode_description_footer_markdown: null, episode_description_footer_html: null, is_blocked: int, is_completed: int, is_locked: int, imported_feed_url: null, new_feed_url: null, payment_pointer: null, location_name: null, location_geo: null, location_osm: null, custom_rss: null, is_published_on_hubs: int, partner_id: null, partner_link_url: null, partner_image_url: null, created_by: int, updated_by: int, created_at: string, updated_at: string}
|
||||
*/
|
||||
public static function podcast(): array
|
||||
{
|
||||
return [
|
||||
'id' => 1,
|
||||
'guid' => '0d341200-0234-5de7-99a6-a7d02bea4ce2',
|
||||
'actor_id' => 1,
|
||||
'handle' => 'Handle',
|
||||
'title' => 'Title',
|
||||
'description_markdown' => 'description',
|
||||
'description_html' => '<p>description</p>',
|
||||
'cover_id' => 1,
|
||||
'banner_id' => 2,
|
||||
'language_code' => 'en',
|
||||
'category_id' => 1,
|
||||
'parental_advisory' => null,
|
||||
'owner_name' => 'Owner',
|
||||
'owner_email' => 'Owner@gmail.com',
|
||||
'publisher' => '',
|
||||
'type' => 'episodic',
|
||||
'copyright' => '',
|
||||
'episode_description_footer_markdown' => null,
|
||||
'episode_description_footer_html' => null,
|
||||
'is_blocked' => 0,
|
||||
'is_completed' => 0,
|
||||
'is_locked' => 1,
|
||||
'imported_feed_url' => null,
|
||||
'new_feed_url' => null,
|
||||
'payment_pointer' => null,
|
||||
'location_name' => null,
|
||||
'location_geo' => null,
|
||||
'location_osm' => null,
|
||||
'custom_rss' => null,
|
||||
'is_published_on_hubs' => 0,
|
||||
'partner_id' => null,
|
||||
'partner_link_url' => null,
|
||||
'partner_image_url' => null,
|
||||
'created_by' => 1,
|
||||
'updated_by' => 1,
|
||||
'created_at' => '2022-06-13 8:00:00',
|
||||
'updated_at' => '2022-06-13 8:00:00',
|
||||
];
|
||||
}
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$this->call(AppSeeder::class);
|
||||
$this->call(TestSeeder::class);
|
||||
$this->db->table('media')
|
||||
->insert(self::cover());
|
||||
$this->db->table('media')
|
||||
->insert(self::banner());
|
||||
$this->db->table('fediverse_actors')
|
||||
->insert(self::actor());
|
||||
$this->db->table('podcasts')
|
||||
->insert(self::podcast());
|
||||
}
|
||||
}
|
||||
|
|
@ -12,10 +12,13 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Database\Seeds;
|
||||
|
||||
use App\Entities\Episode;
|
||||
use App\Entities\Podcast;
|
||||
use App\Models\EpisodeModel;
|
||||
use App\Models\PodcastModel;
|
||||
|
||||
use CodeIgniter\Database\Seeder;
|
||||
use Exception;
|
||||
use Override;
|
||||
|
||||
class FakeWebsiteAnalyticsSeeder extends Seeder
|
||||
{
|
||||
|
|
@ -179,91 +182,96 @@ class FakeWebsiteAnalyticsSeeder extends Seeder
|
|||
'WOSBrowser',
|
||||
];
|
||||
|
||||
#[Override]
|
||||
public function run(): void
|
||||
{
|
||||
$podcast = (new PodcastModel())->first();
|
||||
$podcast = new PodcastModel()
|
||||
->first();
|
||||
|
||||
if ($podcast) {
|
||||
$firstEpisode = (new EpisodeModel())
|
||||
->selectMin('published_at')
|
||||
->first();
|
||||
if (! $podcast instanceof Podcast) {
|
||||
throw new Exception("COULD NOT POPULATE DATABASE:\n\tCreate a podcast with episodes first.\n");
|
||||
}
|
||||
|
||||
for (
|
||||
$date = strtotime((string) $firstEpisode->published_at);
|
||||
$date < strtotime('now');
|
||||
$date = strtotime(date('Y-m-d', $date) . ' +1 day')
|
||||
) {
|
||||
$websiteByBrowser = [];
|
||||
$websiteByEntryPage = [];
|
||||
$websiteByReferer = [];
|
||||
$firstEpisode = new EpisodeModel()
|
||||
->selectMin('published_at')
|
||||
->first();
|
||||
|
||||
$episodes = (new EpisodeModel())
|
||||
->where('podcast_id', $podcast->id)
|
||||
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
|
||||
->findAll();
|
||||
foreach ($episodes as $episode) {
|
||||
$age = floor(($date - strtotime((string) $episode->published_at)) / 86400);
|
||||
$probability1 = (int) floor(exp(3 - $age / 40)) + 1;
|
||||
if (! $firstEpisode instanceof Episode) {
|
||||
throw new Exception("COULD NOT POPULATE DATABASE:\n\tCreate an episode first.");
|
||||
}
|
||||
|
||||
for (
|
||||
$lineNumber = 0;
|
||||
$lineNumber < rand(1, $probability1);
|
||||
++$lineNumber
|
||||
) {
|
||||
$probability2 = (int) floor(exp(6 - $age / 20)) + 10;
|
||||
for (
|
||||
$date = strtotime((string) $firstEpisode->published_at);
|
||||
$date < strtotime('now');
|
||||
$date = strtotime(date('Y-m-d', $date) . ' +1 day')
|
||||
) {
|
||||
$websiteByBrowser = [];
|
||||
$websiteByEntryPage = [];
|
||||
$websiteByReferer = [];
|
||||
|
||||
$domain =
|
||||
$this->domains[rand(0, count($this->domains) - 1)];
|
||||
$keyword =
|
||||
$this->keywords[
|
||||
rand(0, count($this->keywords) - 1)
|
||||
];
|
||||
$browser =
|
||||
$this->browsers[
|
||||
rand(0, count($this->browsers) - 1)
|
||||
];
|
||||
$episodes = new EpisodeModel()
|
||||
->where('podcast_id', $podcast->id)
|
||||
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
|
||||
->findAll();
|
||||
foreach ($episodes as $episode) {
|
||||
$age = floor(($date - strtotime((string) $episode->published_at)) / 86400);
|
||||
$probability1 = (int) floor(exp(3 - $age / 40)) + 1;
|
||||
|
||||
$hits = rand(0, $probability2);
|
||||
for (
|
||||
$lineNumber = 0;
|
||||
$lineNumber < random_int(1, $probability1);
|
||||
++$lineNumber
|
||||
) {
|
||||
$probability2 = (int) floor(exp(6 - $age / 20)) + 10;
|
||||
|
||||
$websiteByBrowser[] = [
|
||||
'podcast_id' => $podcast->id,
|
||||
'date' => date('Y-m-d', $date),
|
||||
'browser' => $browser,
|
||||
'hits' => $hits,
|
||||
$domain =
|
||||
$this->domains[random_int(0, count($this->domains) - 1)];
|
||||
$keyword =
|
||||
$this->keywords[
|
||||
random_int(0, count($this->keywords) - 1)
|
||||
];
|
||||
$websiteByEntryPage[] = [
|
||||
'podcast_id' => $podcast->id,
|
||||
'date' => date('Y-m-d', $date),
|
||||
'entry_page_url' => $episode->link,
|
||||
'hits' => $hits,
|
||||
$browser =
|
||||
$this->browsers[
|
||||
random_int(0, count($this->browsers) - 1)
|
||||
];
|
||||
$websiteByReferer[] = [
|
||||
'podcast_id' => $podcast->id,
|
||||
'date' => date('Y-m-d', $date),
|
||||
'referer_url' =>
|
||||
'http://' . $domain . '/?q=' . $keyword,
|
||||
'domain' => $domain,
|
||||
'keywords' => $keyword,
|
||||
'hits' => $hits,
|
||||
];
|
||||
}
|
||||
|
||||
$hits = random_int(0, $probability2);
|
||||
|
||||
$websiteByBrowser[] = [
|
||||
'podcast_id' => $podcast->id,
|
||||
'date' => date('Y-m-d', $date),
|
||||
'browser' => $browser,
|
||||
'hits' => $hits,
|
||||
];
|
||||
$websiteByEntryPage[] = [
|
||||
'podcast_id' => $podcast->id,
|
||||
'date' => date('Y-m-d', $date),
|
||||
'entry_page_url' => $episode->link,
|
||||
'hits' => $hits,
|
||||
];
|
||||
$websiteByReferer[] = [
|
||||
'podcast_id' => $podcast->id,
|
||||
'date' => date('Y-m-d', $date),
|
||||
'referer_url' => 'http://' . $domain . '/?q=' . $keyword,
|
||||
'domain' => $domain,
|
||||
'keywords' => $keyword,
|
||||
'hits' => $hits,
|
||||
];
|
||||
}
|
||||
|
||||
$this->db
|
||||
->table('analytics_website_by_browser')
|
||||
->ignore(true)
|
||||
->insertBatch($websiteByBrowser);
|
||||
$this->db
|
||||
->table('analytics_website_by_entry_page')
|
||||
->ignore(true)
|
||||
->insertBatch($websiteByEntryPage);
|
||||
$this->db
|
||||
->table('analytics_website_by_referer')
|
||||
->ignore(true)
|
||||
->insertBatch($websiteByReferer);
|
||||
}
|
||||
} else {
|
||||
echo "COULD NOT POPULATE DATABASE:\n\tCreate a podcast with episodes first.\n";
|
||||
|
||||
$this->db
|
||||
->table('analytics_website_by_browser')
|
||||
->ignore(true)
|
||||
->insertBatch($websiteByBrowser);
|
||||
$this->db
|
||||
->table('analytics_website_by_entry_page')
|
||||
->ignore(true)
|
||||
->insertBatch($websiteByEntryPage);
|
||||
$this->db
|
||||
->table('analytics_website_by_referer')
|
||||
->ignore(true)
|
||||
->insertBatch($websiteByReferer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,747 +18,748 @@ declare(strict_types=1);
|
|||
namespace App\Database\Seeds;
|
||||
|
||||
use CodeIgniter\Database\Seeder;
|
||||
use Override;
|
||||
|
||||
class LanguageSeeder extends Seeder
|
||||
{
|
||||
#[Override]
|
||||
public function run(): void
|
||||
{
|
||||
$data = [
|
||||
[
|
||||
'code' => 'aa',
|
||||
'code' => 'aa',
|
||||
'native_name' => 'Afaraf',
|
||||
],
|
||||
[
|
||||
'code' => 'ab',
|
||||
'code' => 'ab',
|
||||
'native_name' => 'аҧсуа бызшәа, аҧсшәа',
|
||||
],
|
||||
[
|
||||
'code' => 'ae',
|
||||
'code' => 'ae',
|
||||
'native_name' => 'Avesta',
|
||||
],
|
||||
[
|
||||
'code' => 'af',
|
||||
'code' => 'af',
|
||||
'native_name' => 'Afrikaans',
|
||||
],
|
||||
[
|
||||
'code' => 'ak',
|
||||
'code' => 'ak',
|
||||
'native_name' => 'Akan',
|
||||
],
|
||||
[
|
||||
'code' => 'am',
|
||||
'code' => 'am',
|
||||
'native_name' => 'አማርኛ',
|
||||
],
|
||||
[
|
||||
'code' => 'an',
|
||||
'code' => 'an',
|
||||
'native_name' => 'Aragonés',
|
||||
],
|
||||
[
|
||||
'code' => 'ar',
|
||||
'code' => 'ar',
|
||||
'native_name' => 'العربية',
|
||||
],
|
||||
[
|
||||
'code' => 'as',
|
||||
'code' => 'as',
|
||||
'native_name' => 'অসমীয়া',
|
||||
],
|
||||
[
|
||||
'code' => 'av',
|
||||
'code' => 'av',
|
||||
'native_name' => 'авар мацӀ, магӀарул мацӀ',
|
||||
],
|
||||
[
|
||||
'code' => 'ay',
|
||||
'code' => 'ay',
|
||||
'native_name' => 'Aymar aru',
|
||||
],
|
||||
[
|
||||
'code' => 'az',
|
||||
'code' => 'az',
|
||||
'native_name' => 'azərbaycan dili',
|
||||
],
|
||||
[
|
||||
'code' => 'ba',
|
||||
'code' => 'ba',
|
||||
'native_name' => 'башҡорт теле',
|
||||
],
|
||||
[
|
||||
'code' => 'be',
|
||||
'code' => 'be',
|
||||
'native_name' => 'беларуская мова',
|
||||
],
|
||||
[
|
||||
'code' => 'bg',
|
||||
'code' => 'bg',
|
||||
'native_name' => 'български език',
|
||||
],
|
||||
[
|
||||
'code' => 'bh',
|
||||
'code' => 'bh',
|
||||
'native_name' => 'भोजपुरी',
|
||||
],
|
||||
[
|
||||
'code' => 'bi',
|
||||
'code' => 'bi',
|
||||
'native_name' => 'Bislama',
|
||||
],
|
||||
[
|
||||
'code' => 'bm',
|
||||
'code' => 'bm',
|
||||
'native_name' => 'Bamanankan',
|
||||
],
|
||||
[
|
||||
'code' => 'bn',
|
||||
'code' => 'bn',
|
||||
'native_name' => 'বাংলা',
|
||||
],
|
||||
[
|
||||
'code' => 'bo',
|
||||
'code' => 'bo',
|
||||
'native_name' => 'བོད་ཡིག',
|
||||
],
|
||||
[
|
||||
'code' => 'br',
|
||||
'code' => 'br',
|
||||
'native_name' => 'Brezhoneg',
|
||||
],
|
||||
[
|
||||
'code' => 'bs',
|
||||
'code' => 'bs',
|
||||
'native_name' => 'Bosanski jezik',
|
||||
],
|
||||
[
|
||||
'code' => 'ca',
|
||||
'code' => 'ca',
|
||||
'native_name' => 'Català, valencià',
|
||||
],
|
||||
[
|
||||
'code' => 'ce',
|
||||
'code' => 'ce',
|
||||
'native_name' => 'нохчийн мотт',
|
||||
],
|
||||
[
|
||||
'code' => 'ch',
|
||||
'code' => 'ch',
|
||||
'native_name' => 'Chamoru',
|
||||
],
|
||||
[
|
||||
'code' => 'co',
|
||||
'code' => 'co',
|
||||
'native_name' => 'Corsu, lingua corsa',
|
||||
],
|
||||
[
|
||||
'code' => 'cr',
|
||||
'code' => 'cr',
|
||||
'native_name' => 'ᓀᐦᐃᔭᐍᐏᐣ',
|
||||
],
|
||||
[
|
||||
'code' => 'cs',
|
||||
'code' => 'cs',
|
||||
'native_name' => 'čeština, český jazyk',
|
||||
],
|
||||
[
|
||||
'code' => 'cu',
|
||||
'code' => 'cu',
|
||||
'native_name' => 'ѩзыкъ словѣньскъ',
|
||||
],
|
||||
[
|
||||
'code' => 'cv',
|
||||
'code' => 'cv',
|
||||
'native_name' => 'чӑваш чӗлхи',
|
||||
],
|
||||
[
|
||||
'code' => 'cy',
|
||||
'code' => 'cy',
|
||||
'native_name' => 'Cymraeg',
|
||||
],
|
||||
[
|
||||
'code' => 'da',
|
||||
'code' => 'da',
|
||||
'native_name' => 'Dansk',
|
||||
],
|
||||
[
|
||||
'code' => 'de',
|
||||
'code' => 'de',
|
||||
'native_name' => 'Deutsch',
|
||||
],
|
||||
[
|
||||
'code' => 'dv',
|
||||
'code' => 'dv',
|
||||
'native_name' => 'ދިވެހި',
|
||||
],
|
||||
[
|
||||
'code' => 'dz',
|
||||
'code' => 'dz',
|
||||
'native_name' => 'རྫོང་ཁ',
|
||||
],
|
||||
[
|
||||
'code' => 'ee',
|
||||
'code' => 'ee',
|
||||
'native_name' => 'Eʋegbe',
|
||||
],
|
||||
[
|
||||
'code' => 'el',
|
||||
'code' => 'el',
|
||||
'native_name' => 'ελληνικά',
|
||||
],
|
||||
[
|
||||
'code' => 'en',
|
||||
'code' => 'en',
|
||||
'native_name' => 'English',
|
||||
],
|
||||
[
|
||||
'code' => 'eo',
|
||||
'code' => 'eo',
|
||||
'native_name' => 'Esperanto',
|
||||
],
|
||||
[
|
||||
'code' => 'es',
|
||||
'code' => 'es',
|
||||
'native_name' => 'Español',
|
||||
],
|
||||
[
|
||||
'code' => 'et',
|
||||
'code' => 'et',
|
||||
'native_name' => 'eesti, eesti keel',
|
||||
],
|
||||
[
|
||||
'code' => 'eu',
|
||||
'code' => 'eu',
|
||||
'native_name' => 'Euskara, euskera',
|
||||
],
|
||||
[
|
||||
'code' => 'fa',
|
||||
'code' => 'fa',
|
||||
'native_name' => 'فارسی',
|
||||
],
|
||||
[
|
||||
'code' => 'ff',
|
||||
'code' => 'ff',
|
||||
'native_name' => 'Fulfulde, Pulaar, Pular',
|
||||
],
|
||||
[
|
||||
'code' => 'fi',
|
||||
'code' => 'fi',
|
||||
'native_name' => 'Suomi, suomen kieli',
|
||||
],
|
||||
[
|
||||
'code' => 'fj',
|
||||
'code' => 'fj',
|
||||
'native_name' => 'Vosa Vakaviti',
|
||||
],
|
||||
[
|
||||
'code' => 'fo',
|
||||
'code' => 'fo',
|
||||
'native_name' => 'Føroyskt',
|
||||
],
|
||||
[
|
||||
'code' => 'fr',
|
||||
'code' => 'fr',
|
||||
'native_name' => 'Français, langue française',
|
||||
],
|
||||
[
|
||||
'code' => 'fy',
|
||||
'code' => 'fy',
|
||||
'native_name' => 'Frysk',
|
||||
],
|
||||
[
|
||||
'code' => 'ga',
|
||||
'code' => 'ga',
|
||||
'native_name' => 'Gaeilge',
|
||||
],
|
||||
[
|
||||
'code' => 'gd',
|
||||
'code' => 'gd',
|
||||
'native_name' => 'Gàidhlig',
|
||||
],
|
||||
[
|
||||
'code' => 'gl',
|
||||
'code' => 'gl',
|
||||
'native_name' => 'Galego',
|
||||
],
|
||||
[
|
||||
'code' => 'gn',
|
||||
'code' => 'gn',
|
||||
'native_name' => "Avañe'ẽ",
|
||||
],
|
||||
[
|
||||
'code' => 'gu',
|
||||
'code' => 'gu',
|
||||
'native_name' => 'ગુજરાતી',
|
||||
],
|
||||
[
|
||||
'code' => 'gv',
|
||||
'code' => 'gv',
|
||||
'native_name' => 'Gaelg, Gailck',
|
||||
],
|
||||
[
|
||||
'code' => 'ha',
|
||||
'code' => 'ha',
|
||||
'native_name' => '(Hausa) هَوُسَ',
|
||||
],
|
||||
[
|
||||
'code' => 'he',
|
||||
'code' => 'he',
|
||||
'native_name' => 'עברית',
|
||||
],
|
||||
[
|
||||
'code' => 'hi',
|
||||
'code' => 'hi',
|
||||
'native_name' => 'हिन्दी, हिंदी',
|
||||
],
|
||||
[
|
||||
'code' => 'ho',
|
||||
'code' => 'ho',
|
||||
'native_name' => 'Hiri Motu',
|
||||
],
|
||||
[
|
||||
'code' => 'hr',
|
||||
'code' => 'hr',
|
||||
'native_name' => 'Hrvatski jezik',
|
||||
],
|
||||
[
|
||||
'code' => 'ht',
|
||||
'code' => 'ht',
|
||||
'native_name' => 'Kreyòl ayisyen',
|
||||
],
|
||||
[
|
||||
'code' => 'hu',
|
||||
'code' => 'hu',
|
||||
'native_name' => 'Magyar',
|
||||
],
|
||||
[
|
||||
'code' => 'hy',
|
||||
'code' => 'hy',
|
||||
'native_name' => 'Հայերեն',
|
||||
],
|
||||
[
|
||||
'code' => 'hz',
|
||||
'code' => 'hz',
|
||||
'native_name' => 'Otjiherero',
|
||||
],
|
||||
[
|
||||
'code' => 'ia',
|
||||
'code' => 'ia',
|
||||
'native_name' => 'Interlingua',
|
||||
],
|
||||
[
|
||||
'code' => 'id',
|
||||
'code' => 'id',
|
||||
'native_name' => 'Bahasa Indonesia',
|
||||
],
|
||||
[
|
||||
'code' => 'ie',
|
||||
'native_name' =>
|
||||
'Interlingue, formerly Occidental',
|
||||
'code' => 'ie',
|
||||
'native_name' => 'Interlingue, formerly Occidental',
|
||||
],
|
||||
[
|
||||
'code' => 'ig',
|
||||
'code' => 'ig',
|
||||
'native_name' => 'Asụsụ Igbo',
|
||||
],
|
||||
[
|
||||
'code' => 'ii',
|
||||
'code' => 'ii',
|
||||
'native_name' => 'ꆈꌠ꒿ Nuosuhxop',
|
||||
],
|
||||
[
|
||||
'code' => 'ik',
|
||||
'code' => 'ik',
|
||||
'native_name' => 'Iñupiaq, Iñupiatun',
|
||||
],
|
||||
[
|
||||
'code' => 'io',
|
||||
'code' => 'io',
|
||||
'native_name' => 'Ido',
|
||||
],
|
||||
[
|
||||
'code' => 'is',
|
||||
'code' => 'is',
|
||||
'native_name' => 'Íslenska',
|
||||
],
|
||||
[
|
||||
'code' => 'it',
|
||||
'code' => 'it',
|
||||
'native_name' => 'Italiano',
|
||||
],
|
||||
[
|
||||
'code' => 'iu',
|
||||
'code' => 'iu',
|
||||
'native_name' => 'ᐃᓄᒃᑎᑐᑦ',
|
||||
],
|
||||
[
|
||||
'code' => 'ja',
|
||||
'code' => 'ja',
|
||||
'native_name' => '日本語 (にほんご)',
|
||||
],
|
||||
[
|
||||
'code' => 'jv',
|
||||
'code' => 'jv',
|
||||
'native_name' => 'ꦧꦱꦗꦮ, Basa Jawa',
|
||||
],
|
||||
[
|
||||
'code' => 'ka',
|
||||
'code' => 'ka',
|
||||
'native_name' => 'ქართული',
|
||||
],
|
||||
[
|
||||
'code' => 'kg',
|
||||
'code' => 'kg',
|
||||
'native_name' => 'Kikongo',
|
||||
],
|
||||
[
|
||||
'code' => 'ki',
|
||||
'code' => 'ki',
|
||||
'native_name' => 'Gĩkũyũ',
|
||||
],
|
||||
[
|
||||
'code' => 'kj',
|
||||
'code' => 'kj',
|
||||
'native_name' => 'Kuanyama',
|
||||
],
|
||||
[
|
||||
'code' => 'kk',
|
||||
'code' => 'kk',
|
||||
'native_name' => 'қазақ тілі',
|
||||
],
|
||||
[
|
||||
'code' => 'kl',
|
||||
'code' => 'kl',
|
||||
'native_name' => 'Kalaallisut, kalaallit oqaasii',
|
||||
],
|
||||
[
|
||||
'code' => 'km',
|
||||
'code' => 'km',
|
||||
'native_name' => 'ខ្មែរ, ខេមរភាសា, ភាសាខ្មែរ',
|
||||
],
|
||||
[
|
||||
'code' => 'kn',
|
||||
'code' => 'kn',
|
||||
'native_name' => 'ಕನ್ನಡ',
|
||||
],
|
||||
[
|
||||
'code' => 'ko',
|
||||
'code' => 'ko',
|
||||
'native_name' => '한국어',
|
||||
],
|
||||
[
|
||||
'code' => 'kr',
|
||||
'code' => 'kr',
|
||||
'native_name' => 'Kanuri',
|
||||
],
|
||||
[
|
||||
'code' => 'ks',
|
||||
'code' => 'ks',
|
||||
'native_name' => 'कश्मीरी, كشميري',
|
||||
],
|
||||
[
|
||||
'code' => 'ku',
|
||||
'code' => 'ku',
|
||||
'native_name' => 'Kurdî, کوردی',
|
||||
],
|
||||
[
|
||||
'code' => 'kv',
|
||||
'code' => 'kv',
|
||||
'native_name' => 'коми кыв',
|
||||
],
|
||||
[
|
||||
'code' => 'kw',
|
||||
'code' => 'kw',
|
||||
'native_name' => 'Kernewek',
|
||||
],
|
||||
[
|
||||
'code' => 'ky',
|
||||
'code' => 'ky',
|
||||
'native_name' => 'Кыргызча, Кыргыз тили',
|
||||
],
|
||||
[
|
||||
'code' => 'la',
|
||||
'code' => 'la',
|
||||
'native_name' => 'Latine, lingua latina',
|
||||
],
|
||||
[
|
||||
'code' => 'lb',
|
||||
'code' => 'lb',
|
||||
'native_name' => 'Lëtzebuergesch',
|
||||
],
|
||||
[
|
||||
'code' => 'lg',
|
||||
'code' => 'lg',
|
||||
'native_name' => 'Luganda',
|
||||
],
|
||||
[
|
||||
'code' => 'li',
|
||||
'code' => 'li',
|
||||
'native_name' => 'Limburgs',
|
||||
],
|
||||
[
|
||||
'code' => 'ln',
|
||||
'code' => 'ln',
|
||||
'native_name' => 'Lingála',
|
||||
],
|
||||
[
|
||||
'code' => 'lo',
|
||||
'code' => 'lo',
|
||||
'native_name' => 'ພາສາລາວ',
|
||||
],
|
||||
[
|
||||
'code' => 'lt',
|
||||
'code' => 'lt',
|
||||
'native_name' => 'Lietuvių kalba',
|
||||
],
|
||||
[
|
||||
'code' => 'lu',
|
||||
'code' => 'lu',
|
||||
'native_name' => 'Kiluba',
|
||||
],
|
||||
[
|
||||
'code' => 'lv',
|
||||
'code' => 'lv',
|
||||
'native_name' => 'Latviešu valoda',
|
||||
],
|
||||
[
|
||||
'code' => 'mg',
|
||||
'code' => 'mg',
|
||||
'native_name' => 'Fiteny malagasy',
|
||||
],
|
||||
[
|
||||
'code' => 'mh',
|
||||
'code' => 'mh',
|
||||
'native_name' => 'Kajin M̧ajeļ',
|
||||
],
|
||||
[
|
||||
'code' => 'mi',
|
||||
'code' => 'mi',
|
||||
'native_name' => 'Te reo Māori',
|
||||
],
|
||||
[
|
||||
'code' => 'mk',
|
||||
'code' => 'mk',
|
||||
'native_name' => 'македонски јазик',
|
||||
],
|
||||
[
|
||||
'code' => 'ml',
|
||||
'code' => 'ml',
|
||||
'native_name' => 'മലയാളം',
|
||||
],
|
||||
[
|
||||
'code' => 'mn',
|
||||
'code' => 'mn',
|
||||
'native_name' => 'Монгол хэл',
|
||||
],
|
||||
[
|
||||
'code' => 'mr',
|
||||
'code' => 'mr',
|
||||
'native_name' => 'मराठी',
|
||||
],
|
||||
[
|
||||
'code' => 'ms',
|
||||
'code' => 'ms',
|
||||
'native_name' => 'Bahasa Melayu, بهاس ملايو',
|
||||
],
|
||||
[
|
||||
'code' => 'mt',
|
||||
'code' => 'mt',
|
||||
'native_name' => 'Malti',
|
||||
],
|
||||
[
|
||||
'code' => 'my',
|
||||
'code' => 'my',
|
||||
'native_name' => 'ဗမာစာ',
|
||||
],
|
||||
[
|
||||
'code' => 'na',
|
||||
'code' => 'na',
|
||||
'native_name' => 'Dorerin Naoero',
|
||||
],
|
||||
[
|
||||
'code' => 'nb',
|
||||
'code' => 'nb',
|
||||
'native_name' => 'Norsk Bokmål',
|
||||
],
|
||||
[
|
||||
'code' => 'nd',
|
||||
'code' => 'nd',
|
||||
'native_name' => 'isiNdebele',
|
||||
],
|
||||
[
|
||||
'code' => 'ne',
|
||||
'code' => 'ne',
|
||||
'native_name' => 'नेपाली',
|
||||
],
|
||||
[
|
||||
'code' => 'ng',
|
||||
'code' => 'ng',
|
||||
'native_name' => 'Owambo',
|
||||
],
|
||||
[
|
||||
'code' => 'nl',
|
||||
'code' => 'nl',
|
||||
'native_name' => 'Nederlands, Vlaams',
|
||||
],
|
||||
[
|
||||
'code' => 'nn',
|
||||
'code' => 'nn',
|
||||
'native_name' => 'Norsk Nynorsk',
|
||||
],
|
||||
[
|
||||
'code' => 'no',
|
||||
'code' => 'no',
|
||||
'native_name' => 'Norsk',
|
||||
],
|
||||
[
|
||||
'code' => 'nr',
|
||||
'code' => 'nr',
|
||||
'native_name' => 'isiNdebele',
|
||||
],
|
||||
[
|
||||
'code' => 'nv',
|
||||
'code' => 'nv',
|
||||
'native_name' => 'Diné bizaad',
|
||||
],
|
||||
[
|
||||
'code' => 'ny',
|
||||
'code' => 'ny',
|
||||
'native_name' => 'Chicheŵa, chinyanja',
|
||||
],
|
||||
[
|
||||
'code' => 'oc',
|
||||
'code' => 'oc',
|
||||
'native_name' => 'Occitan, lenga d’òc',
|
||||
],
|
||||
[
|
||||
'code' => 'oj',
|
||||
'code' => 'oj',
|
||||
'native_name' => 'ᐊᓂᔑᓈᐯᒧᐎᓐ',
|
||||
],
|
||||
[
|
||||
'code' => 'om',
|
||||
'code' => 'om',
|
||||
'native_name' => 'Afaan Oromoo',
|
||||
],
|
||||
[
|
||||
'code' => 'or',
|
||||
'code' => 'or',
|
||||
'native_name' => 'ଓଡ଼ିଆ',
|
||||
],
|
||||
[
|
||||
'code' => 'os',
|
||||
'code' => 'os',
|
||||
'native_name' => 'ирон æвзаг',
|
||||
],
|
||||
[
|
||||
'code' => 'pa',
|
||||
'code' => 'pa',
|
||||
'native_name' => 'ਪੰਜਾਬੀ, پنجابی',
|
||||
],
|
||||
[
|
||||
'code' => 'pi',
|
||||
'code' => 'pi',
|
||||
'native_name' => 'पालि, पाळि',
|
||||
],
|
||||
[
|
||||
'code' => 'pl',
|
||||
'code' => 'pl',
|
||||
'native_name' => 'język polski, polszczyzna',
|
||||
],
|
||||
[
|
||||
'code' => 'ps',
|
||||
'code' => 'ps',
|
||||
'native_name' => 'پښتو',
|
||||
],
|
||||
[
|
||||
'code' => 'pt',
|
||||
'code' => 'pt',
|
||||
'native_name' => 'Português',
|
||||
],
|
||||
[
|
||||
'code' => 'qu',
|
||||
'code' => 'qu',
|
||||
'native_name' => 'Runa Simi, Kichwa',
|
||||
],
|
||||
[
|
||||
'code' => 'rm',
|
||||
'code' => 'rm',
|
||||
'native_name' => 'Rumantsch Grischun',
|
||||
],
|
||||
[
|
||||
'code' => 'rn',
|
||||
'code' => 'rn',
|
||||
'native_name' => 'Ikirundi',
|
||||
],
|
||||
[
|
||||
'code' => 'ro',
|
||||
'code' => 'ro',
|
||||
'native_name' => 'Română',
|
||||
],
|
||||
[
|
||||
'code' => 'ru',
|
||||
'code' => 'ru',
|
||||
'native_name' => 'Pусский',
|
||||
],
|
||||
[
|
||||
'code' => 'rw',
|
||||
'code' => 'rw',
|
||||
'native_name' => 'Ikinyarwanda',
|
||||
],
|
||||
[
|
||||
'code' => 'sa',
|
||||
'code' => 'sa',
|
||||
'native_name' => 'संस्कृतम्',
|
||||
],
|
||||
[
|
||||
'code' => 'sc',
|
||||
'code' => 'sc',
|
||||
'native_name' => 'Sardu',
|
||||
],
|
||||
[
|
||||
'code' => 'sd',
|
||||
'code' => 'sd',
|
||||
'native_name' => 'सिन्धी, سنڌي، سندھی',
|
||||
],
|
||||
[
|
||||
'code' => 'se',
|
||||
'code' => 'se',
|
||||
'native_name' => 'Davvisámegiella',
|
||||
],
|
||||
[
|
||||
'code' => 'sg',
|
||||
'code' => 'sg',
|
||||
'native_name' => 'Yângâ tî sängö',
|
||||
],
|
||||
[
|
||||
'code' => 'si',
|
||||
'code' => 'si',
|
||||
'native_name' => 'සිංහල',
|
||||
],
|
||||
[
|
||||
'code' => 'sk',
|
||||
'code' => 'sk',
|
||||
'native_name' => 'Slovenčina, Slovenský Jazyk',
|
||||
],
|
||||
[
|
||||
'code' => 'sl',
|
||||
'code' => 'sl',
|
||||
'native_name' => 'Slovenski Jezik, Slovenščina',
|
||||
],
|
||||
[
|
||||
'code' => 'sm',
|
||||
'code' => 'sm',
|
||||
'native_name' => "Gagana fa'a Samoa",
|
||||
],
|
||||
[
|
||||
'code' => 'sn',
|
||||
'code' => 'sn',
|
||||
'native_name' => 'chiShona',
|
||||
],
|
||||
[
|
||||
'code' => 'so',
|
||||
'code' => 'so',
|
||||
'native_name' => 'Soomaaliga, af Soomaali',
|
||||
],
|
||||
[
|
||||
'code' => 'sq',
|
||||
'code' => 'sq',
|
||||
'native_name' => 'Shqip',
|
||||
],
|
||||
[
|
||||
'code' => 'sr',
|
||||
'code' => 'sr',
|
||||
'native_name' => 'српски језик',
|
||||
],
|
||||
[
|
||||
'code' => 'ss',
|
||||
'code' => 'ss',
|
||||
'native_name' => 'SiSwati',
|
||||
],
|
||||
[
|
||||
'code' => 'st',
|
||||
'code' => 'st',
|
||||
'native_name' => 'Sesotho',
|
||||
],
|
||||
[
|
||||
'code' => 'su',
|
||||
'code' => 'su',
|
||||
'native_name' => 'Basa Sunda',
|
||||
],
|
||||
[
|
||||
'code' => 'sv',
|
||||
'code' => 'sv',
|
||||
'native_name' => 'Svenska',
|
||||
],
|
||||
[
|
||||
'code' => 'sw',
|
||||
'code' => 'sw',
|
||||
'native_name' => 'Kiswahili',
|
||||
],
|
||||
[
|
||||
'code' => 'ta',
|
||||
'code' => 'ta',
|
||||
'native_name' => 'தமிழ்',
|
||||
],
|
||||
[
|
||||
'code' => 'te',
|
||||
'code' => 'te',
|
||||
'native_name' => 'తెలుగు',
|
||||
],
|
||||
[
|
||||
'code' => 'tg',
|
||||
'code' => 'tg',
|
||||
'native_name' => 'тоҷикӣ, toçikī, تاجیکی',
|
||||
],
|
||||
[
|
||||
'code' => 'th',
|
||||
'code' => 'th',
|
||||
'native_name' => 'ไทย',
|
||||
],
|
||||
[
|
||||
'code' => 'ti',
|
||||
'code' => 'ti',
|
||||
'native_name' => 'ትግርኛ',
|
||||
],
|
||||
[
|
||||
'code' => 'tk',
|
||||
'code' => 'tk',
|
||||
'native_name' => 'Türkmen, Түркмен',
|
||||
],
|
||||
[
|
||||
'code' => 'tl',
|
||||
'code' => 'tl',
|
||||
'native_name' => 'Wikang Tagalog',
|
||||
],
|
||||
[
|
||||
'code' => 'tn',
|
||||
'code' => 'tn',
|
||||
'native_name' => 'Setswana',
|
||||
],
|
||||
[
|
||||
'code' => 'to',
|
||||
'code' => 'to',
|
||||
'native_name' => 'Faka Tonga',
|
||||
],
|
||||
[
|
||||
'code' => 'tr',
|
||||
'code' => 'tr',
|
||||
'native_name' => 'Türkçe',
|
||||
],
|
||||
[
|
||||
'code' => 'ts',
|
||||
'code' => 'ts',
|
||||
'native_name' => 'Xitsonga',
|
||||
],
|
||||
[
|
||||
'code' => 'tt',
|
||||
'code' => 'tt',
|
||||
'native_name' => 'татар теле, tatar tele',
|
||||
],
|
||||
[
|
||||
'code' => 'tw',
|
||||
'code' => 'tw',
|
||||
'native_name' => 'Twi',
|
||||
],
|
||||
[
|
||||
'code' => 'ty',
|
||||
'code' => 'ty',
|
||||
'native_name' => 'Reo Tahiti',
|
||||
],
|
||||
[
|
||||
'code' => 'ug',
|
||||
'code' => 'ug',
|
||||
'native_name' => 'ئۇيغۇرچە, Uyghurche',
|
||||
],
|
||||
[
|
||||
'code' => 'uk',
|
||||
'code' => 'uk',
|
||||
'native_name' => 'Українська',
|
||||
],
|
||||
[
|
||||
'code' => 'ur',
|
||||
'code' => 'ur',
|
||||
'native_name' => 'اردو',
|
||||
],
|
||||
[
|
||||
'code' => 'uz',
|
||||
'code' => 'uz',
|
||||
'native_name' => 'Oʻzbek, Ўзбек, أۇزبېك',
|
||||
],
|
||||
[
|
||||
'code' => 've',
|
||||
'code' => 've',
|
||||
'native_name' => 'Tshivenḓa',
|
||||
],
|
||||
[
|
||||
'code' => 'vi',
|
||||
'code' => 'vi',
|
||||
'native_name' => 'Tiếng Việt',
|
||||
],
|
||||
[
|
||||
'code' => 'vo',
|
||||
'code' => 'vo',
|
||||
'native_name' => 'Volapük',
|
||||
],
|
||||
[
|
||||
'code' => 'wa',
|
||||
'code' => 'wa',
|
||||
'native_name' => 'Walon',
|
||||
],
|
||||
[
|
||||
'code' => 'wo',
|
||||
'code' => 'wo',
|
||||
'native_name' => 'Wollof',
|
||||
],
|
||||
[
|
||||
'code' => 'xh',
|
||||
'code' => 'xh',
|
||||
'native_name' => 'isiXhosa',
|
||||
],
|
||||
[
|
||||
'code' => 'yi',
|
||||
'code' => 'yi',
|
||||
'native_name' => 'ייִדיש',
|
||||
],
|
||||
[
|
||||
'code' => 'yo',
|
||||
'code' => 'yo',
|
||||
'native_name' => 'Yorùbá',
|
||||
],
|
||||
[
|
||||
'code' => 'za',
|
||||
'code' => 'za',
|
||||
'native_name' => 'Saɯ cueŋƅ, Saw cuengh',
|
||||
],
|
||||
[
|
||||
'code' => 'zh',
|
||||
'code' => 'zh',
|
||||
'native_name' => '中文 (Zhōngwén), 汉语, 漢語',
|
||||
],
|
||||
[
|
||||
'code' => 'zu',
|
||||
'code' => 'zu',
|
||||
'native_name' => 'isiZulu',
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,619 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class PlatformsSeeder Inserts values in platforms table in database
|
||||
*
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Database\Seeds;
|
||||
|
||||
use CodeIgniter\Database\Seeder;
|
||||
|
||||
class PlatformSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$podcastingData = [
|
||||
[
|
||||
'slug' => 'amazon',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Amazon Music and Audible',
|
||||
'home_url' => 'https://music.amazon.com/podcasts',
|
||||
'submit_url' => 'http://amazon.com/podcasters',
|
||||
],
|
||||
[
|
||||
'slug' => 'antennapod',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'AntennaPod',
|
||||
'home_url' => 'https://antennapod.org/',
|
||||
'submit_url' => 'https://api.podcastindex.org/signup',
|
||||
],
|
||||
[
|
||||
'slug' => 'apple',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Apple Podcasts',
|
||||
'home_url' => 'https://www.apple.com/itunes/podcasts/',
|
||||
'submit_url' =>
|
||||
'https://podcastsconnect.apple.com/my-podcasts/new-feed',
|
||||
],
|
||||
[
|
||||
'slug' => 'blubrry',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Blubrry',
|
||||
'home_url' => 'https://www.blubrry.com/',
|
||||
'submit_url' => 'https://www.blubrry.com/addpodcast.php',
|
||||
],
|
||||
[
|
||||
'slug' => 'breaker',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Breaker',
|
||||
'home_url' => 'https://www.breaker.audio/',
|
||||
'submit_url' => 'https://podcasters.breaker.audio/',
|
||||
],
|
||||
[
|
||||
'slug' => 'castbox',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Castbox',
|
||||
'home_url' => 'https://castbox.fm/',
|
||||
'submit_url' =>
|
||||
'https://helpcenter.castbox.fm/portal/kb/articles/submit-my-podcast',
|
||||
],
|
||||
[
|
||||
'slug' => 'castopod',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Castopod',
|
||||
'home_url' => 'https://castopod.org/',
|
||||
'submit_url' => 'https://castopod.org/instances',
|
||||
],
|
||||
[
|
||||
'slug' => 'castro',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Castro',
|
||||
'home_url' => 'http://castro.fm/',
|
||||
'submit_url' =>
|
||||
'https://castro.fm/support/link-to-your-podcast-in-castro',
|
||||
],
|
||||
[
|
||||
'slug' => 'chartable',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Chartable',
|
||||
'home_url' => 'https://chartable.com/',
|
||||
'submit_url' => 'https://chartable.com/podcasts/submit',
|
||||
],
|
||||
[
|
||||
'slug' => 'deezer',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Deezer',
|
||||
'home_url' => 'https://www.deezer.com/',
|
||||
'submit_url' => 'https://podcasters.deezer.com/submission',
|
||||
],
|
||||
[
|
||||
'slug' => 'fyyd',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'fyyd',
|
||||
'home_url' => 'https://fyyd.de/',
|
||||
'submit_url' => 'https://fyyd.de/add-feed',
|
||||
],
|
||||
[
|
||||
'slug' => 'google',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Google Podcasts',
|
||||
'home_url' => 'https://podcasts.google.com/about',
|
||||
'submit_url' =>
|
||||
'https://search.google.com/search-console/about',
|
||||
],
|
||||
[
|
||||
'slug' => 'ivoox',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Ivoox',
|
||||
'home_url' => 'https://www.ivoox.com/',
|
||||
'submit_url' => 'http://www.ivoox.com/upload-podcast_u.html',
|
||||
],
|
||||
[
|
||||
'slug' => 'listennotes',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'ListenNotes',
|
||||
'home_url' => 'https://www.listennotes.com/',
|
||||
'submit_url' => 'https://www.listennotes.com/submit/',
|
||||
],
|
||||
[
|
||||
'slug' => 'overcast',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Overcast',
|
||||
'home_url' => 'https://overcast.fm/',
|
||||
'submit_url' => 'https://overcast.fm/podcasterinfo',
|
||||
],
|
||||
[
|
||||
'slug' => 'playerfm',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Player.Fm',
|
||||
'home_url' => 'https://player.fm/',
|
||||
'submit_url' => 'https://player.fm/importer/feed',
|
||||
],
|
||||
[
|
||||
'slug' => 'pocketcasts',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Pocketcasts',
|
||||
'home_url' => 'https://www.pocketcasts.com/',
|
||||
'submit_url' => 'https://www.pocketcasts.com/submit/',
|
||||
],
|
||||
[
|
||||
'slug' => 'podbean',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Podbean',
|
||||
'home_url' => 'https://www.podbean.com/',
|
||||
'submit_url' => 'https://www.podbean.com/site/submitPodcast',
|
||||
],
|
||||
[
|
||||
'slug' => 'podcastaddict',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Podcast Addict',
|
||||
'home_url' => 'https://podcastaddict.com/',
|
||||
'submit_url' => 'https://podcastaddict.com/submit',
|
||||
],
|
||||
[
|
||||
'slug' => 'podcastindex',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Podcast Index',
|
||||
'home_url' => 'https://podcastindex.org/',
|
||||
'submit_url' => 'https://api.podcastindex.org/signup',
|
||||
],
|
||||
[
|
||||
'slug' => 'podchaser',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Podchaser',
|
||||
'home_url' => 'https://www.podchaser.com/',
|
||||
'submit_url' => 'https://www.podchaser.com/creators/edit',
|
||||
],
|
||||
[
|
||||
'slug' => 'podcloud',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'podCloud',
|
||||
'home_url' => 'https://podcloud.fr/',
|
||||
'submit_url' => 'https://podcloud.fr/studio/podcasts/new',
|
||||
],
|
||||
[
|
||||
'slug' => 'podinstall',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Podinstall',
|
||||
'home_url' => 'https://www.podinstall.com/',
|
||||
'submit_url' => 'https://www.podinstall.com/claim.html',
|
||||
],
|
||||
[
|
||||
'slug' => 'podlink',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'pod.link',
|
||||
'home_url' => 'https://pod.link/',
|
||||
'submit_url' => 'https://pod.link',
|
||||
],
|
||||
[
|
||||
'slug' => 'podtail',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Podtail',
|
||||
'home_url' => 'https://podtail.com/',
|
||||
'submit_url' => 'https://podtail.com/about/faq/',
|
||||
],
|
||||
[
|
||||
'slug' => 'podfriend',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Podfriend',
|
||||
'home_url' => 'https://www.podfriend.com/',
|
||||
'submit_url' => 'https://api.podcastindex.org/signup',
|
||||
],
|
||||
[
|
||||
'slug' => 'podverse',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Podverse',
|
||||
'home_url' => 'https://podverse.fm/',
|
||||
'submit_url' =>
|
||||
'https://docs.google.com/forms/d/e/1FAIpQLSdewKP-YrE8zGjDPrkmoJEwCxPl_gizEkmzAlTYsiWAuAk1Ng/viewform',
|
||||
],
|
||||
[
|
||||
'slug' => 'radiopublic',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'RadioPublic',
|
||||
'home_url' => 'https://radiopublic.com/',
|
||||
'submit_url' => 'https://podcasters.radiopublic.com/signup',
|
||||
],
|
||||
[
|
||||
'slug' => 'spotify',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Spotify',
|
||||
'home_url' => 'https://www.spotify.com/',
|
||||
'submit_url' => 'https://podcasters.spotify.com/submit',
|
||||
],
|
||||
[
|
||||
'slug' => 'spreaker',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Spreaker',
|
||||
'home_url' => 'https://www.spreaker.com/',
|
||||
'submit_url' => 'https://www.spreaker.com/cms/shows/rss-import',
|
||||
],
|
||||
[
|
||||
'slug' => 'stitcher',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Stitcher',
|
||||
'home_url' => 'https://www.stitcher.com/',
|
||||
'submit_url' => 'https://partners.stitcher.com/join',
|
||||
],
|
||||
[
|
||||
'slug' => 'tunein',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'TuneIn',
|
||||
'home_url' => 'https://tunein.com/',
|
||||
'submit_url' =>
|
||||
'https://help.tunein.com/contact/add-podcast-S19TR3Sdf',
|
||||
],
|
||||
[
|
||||
'slug' => 'anytime',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Anytime Podcast Player',
|
||||
'home_url' => 'https://anytimeplayer.app/',
|
||||
'submit_url' => '',
|
||||
],
|
||||
[
|
||||
'slug' => 'breez',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Breez',
|
||||
'home_url' => 'https://breez.technology/',
|
||||
'submit_url' => '',
|
||||
],
|
||||
[
|
||||
'slug' => 'castamatic',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Castamatic',
|
||||
'home_url' => 'https://castamatic.com/',
|
||||
'submit_url' => '',
|
||||
],
|
||||
[
|
||||
'slug' => 'castcoverage',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'CastCoverage',
|
||||
'home_url' => 'http://castcoverage.com/',
|
||||
'submit_url' => '',
|
||||
],
|
||||
[
|
||||
'slug' => 'curiocaster',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'CurioCaster',
|
||||
'home_url' => 'https://curiocaster.com/',
|
||||
'submit_url' => '',
|
||||
],
|
||||
[
|
||||
'slug' => 'escapepod',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Escapepod',
|
||||
'home_url' => 'http://y20k.org/escapepod/',
|
||||
'submit_url' => '',
|
||||
],
|
||||
[
|
||||
'slug' => 'fountain',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Fountain',
|
||||
'home_url' => 'https://www.fountain.fm/',
|
||||
'submit_url' => '',
|
||||
],
|
||||
[
|
||||
'slug' => 'gpodder',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'gPodder',
|
||||
'home_url' => 'https://gpodder.org/',
|
||||
'submit_url' => '',
|
||||
],
|
||||
[
|
||||
'slug' => 'hypercatcher',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'HyperCatcher',
|
||||
'home_url' => 'https://hypercatcher.com/',
|
||||
'submit_url' => '',
|
||||
],
|
||||
[
|
||||
'slug' => 'ivyfm',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Ivy Podcast Discovery',
|
||||
'home_url' => 'https://ivy.fm/',
|
||||
'submit_url' => '',
|
||||
],
|
||||
[
|
||||
'slug' => 'jumplink',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'JumpLink',
|
||||
'home_url' => 'https://jump.link/',
|
||||
'submit_url' => 'https://jump.link/a/accounts/signup/',
|
||||
],
|
||||
[
|
||||
'slug' => 'kasts',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Kasts',
|
||||
'home_url' => 'https://apps.kde.org/kasts/',
|
||||
'submit_url' => '',
|
||||
],
|
||||
[
|
||||
'slug' => 'playapod',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Playapod',
|
||||
'home_url' => 'https://playapod.com/',
|
||||
'submit_url' => '',
|
||||
],
|
||||
[
|
||||
'slug' => 'plink',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Plink',
|
||||
'home_url' => 'https://plinkhq.com/',
|
||||
'submit_url' => '',
|
||||
],
|
||||
[
|
||||
'slug' => 'podcastchapters',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Podcast Chapters',
|
||||
'home_url' => 'https://chaptersapp.com/',
|
||||
'submit_url' => '',
|
||||
],
|
||||
[
|
||||
'slug' => 'podcastguru',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Podcast Guru',
|
||||
'home_url' => 'https://podcastguru.io/',
|
||||
'submit_url' => 'https://podcastguru.io/promote-your-podcast/',
|
||||
],
|
||||
[
|
||||
'slug' => 'podlp',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'PodLP',
|
||||
'home_url' => 'https://podlp.com/',
|
||||
'submit_url' => 'https://podlp.com/submit.html',
|
||||
],
|
||||
[
|
||||
'slug' => 'podnews',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'podnews',
|
||||
'home_url' => 'https://podnews.net/podcast/subscribe-pages',
|
||||
'submit_url' => '',
|
||||
],
|
||||
[
|
||||
'slug' => 'podstation',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'podStation',
|
||||
'home_url' => 'https://podstation.github.io/',
|
||||
'submit_url' => '',
|
||||
],
|
||||
[
|
||||
'slug' => 'sphinxchat',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Sphinx',
|
||||
'home_url' => 'https://sphinx.chat/',
|
||||
'submit_url' => '',
|
||||
],
|
||||
[
|
||||
'slug' => 'tsacdop',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Tsacdop',
|
||||
'home_url' => 'https://www.tsacdop.app/',
|
||||
'submit_url' => '',
|
||||
],
|
||||
[
|
||||
'slug' => 'zion',
|
||||
'type' => 'podcasting',
|
||||
'label' => 'Zion',
|
||||
'home_url' => 'https://getzion.com/',
|
||||
'submit_url' => 'https://shop.n2n2.chat/',
|
||||
],
|
||||
];
|
||||
|
||||
$fundingData = [
|
||||
[
|
||||
'slug' => 'paypal',
|
||||
'type' => 'funding',
|
||||
'label' => 'Paypal',
|
||||
'home_url' => 'https://www.paypal.com/',
|
||||
'submit_url' => 'https://www.paypal.com/paypalme/my/grab',
|
||||
],
|
||||
[
|
||||
'slug' => 'fosspay',
|
||||
'type' => 'funding',
|
||||
'label' => 'fosspay',
|
||||
'home_url' => 'https://git.sr.ht/~sircmpwn/fosspay',
|
||||
'submit_url' => '',
|
||||
],
|
||||
[
|
||||
'slug' => 'gofundme',
|
||||
'type' => 'funding',
|
||||
'label' => 'GoFundMe',
|
||||
'home_url' => 'https://www.gofundme.com/',
|
||||
'submit_url' => 'https://www.gofundme.com/sign-up',
|
||||
],
|
||||
[
|
||||
'slug' => 'helloasso',
|
||||
'type' => 'funding',
|
||||
'label' => 'helloasso',
|
||||
'home_url' => 'https://www.helloasso.com/',
|
||||
'submit_url' => 'https://auth.helloasso.com/inscription',
|
||||
],
|
||||
[
|
||||
'slug' => 'indiegogo',
|
||||
'type' => 'funding',
|
||||
'label' => 'Indiegogo',
|
||||
'home_url' => 'https://www.indiegogo.com/',
|
||||
'submit_url' => 'https://www.indiegogo.com/start-a-campaign#/',
|
||||
],
|
||||
[
|
||||
'slug' => 'kickstarter',
|
||||
'type' => 'funding',
|
||||
'label' => 'Kickstarter',
|
||||
'home_url' => 'https://www.kickstarter.com/',
|
||||
'submit_url' => 'https://www.kickstarter.com/learn',
|
||||
],
|
||||
[
|
||||
'slug' => 'kisskissbankbank',
|
||||
'type' => 'funding',
|
||||
'label' => 'KissKissBankBank',
|
||||
'home_url' => 'https://www.kisskissbankbank.com/',
|
||||
'submit_url' =>
|
||||
'https://www.kisskissbankbank.com/en/financer-mon-projet',
|
||||
],
|
||||
[
|
||||
'slug' => 'liberapay',
|
||||
'type' => 'funding',
|
||||
'label' => 'Liberapay',
|
||||
'home_url' => 'https://liberapay.com/',
|
||||
'submit_url' => 'https://liberapay.com/sign-up',
|
||||
],
|
||||
[
|
||||
'slug' => 'patreon',
|
||||
'type' => 'funding',
|
||||
'label' => 'Patreon',
|
||||
'home_url' => 'https://www.patreon.com/',
|
||||
'submit_url' => 'https://www.patreon.com/create',
|
||||
],
|
||||
[
|
||||
'slug' => 'tipeee',
|
||||
'type' => 'funding',
|
||||
'label' => 'Tipeee',
|
||||
'home_url' => 'https://tipeee.com/',
|
||||
'submit_url' => 'https://tipeee.com/register/',
|
||||
],
|
||||
[
|
||||
'slug' => 'ulule',
|
||||
'type' => 'funding',
|
||||
'label' => 'Ulule',
|
||||
'home_url' => 'https://www.ulule.com/',
|
||||
'submit_url' => 'https://www.ulule.com/projects/create/#/',
|
||||
],
|
||||
];
|
||||
|
||||
$socialData = [
|
||||
[
|
||||
'slug' => 'discord',
|
||||
'type' => 'social',
|
||||
'label' => 'Discord',
|
||||
'home_url' => 'https://discord.com/',
|
||||
'submit_url' => 'https://discord.com/register',
|
||||
],
|
||||
[
|
||||
'slug' => 'facebook',
|
||||
'type' => 'social',
|
||||
'label' => 'Facebook',
|
||||
'home_url' => 'https://www.facebook.com/',
|
||||
'submit_url' =>
|
||||
'https://www.facebook.com/pages/creation/?ref_type=comet_home',
|
||||
],
|
||||
[
|
||||
'slug' => 'funkwhale',
|
||||
'type' => 'social',
|
||||
'label' => 'Funkwhale',
|
||||
'home_url' => 'https://funkwhale.audio/',
|
||||
'submit_url' => 'https://network.funkwhale.audio/dashboards/',
|
||||
],
|
||||
[
|
||||
'slug' => 'instagram',
|
||||
'type' => 'social',
|
||||
'label' => 'Instagram',
|
||||
'home_url' => 'https://www.instagram.com/',
|
||||
'submit_url' =>
|
||||
'https://www.instagram.com/accounts/emailsignup/',
|
||||
],
|
||||
[
|
||||
'slug' => 'linkedin',
|
||||
'type' => 'social',
|
||||
'label' => 'LinkedIn',
|
||||
'home_url' => 'https://www.linkedin.com/',
|
||||
'submit_url' => 'https://www.linkedin.com/company/setup/new/',
|
||||
],
|
||||
[
|
||||
'slug' => 'mastodon',
|
||||
'type' => 'social',
|
||||
'label' => 'Mastodon',
|
||||
'home_url' => 'https://joinmastodon.org/',
|
||||
'submit_url' => 'https://joinmastodon.org/communities',
|
||||
],
|
||||
[
|
||||
'slug' => 'misskey',
|
||||
'type' => 'social',
|
||||
'label' => 'Misskey',
|
||||
'home_url' => 'https://join.misskey.page/',
|
||||
'submit_url' => 'https://join.misskey.page/en-US/instances',
|
||||
],
|
||||
[
|
||||
'slug' => 'mobilizon',
|
||||
'type' => 'social',
|
||||
'label' => 'Mobilizon',
|
||||
'home_url' => 'https://joinmobilizon.org/',
|
||||
'submit_url' => 'https://instances.joinmobilizon.org/instances',
|
||||
],
|
||||
[
|
||||
'slug' => 'peertube',
|
||||
'type' => 'social',
|
||||
'label' => 'PeerTube',
|
||||
'home_url' => 'https://joinpeertube.org/',
|
||||
'submit_url' => 'https://joinpeertube.org/instances',
|
||||
],
|
||||
[
|
||||
'slug' => 'pixelfed',
|
||||
'type' => 'social',
|
||||
'label' => 'Pixelfed',
|
||||
'home_url' => 'https://pixelfed.org/',
|
||||
'submit_url' => 'https://beta.joinpixelfed.org/',
|
||||
],
|
||||
[
|
||||
'slug' => 'pleroma',
|
||||
'type' => 'social',
|
||||
'label' => 'Pleroma',
|
||||
'home_url' => 'https://pleroma.social/',
|
||||
'submit_url' => 'https://pleroma.social/#featured-instances',
|
||||
],
|
||||
[
|
||||
'slug' => 'plume',
|
||||
'type' => 'social',
|
||||
'label' => 'Plume',
|
||||
'home_url' => 'https://joinplu.me/',
|
||||
'submit_url' => 'https://joinplu.me/#instances',
|
||||
],
|
||||
[
|
||||
'slug' => 'slack',
|
||||
'type' => 'social',
|
||||
'label' => 'Slack',
|
||||
'home_url' => 'https://slack.com/',
|
||||
'submit_url' => 'https://slack.com/get-started#/create',
|
||||
],
|
||||
[
|
||||
'slug' => 'twitch',
|
||||
'type' => 'social',
|
||||
'label' => 'Twitch',
|
||||
'home_url' => 'https://www.twitch.tv/',
|
||||
'submit_url' => 'https://www.twitch.tv/signup',
|
||||
],
|
||||
[
|
||||
'slug' => 'twitter',
|
||||
'type' => 'social',
|
||||
'label' => 'Twitter',
|
||||
'home_url' => 'https://twitter.com/',
|
||||
'submit_url' => 'https://twitter.com/i/flow/signup',
|
||||
],
|
||||
[
|
||||
'slug' => 'writefreely',
|
||||
'type' => 'social',
|
||||
'label' => 'WriteFreely',
|
||||
'home_url' => 'https://writefreely.org/',
|
||||
'submit_url' => 'https://writefreely.org/instances',
|
||||
],
|
||||
[
|
||||
'slug' => 'youtube',
|
||||
'type' => 'social',
|
||||
'label' => 'Youtube',
|
||||
'home_url' => 'https://www.youtube.com/',
|
||||
'submit_url' => 'https://creatoracademy.youtube.com/page/home',
|
||||
],
|
||||
];
|
||||
|
||||
$data = array_merge($podcastingData, $fundingData, $socialData);
|
||||
$this->db
|
||||
->table('platforms')
|
||||
->ignore(true)
|
||||
->insertBatch($data);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class TestSeeder Inserts a superadmin user in the database
|
||||
*
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Database\Seeds;
|
||||
|
||||
use CodeIgniter\Database\Seeder;
|
||||
|
||||
class TestSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
/**
|
||||
* Inserts an active user with the following credentials: username: admin password: AGUehL3P
|
||||
*/
|
||||
$this->db->table('users')
|
||||
->insert([
|
||||
'id' => 1,
|
||||
'username' => 'admin',
|
||||
'email' => 'admin@example.com',
|
||||
'password_hash' =>
|
||||
'$2y$10$TXJEHX/djW8jtzgpDVf7dOOCGo5rv1uqtAYWdwwwkttQcDkAeB2.6',
|
||||
'active' => 1,
|
||||
]);
|
||||
|
||||
$this->db
|
||||
->table('auth_groups_users')
|
||||
->insert([
|
||||
'group_id' => 1,
|
||||
'user_id' => 1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ namespace App\Entities;
|
|||
|
||||
use App\Models\PodcastModel;
|
||||
use Modules\Fediverse\Entities\Actor as FediverseActor;
|
||||
use RuntimeException;
|
||||
use Override;
|
||||
|
||||
/**
|
||||
* @property Podcast|null $podcast
|
||||
|
|
@ -26,34 +26,33 @@ class Actor extends FediverseActor
|
|||
|
||||
public function getIsPodcast(): bool
|
||||
{
|
||||
return $this->getPodcast() !== null;
|
||||
return $this->getPodcast() instanceof Podcast;
|
||||
}
|
||||
|
||||
public function getPodcast(): ?Podcast
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Podcast id must be set before getting associated podcast.');
|
||||
}
|
||||
|
||||
if (! $this->podcast instanceof Podcast) {
|
||||
$this->podcast = (new PodcastModel())->getPodcastByActorId($this->id);
|
||||
$this->podcast = new PodcastModel()
|
||||
->getPodcastByActorId($this->id);
|
||||
}
|
||||
|
||||
return $this->podcast;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getAvatarImageUrl(): string
|
||||
{
|
||||
if ($this->podcast !== null) {
|
||||
if ($this->podcast instanceof Podcast) {
|
||||
return $this->podcast->cover->thumbnail_url;
|
||||
}
|
||||
|
||||
return parent::getAvatarImageUrl();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getAvatarImageMimetype(): string
|
||||
{
|
||||
if ($this->podcast !== null) {
|
||||
if ($this->podcast instanceof Podcast) {
|
||||
return $this->podcast->cover->thumbnail_mimetype;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use CodeIgniter\Entity\Entity;
|
|||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $parent_id
|
||||
* @property ?int $parent_id
|
||||
* @property Category|null $parent
|
||||
* @property string $code
|
||||
* @property string $apple_category
|
||||
|
|
@ -29,22 +29,20 @@ class Category extends Entity
|
|||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'parent_id' => '?integer',
|
||||
'code' => 'string',
|
||||
'apple_category' => 'string',
|
||||
'id' => 'integer',
|
||||
'parent_id' => '?integer',
|
||||
'code' => 'string',
|
||||
'apple_category' => 'string',
|
||||
'google_category' => 'string',
|
||||
];
|
||||
|
||||
/**
|
||||
* @noRector ReturnTypeDeclarationRector
|
||||
*/
|
||||
public function getParent(): ?self
|
||||
{
|
||||
if ($this->parent_id === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (new CategoryModel())->getCategoryById($this->parent_id);
|
||||
return new CategoryModel()
|
||||
->getCategoryById($this->parent_id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,17 +11,17 @@ declare(strict_types=1);
|
|||
namespace App\Entities\Clip;
|
||||
|
||||
use App\Entities\Episode;
|
||||
use App\Entities\Media\Audio;
|
||||
use App\Entities\Media\Video;
|
||||
use App\Entities\Podcast;
|
||||
use App\Models\EpisodeModel;
|
||||
use App\Models\MediaModel;
|
||||
use App\Models\PodcastModel;
|
||||
use App\Models\UserModel;
|
||||
use CodeIgniter\Entity\Entity;
|
||||
use CodeIgniter\Files\File;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use Modules\Auth\Entities\User;
|
||||
use CodeIgniter\Shield\Entities\User;
|
||||
use Modules\Auth\Models\UserModel;
|
||||
use Modules\Media\Entities\Audio;
|
||||
use Modules\Media\Entities\Video;
|
||||
use Modules\Media\Models\MediaModel;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
|
|
@ -31,12 +31,12 @@ use Modules\Auth\Entities\User;
|
|||
* @property Episode $episode
|
||||
* @property string $title
|
||||
* @property double $start_time
|
||||
* @property double $end_time
|
||||
* @property ?double $end_time
|
||||
* @property double $duration
|
||||
* @property string $type
|
||||
* @property int|null $media_id
|
||||
* @property Video|Audio|null $media
|
||||
* @property array|null $metadata
|
||||
* @property array<mixed>|null $metadata
|
||||
* @property string $status
|
||||
* @property string $logs
|
||||
* @property User $user
|
||||
|
|
@ -57,7 +57,8 @@ class BaseClip extends Entity
|
|||
protected ?float $end_time = null;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
* @var array<int, string>
|
||||
* @phpstan-var list<string>
|
||||
*/
|
||||
protected $dates = ['created_at', 'updated_at', 'job_started_at', 'job_ended_at'];
|
||||
|
||||
|
|
@ -65,29 +66,21 @@ class BaseClip extends Entity
|
|||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'id' => 'integer',
|
||||
'podcast_id' => 'integer',
|
||||
'episode_id' => 'integer',
|
||||
'title' => 'string',
|
||||
'title' => 'string',
|
||||
'start_time' => 'double',
|
||||
'duration' => 'double',
|
||||
'type' => 'string',
|
||||
'media_id' => '?integer',
|
||||
'metadata' => '?json-array',
|
||||
'status' => 'string',
|
||||
'logs' => 'string',
|
||||
'duration' => 'double',
|
||||
'type' => 'string',
|
||||
'media_id' => '?integer',
|
||||
'metadata' => '?json-array',
|
||||
'status' => 'string',
|
||||
'logs' => 'string',
|
||||
'created_by' => 'integer',
|
||||
'updated_by' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $data
|
||||
*/
|
||||
public function __construct(array $data = null)
|
||||
{
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
public function getJobDuration(): ?int
|
||||
{
|
||||
if ($this->job_duration === null && $this->job_started_at && $this->job_ended_at) {
|
||||
|
|
@ -109,44 +102,43 @@ class BaseClip extends Entity
|
|||
|
||||
public function getPodcast(): ?Podcast
|
||||
{
|
||||
return (new PodcastModel())->getPodcastById($this->podcast_id);
|
||||
return new PodcastModel()
|
||||
->getPodcastById($this->podcast_id);
|
||||
}
|
||||
|
||||
public function getEpisode(): ?Episode
|
||||
{
|
||||
return (new EpisodeModel())->getEpisodeById($this->episode_id);
|
||||
return new EpisodeModel()
|
||||
->getEpisodeById($this->episode_id);
|
||||
}
|
||||
|
||||
public function getUser(): ?User
|
||||
{
|
||||
return (new UserModel())->find($this->created_by);
|
||||
/** @var ?User */
|
||||
return new UserModel()
|
||||
->find($this->created_by);
|
||||
}
|
||||
|
||||
public function setMedia(string $filePath = null): static
|
||||
public function setMedia(File $file, string $fileKey): static
|
||||
{
|
||||
if ($filePath === null) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$file = new File($filePath);
|
||||
|
||||
if ($this->media_id !== null) {
|
||||
$this->getMedia()
|
||||
->setFile($file);
|
||||
$this->getMedia()
|
||||
->updated_by = (int) user_id();
|
||||
(new MediaModel('audio'))->updateMedia($this->getMedia());
|
||||
->updated_by = $this->attributes['updated_by'];
|
||||
new MediaModel('audio')
|
||||
->updateMedia($this->getMedia());
|
||||
} else {
|
||||
$media = new Audio([
|
||||
'file_path' => $filePath,
|
||||
'file_key' => $fileKey,
|
||||
'language_code' => $this->getPodcast()
|
||||
->language_code,
|
||||
'uploaded_by' => $this->attributes['created_by'],
|
||||
'updated_by' => $this->attributes['created_by'],
|
||||
'uploaded_by' => $this->attributes['updated_by'],
|
||||
'updated_by' => $this->attributes['updated_by'],
|
||||
]);
|
||||
$media->setFile($file);
|
||||
|
||||
$this->attributes['media_id'] = (new MediaModel())->saveMedia($media);
|
||||
$this->attributes['media_id'] = new MediaModel()->saveMedia($media);
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
|
@ -155,7 +147,8 @@ class BaseClip extends Entity
|
|||
public function getMedia(): Audio | Video | null
|
||||
{
|
||||
if ($this->media_id !== null && $this->media === null) {
|
||||
$this->media = (new MediaModel($this->type))->getMediaById($this->media_id);
|
||||
$this->media = new MediaModel($this->type)
|
||||
->getMediaById($this->media_id);
|
||||
}
|
||||
|
||||
return $this->media;
|
||||
|
|
|
|||
|
|
@ -10,12 +10,13 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entities\Clip;
|
||||
|
||||
use App\Entities\Media\Video;
|
||||
use App\Models\MediaModel;
|
||||
use CodeIgniter\Files\File;
|
||||
use Modules\Media\Entities\Video;
|
||||
use Modules\Media\Models\MediaModel;
|
||||
use Override;
|
||||
|
||||
/**
|
||||
* @property array $theme
|
||||
* @property array{name:string,preview:string} $theme
|
||||
* @property string $format
|
||||
*/
|
||||
class VideoClip extends BaseClip
|
||||
|
|
@ -25,7 +26,7 @@ class VideoClip extends BaseClip
|
|||
/**
|
||||
* @param array<string, mixed>|null $data
|
||||
*/
|
||||
public function __construct(array $data = null)
|
||||
public function __construct(?array $data = null)
|
||||
{
|
||||
parent::__construct($data);
|
||||
|
||||
|
|
@ -36,7 +37,7 @@ class VideoClip extends BaseClip
|
|||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $theme
|
||||
* @param array{name:string,preview:string} $theme
|
||||
*/
|
||||
public function setTheme(array $theme): self
|
||||
{
|
||||
|
|
@ -53,7 +54,7 @@ class VideoClip extends BaseClip
|
|||
|
||||
public function setFormat(string $format): self
|
||||
{
|
||||
$this->attributes['metadata'] = json_decode($this->attributes['metadata'], true);
|
||||
$this->attributes['metadata'] = json_decode((string) $this->attributes['metadata'], true);
|
||||
|
||||
$this->attributes['format'] = $format;
|
||||
$this->attributes['metadata']['format'] = $format;
|
||||
|
|
@ -63,30 +64,24 @@ class VideoClip extends BaseClip
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setMedia(string $filePath = null): static
|
||||
#[Override]
|
||||
public function setMedia(File $file, string $fileKey): static
|
||||
{
|
||||
if ($filePath === null) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($this->attributes['media_id'] !== null) {
|
||||
// media is already set, do nothing
|
||||
return $this;
|
||||
}
|
||||
|
||||
helper('media');
|
||||
$file = new File(media_path($filePath));
|
||||
|
||||
$video = new Video([
|
||||
'file_path' => $filePath,
|
||||
'file_key' => $fileKey,
|
||||
'language_code' => $this->getPodcast()
|
||||
->language_code,
|
||||
'uploaded_by' => $this->attributes['created_by'],
|
||||
'updated_by' => $this->attributes['created_by'],
|
||||
'updated_by' => $this->attributes['created_by'],
|
||||
]);
|
||||
$video->setFile($file);
|
||||
|
||||
$this->attributes['media_id'] = (new MediaModel())->saveMedia($video);
|
||||
$this->attributes['media_id'] = new MediaModel('video')->saveMedia($video);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,22 +45,19 @@ class Credit extends Entity
|
|||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'podcast_id' => 'integer',
|
||||
'episode_id' => '?integer',
|
||||
'person_id' => 'integer',
|
||||
'full_name' => 'string',
|
||||
'podcast_id' => 'integer',
|
||||
'episode_id' => '?integer',
|
||||
'person_id' => 'integer',
|
||||
'full_name' => 'string',
|
||||
'person_group' => 'string',
|
||||
'person_role' => 'string',
|
||||
'person_role' => 'string',
|
||||
];
|
||||
|
||||
public function getPerson(): ?Person
|
||||
{
|
||||
if ($this->person_id === null) {
|
||||
throw new RuntimeException('Credit must have person_id before getting person.');
|
||||
}
|
||||
|
||||
if (! $this->person instanceof Person) {
|
||||
$this->person = (new PersonModel())->getPersonById($this->person_id);
|
||||
$this->person = new PersonModel()
|
||||
->getPersonById($this->person_id);
|
||||
}
|
||||
|
||||
return $this->person;
|
||||
|
|
@ -68,12 +65,9 @@ class Credit extends Entity
|
|||
|
||||
public function getPodcast(): ?Podcast
|
||||
{
|
||||
if ($this->podcast_id === null) {
|
||||
throw new RuntimeException('Credit must have podcast_id before getting podcast.');
|
||||
}
|
||||
|
||||
if (! $this->podcast instanceof Podcast) {
|
||||
$this->podcast = (new PodcastModel())->getPodcastById($this->podcast_id);
|
||||
$this->podcast = new PodcastModel()
|
||||
->getPodcastById($this->podcast_id);
|
||||
}
|
||||
|
||||
return $this->podcast;
|
||||
|
|
@ -86,27 +80,23 @@ class Credit extends Entity
|
|||
}
|
||||
|
||||
if (! $this->episode instanceof Episode) {
|
||||
$this->episode = (new EpisodeModel())->getPublishedEpisodeById($this->podcast_id, $this->episode_id);
|
||||
$this->episode = new EpisodeModel()
|
||||
->getPublishedEpisodeById($this->podcast_id, $this->episode_id);
|
||||
}
|
||||
|
||||
return $this->episode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @noRector ReturnTypeDeclarationRector
|
||||
*/
|
||||
public function getGroupLabel(): string
|
||||
{
|
||||
if ($this->person_group === null) {
|
||||
if ($this->person_group === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
return lang("PersonsTaxonomy.persons.{$this->person_group}.label");
|
||||
}
|
||||
|
||||
/**
|
||||
* @noRector ReturnTypeDeclarationRector
|
||||
*/
|
||||
public function getRoleLabel(): string
|
||||
{
|
||||
if ($this->person_group === '') {
|
||||
|
|
@ -117,6 +107,7 @@ class Credit extends Entity
|
|||
return '';
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
return lang("PersonsTaxonomy.persons.{$this->person_group}.roles.{$this->person_role}.label");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,14 +11,9 @@ declare(strict_types=1);
|
|||
namespace App\Entities;
|
||||
|
||||
use App\Entities\Clip\Soundbite;
|
||||
use App\Entities\Media\Audio;
|
||||
use App\Entities\Media\Chapters;
|
||||
use App\Entities\Media\Image;
|
||||
use App\Entities\Media\Transcript;
|
||||
use App\Libraries\SimpleRSSElement;
|
||||
use App\Models\ClipModel;
|
||||
use App\Models\EpisodeCommentModel;
|
||||
use App\Models\MediaModel;
|
||||
use App\Models\EpisodeModel;
|
||||
use App\Models\PersonModel;
|
||||
use App\Models\PodcastModel;
|
||||
use App\Models\PostModel;
|
||||
|
|
@ -26,32 +21,40 @@ use CodeIgniter\Entity\Entity;
|
|||
use CodeIgniter\Files\File;
|
||||
use CodeIgniter\HTTP\Files\UploadedFile;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use Exception;
|
||||
use League\CommonMark\Environment\Environment;
|
||||
use League\CommonMark\Extension\Autolink\AutolinkExtension;
|
||||
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
|
||||
use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
|
||||
use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
|
||||
use League\CommonMark\MarkdownConverter;
|
||||
use RuntimeException;
|
||||
use Modules\Media\Entities\Audio;
|
||||
use Modules\Media\Entities\Chapters;
|
||||
use Modules\Media\Entities\Image;
|
||||
use Modules\Media\Entities\Transcript;
|
||||
use Modules\Media\Models\MediaModel;
|
||||
use Override;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $podcast_id
|
||||
* @property Podcast $podcast
|
||||
* @property ?string $preview_id
|
||||
* @property string $preview_link
|
||||
* @property string $link
|
||||
* @property string $guid
|
||||
* @property string $slug
|
||||
* @property string $title
|
||||
* @property int $audio_id
|
||||
* @property Audio $audio
|
||||
* @property string $audio_analytics_url
|
||||
* @property ?Audio $audio
|
||||
* @property string $audio_url
|
||||
* @property string $audio_web_url
|
||||
* @property string $audio_opengraph_url
|
||||
* @property string|null $description Holds text only description, striped of any markdown or html special characters
|
||||
* @property string $description_markdown
|
||||
* @property string $description_html
|
||||
* @property int $cover_id
|
||||
* @property Image $cover
|
||||
* @property ?int $cover_id
|
||||
* @property ?Image $cover
|
||||
* @property int|null $transcript_id
|
||||
* @property Transcript|null $transcript
|
||||
* @property string|null $transcript_remote_url
|
||||
|
|
@ -59,17 +62,16 @@ use RuntimeException;
|
|||
* @property Chapters|null $chapters
|
||||
* @property string|null $chapters_remote_url
|
||||
* @property string|null $parental_advisory
|
||||
* @property int $number
|
||||
* @property int $season_number
|
||||
* @property ?int $number
|
||||
* @property ?int $season_number
|
||||
* @property string $type
|
||||
* @property bool $is_blocked
|
||||
* @property Location|null $location
|
||||
* @property string|null $location_name
|
||||
* @property string|null $location_geo
|
||||
* @property string|null $location_osm
|
||||
* @property array|null $custom_rss
|
||||
* @property string $custom_rss_string
|
||||
* @property bool $is_published_on_hubs
|
||||
* @property int $downloads_count
|
||||
* @property int $posts_count
|
||||
* @property int $comments_count
|
||||
* @property EpisodeComment[]|null $comments
|
||||
|
|
@ -87,19 +89,19 @@ use RuntimeException;
|
|||
*/
|
||||
class Episode extends Entity
|
||||
{
|
||||
protected Podcast $podcast;
|
||||
public string $link = '';
|
||||
|
||||
protected string $link;
|
||||
public string $audio_url = '';
|
||||
|
||||
public string $audio_web_url = '';
|
||||
|
||||
public string $audio_opengraph_url = '';
|
||||
|
||||
protected Podcast $podcast;
|
||||
|
||||
protected ?Audio $audio = null;
|
||||
|
||||
protected string $audio_analytics_url;
|
||||
|
||||
protected string $audio_web_url;
|
||||
|
||||
protected string $audio_opengraph_url;
|
||||
|
||||
protected string $embed_url;
|
||||
protected string $embed_url = '';
|
||||
|
||||
protected ?Image $cover = null;
|
||||
|
||||
|
|
@ -131,12 +133,11 @@ class Episode extends Entity
|
|||
|
||||
protected ?Location $location = null;
|
||||
|
||||
protected string $custom_rss_string;
|
||||
|
||||
protected ?string $publication_status = null;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
* @var array<int, string>
|
||||
* @phpstan-var list<string>
|
||||
*/
|
||||
protected $dates = ['published_at', 'created_at', 'updated_at'];
|
||||
|
||||
|
|
@ -144,39 +145,65 @@ class Episode extends Entity
|
|||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'podcast_id' => 'integer',
|
||||
'guid' => 'string',
|
||||
'slug' => 'string',
|
||||
'title' => 'string',
|
||||
'audio_id' => 'integer',
|
||||
'description_markdown' => 'string',
|
||||
'description_html' => 'string',
|
||||
'cover_id' => '?integer',
|
||||
'transcript_id' => '?integer',
|
||||
'id' => 'integer',
|
||||
'podcast_id' => 'integer',
|
||||
'preview_id' => '?string',
|
||||
'guid' => 'string',
|
||||
'slug' => 'string',
|
||||
'title' => 'string',
|
||||
'audio_id' => 'integer',
|
||||
'description_markdown' => 'string',
|
||||
'description_html' => 'string',
|
||||
'cover_id' => '?integer',
|
||||
'transcript_id' => '?integer',
|
||||
'transcript_remote_url' => '?string',
|
||||
'chapters_id' => '?integer',
|
||||
'chapters_remote_url' => '?string',
|
||||
'parental_advisory' => '?string',
|
||||
'number' => '?integer',
|
||||
'season_number' => '?integer',
|
||||
'type' => 'string',
|
||||
'is_blocked' => 'boolean',
|
||||
'location_name' => '?string',
|
||||
'location_geo' => '?string',
|
||||
'location_osm' => '?string',
|
||||
'custom_rss' => '?json-array',
|
||||
'is_published_on_hubs' => 'boolean',
|
||||
'posts_count' => 'integer',
|
||||
'comments_count' => 'integer',
|
||||
'is_premium' => 'boolean',
|
||||
'created_by' => 'integer',
|
||||
'updated_by' => 'integer',
|
||||
'chapters_id' => '?integer',
|
||||
'chapters_remote_url' => '?string',
|
||||
'parental_advisory' => '?string',
|
||||
'number' => '?integer',
|
||||
'season_number' => '?integer',
|
||||
'type' => 'string',
|
||||
'is_blocked' => 'boolean',
|
||||
'location_name' => '?string',
|
||||
'location_geo' => '?string',
|
||||
'location_osm' => '?string',
|
||||
'is_published_on_hubs' => 'boolean',
|
||||
'downloads_count' => 'integer',
|
||||
'posts_count' => 'integer',
|
||||
'comments_count' => 'integer',
|
||||
'is_premium' => 'boolean',
|
||||
'created_by' => 'integer',
|
||||
'updated_by' => 'integer',
|
||||
];
|
||||
|
||||
public function setCover(UploadedFile | File $file = null): self
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
#[Override]
|
||||
public function injectRawData(array $data): static
|
||||
{
|
||||
if ($file === null || ($file instanceof UploadedFile && ! $file->isValid())) {
|
||||
parent::injectRawData($data);
|
||||
|
||||
$this->link = url_to('episode', esc($this->getPodcast()->handle, 'url'), esc($this->attributes['slug'], 'url'));
|
||||
|
||||
$this->audio_url = url_to(
|
||||
'episode-audio',
|
||||
$this->getPodcast()
|
||||
->handle,
|
||||
$this->slug,
|
||||
$this->getAudio()
|
||||
->file_extension,
|
||||
);
|
||||
|
||||
$this->audio_opengraph_url = $this->audio_url . '?_from=-+Open+Graph+-';
|
||||
$this->audio_web_url = $this->audio_url . '?_from=-+Website+-';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCover(UploadedFile | File|null $file = null): self
|
||||
{
|
||||
if (! $file instanceof File || ($file instanceof UploadedFile && ! $file->isValid())) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -184,20 +211,20 @@ class Episode extends Entity
|
|||
$this->getCover()
|
||||
->setFile($file);
|
||||
$this->getCover()
|
||||
->updated_by = (int) user_id();
|
||||
(new MediaModel('image'))->updateMedia($this->getCover());
|
||||
->updated_by = $this->attributes['updated_by'];
|
||||
new MediaModel('image')
|
||||
->updateMedia($this->getCover());
|
||||
} else {
|
||||
$cover = new Image([
|
||||
'file_name' => $this->attributes['slug'],
|
||||
'file_directory' => 'podcasts/' . $this->getPodcast()->handle,
|
||||
'sizes' => config('Images')
|
||||
'file_key' => 'podcasts/' . $this->getPodcast()->handle . '/' . $this->attributes['slug'] . '.' . $file->getExtension(),
|
||||
'sizes' => config('Images')
|
||||
->podcastCoverSizes,
|
||||
'uploaded_by' => user_id(),
|
||||
'updated_by' => user_id(),
|
||||
'uploaded_by' => $this->attributes['updated_by'],
|
||||
'updated_by' => $this->attributes['updated_by'],
|
||||
]);
|
||||
$cover->setFile($file);
|
||||
|
||||
$this->attributes['cover_id'] = (new MediaModel('image'))->saveMedia($cover);
|
||||
$this->attributes['cover_id'] = new MediaModel('image')->saveMedia($cover);
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
|
@ -216,14 +243,15 @@ class Episode extends Entity
|
|||
return $this->cover;
|
||||
}
|
||||
|
||||
$this->cover = (new MediaModel('image'))->getMediaById($this->cover_id);
|
||||
$this->cover = new MediaModel('image')
|
||||
->getMediaById($this->cover_id);
|
||||
|
||||
return $this->cover;
|
||||
}
|
||||
|
||||
public function setAudio(UploadedFile | File $file = null): self
|
||||
public function setAudio(UploadedFile | File|null $file = null): self
|
||||
{
|
||||
if ($file === null || ($file instanceof UploadedFile && ! $file->isValid())) {
|
||||
if (! $file instanceof File || ($file instanceof UploadedFile && ! $file->isValid())) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -231,20 +259,20 @@ class Episode extends Entity
|
|||
$this->getAudio()
|
||||
->setFile($file);
|
||||
$this->getAudio()
|
||||
->updated_by = (int) user_id();
|
||||
(new MediaModel('audio'))->updateMedia($this->getAudio());
|
||||
->updated_by = $this->attributes['updated_by'];
|
||||
new MediaModel('audio')
|
||||
->updateMedia($this->getAudio());
|
||||
} else {
|
||||
$audio = new Audio([
|
||||
'file_name' => pathinfo($file->getRandomName(), PATHINFO_FILENAME),
|
||||
'file_directory' => 'podcasts/' . $this->getPodcast()->handle,
|
||||
'file_key' => 'podcasts/' . $this->getPodcast()->handle . '/' . $file->getRandomName(),
|
||||
'language_code' => $this->getPodcast()
|
||||
->language_code,
|
||||
'uploaded_by' => user_id(),
|
||||
'updated_by' => user_id(),
|
||||
'uploaded_by' => $this->attributes['updated_by'],
|
||||
'updated_by' => $this->attributes['updated_by'],
|
||||
]);
|
||||
$audio->setFile($file);
|
||||
|
||||
$this->attributes['audio_id'] = (new MediaModel())->saveMedia($audio);
|
||||
$this->attributes['audio_id'] = new MediaModel()->saveMedia($audio);
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
|
@ -253,36 +281,37 @@ class Episode extends Entity
|
|||
public function getAudio(): Audio
|
||||
{
|
||||
if (! $this->audio instanceof Audio) {
|
||||
$this->audio = (new MediaModel('audio'))->getMediaById($this->audio_id);
|
||||
$this->audio = new MediaModel('audio')
|
||||
->getMediaById($this->audio_id);
|
||||
}
|
||||
|
||||
return $this->audio;
|
||||
}
|
||||
|
||||
public function setTranscript(UploadedFile | File $file = null): self
|
||||
public function setTranscript(UploadedFile | File|null $file = null): self
|
||||
{
|
||||
if ($file === null || ($file instanceof UploadedFile && ! $file->isValid())) {
|
||||
if (! $file instanceof File || ($file instanceof UploadedFile && ! $file->isValid())) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($this->getTranscript() !== null) {
|
||||
if ($this->getTranscript() instanceof Transcript) {
|
||||
$this->getTranscript()
|
||||
->setFile($file);
|
||||
$this->getTranscript()
|
||||
->updated_by = (int) user_id();
|
||||
(new MediaModel('transcript'))->updateMedia($this->getTranscript());
|
||||
->updated_by = $this->attributes['updated_by'];
|
||||
new MediaModel('transcript')
|
||||
->updateMedia($this->getTranscript());
|
||||
} else {
|
||||
$transcript = new Transcript([
|
||||
'file_name' => $this->attributes['slug'] . '-transcript',
|
||||
'file_directory' => 'podcasts/' . $this->getPodcast()->handle,
|
||||
'file_key' => 'podcasts/' . $this->getPodcast()->handle . '/' . $this->attributes['slug'] . '-transcript.' . $file->getExtension(),
|
||||
'language_code' => $this->getPodcast()
|
||||
->language_code,
|
||||
'uploaded_by' => user_id(),
|
||||
'updated_by' => user_id(),
|
||||
'uploaded_by' => $this->attributes['updated_by'],
|
||||
'updated_by' => $this->attributes['updated_by'],
|
||||
]);
|
||||
$transcript->setFile($file);
|
||||
|
||||
$this->attributes['transcript_id'] = (new MediaModel('transcript'))->saveMedia($transcript);
|
||||
$this->attributes['transcript_id'] = new MediaModel('transcript')->saveMedia($transcript);
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
|
@ -290,37 +319,38 @@ class Episode extends Entity
|
|||
|
||||
public function getTranscript(): ?Transcript
|
||||
{
|
||||
if ($this->transcript_id !== null && $this->transcript === null) {
|
||||
$this->transcript = (new MediaModel('transcript'))->getMediaById($this->transcript_id);
|
||||
if ($this->transcript_id !== null && ! $this->transcript instanceof Transcript) {
|
||||
$this->transcript = new MediaModel('transcript')
|
||||
->getMediaById($this->transcript_id);
|
||||
}
|
||||
|
||||
return $this->transcript;
|
||||
}
|
||||
|
||||
public function setChapters(UploadedFile | File $file = null): self
|
||||
public function setChapters(UploadedFile | File|null $file = null): self
|
||||
{
|
||||
if ($file === null || ($file instanceof UploadedFile && ! $file->isValid())) {
|
||||
if (! $file instanceof File || ($file instanceof UploadedFile && ! $file->isValid())) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($this->getChapters() !== null) {
|
||||
if ($this->getChapters() instanceof Chapters) {
|
||||
$this->getChapters()
|
||||
->setFile($file);
|
||||
$this->getChapters()
|
||||
->updated_by = (int) user_id();
|
||||
(new MediaModel('chapters'))->updateMedia($this->getChapters());
|
||||
->updated_by = $this->attributes['updated_by'];
|
||||
new MediaModel('chapters')
|
||||
->updateMedia($this->getChapters());
|
||||
} else {
|
||||
$chapters = new Chapters([
|
||||
'file_name' => $this->attributes['slug'] . '-chapters',
|
||||
'file_directory' => 'podcasts/' . $this->getPodcast()->handle,
|
||||
'file_key' => 'podcasts/' . $this->getPodcast()->handle . '/' . $this->attributes['slug'] . '-chapters' . '.' . $file->getExtension(),
|
||||
'language_code' => $this->getPodcast()
|
||||
->language_code,
|
||||
'uploaded_by' => user_id(),
|
||||
'updated_by' => user_id(),
|
||||
'uploaded_by' => $this->attributes['updated_by'],
|
||||
'updated_by' => $this->attributes['updated_by'],
|
||||
]);
|
||||
$chapters->setFile($file);
|
||||
|
||||
$this->attributes['chapters_id'] = (new MediaModel('chapters'))->saveMedia($chapters);
|
||||
$this->attributes['chapters_id'] = new MediaModel('chapters')->saveMedia($chapters);
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
|
@ -328,51 +358,20 @@ class Episode extends Entity
|
|||
|
||||
public function getChapters(): ?Chapters
|
||||
{
|
||||
if ($this->chapters_id !== null && $this->chapters === null) {
|
||||
$this->chapters = (new MediaModel('chapters'))->getMediaById($this->chapters_id);
|
||||
if ($this->chapters_id !== null && ! $this->chapters instanceof Chapters) {
|
||||
$this->chapters = new MediaModel('chapters')
|
||||
->getMediaById($this->chapters_id);
|
||||
}
|
||||
|
||||
return $this->chapters;
|
||||
}
|
||||
|
||||
public function getAudioAnalyticsUrl(): string
|
||||
{
|
||||
helper('analytics');
|
||||
|
||||
return generate_episode_analytics_url(
|
||||
$this->podcast_id,
|
||||
$this->id,
|
||||
$this->getPodcast()
|
||||
->handle,
|
||||
$this->attributes['slug'],
|
||||
$this->getAudio()
|
||||
->file_extension,
|
||||
$this->getAudio()
|
||||
->duration,
|
||||
$this->getAudio()
|
||||
->file_size,
|
||||
$this->getAudio()
|
||||
->header_size,
|
||||
$this->published_at,
|
||||
);
|
||||
}
|
||||
|
||||
public function getAudioWebUrl(): string
|
||||
{
|
||||
return $this->getAudioAnalyticsUrl() . '?_from=-+Website+-';
|
||||
}
|
||||
|
||||
public function getAudioOpengraphUrl(): string
|
||||
{
|
||||
return $this->getAudioAnalyticsUrl() . '?_from=-+Open+Graph+-';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets transcript url from transcript file uri if it exists or returns the transcript_remote_url which can be null.
|
||||
*/
|
||||
public function getTranscriptUrl(): ?string
|
||||
{
|
||||
if ($this->transcript !== null) {
|
||||
if ($this->transcript instanceof Transcript) {
|
||||
return $this->transcript->file_url;
|
||||
}
|
||||
|
||||
|
|
@ -384,7 +383,7 @@ class Episode extends Entity
|
|||
*/
|
||||
public function getChaptersFileUrl(): ?string
|
||||
{
|
||||
if ($this->chapters !== null) {
|
||||
if ($this->chapters instanceof Chapters) {
|
||||
return $this->chapters->file_url;
|
||||
}
|
||||
|
||||
|
|
@ -398,12 +397,9 @@ class Episode extends Entity
|
|||
*/
|
||||
public function getPersons(): array
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Episode must be created before getting persons.');
|
||||
}
|
||||
|
||||
if ($this->persons === null) {
|
||||
$this->persons = (new PersonModel())->getEpisodePersons($this->podcast_id, $this->id);
|
||||
$this->persons = new PersonModel()
|
||||
->getEpisodePersons($this->podcast_id, $this->id);
|
||||
}
|
||||
|
||||
return $this->persons;
|
||||
|
|
@ -416,12 +412,9 @@ class Episode extends Entity
|
|||
*/
|
||||
public function getSoundbites(): array
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Episode must be created before getting soundbites.');
|
||||
}
|
||||
|
||||
if ($this->soundbites === null) {
|
||||
$this->soundbites = (new ClipModel())->getEpisodeSoundbites($this->getPodcast()->id, $this->id);
|
||||
$this->soundbites = new ClipModel()
|
||||
->getEpisodeSoundbites($this->getPodcast()->id, $this->id);
|
||||
}
|
||||
|
||||
return $this->soundbites;
|
||||
|
|
@ -432,12 +425,9 @@ class Episode extends Entity
|
|||
*/
|
||||
public function getPosts(): array
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Episode must be created before getting posts.');
|
||||
}
|
||||
|
||||
if ($this->posts === null) {
|
||||
$this->posts = (new PostModel())->getEpisodePosts($this->id);
|
||||
$this->posts = new PostModel()
|
||||
->getEpisodePosts($this->id);
|
||||
}
|
||||
|
||||
return $this->posts;
|
||||
|
|
@ -448,45 +438,38 @@ class Episode extends Entity
|
|||
*/
|
||||
public function getComments(): array
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Episode must be created before getting comments.');
|
||||
}
|
||||
|
||||
if ($this->comments === null) {
|
||||
$this->comments = (new EpisodeCommentModel())->getEpisodeComments($this->id);
|
||||
$this->comments = new EpisodeCommentModel()
|
||||
->getEpisodeComments($this->id);
|
||||
}
|
||||
|
||||
return $this->comments;
|
||||
}
|
||||
|
||||
public function getLink(): string
|
||||
{
|
||||
return url_to('episode', esc($this->getPodcast()->handle), esc($this->attributes['slug']));
|
||||
}
|
||||
|
||||
public function getEmbedUrl(string $theme = null): string
|
||||
public function getEmbedUrl(?string $theme = null): string
|
||||
{
|
||||
return $theme
|
||||
? url_to('embed-theme', esc($this->getPodcast()->handle), esc($this->attributes['slug']), $theme,)
|
||||
? url_to('embed-theme', esc($this->getPodcast()->handle), esc($this->attributes['slug']), $theme)
|
||||
: url_to('embed', esc($this->getPodcast()->handle), esc($this->attributes['slug']));
|
||||
}
|
||||
|
||||
public function setGuid(?string $guid = null): static
|
||||
{
|
||||
$this->attributes['guid'] = $guid === null ? $this->getLink() : $guid;
|
||||
$this->attributes['guid'] = $guid ?? $this->link;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPodcast(): ?Podcast
|
||||
{
|
||||
return (new PodcastModel())->getPodcastById($this->podcast_id);
|
||||
return new PodcastModel()
|
||||
->getPodcastById($this->podcast_id);
|
||||
}
|
||||
|
||||
public function setDescriptionMarkdown(string $descriptionMarkdown): static
|
||||
{
|
||||
$config = [
|
||||
'html_input' => 'escape',
|
||||
'html_input' => 'escape',
|
||||
'allow_unsafe_links' => false,
|
||||
];
|
||||
|
||||
|
|
@ -504,39 +487,11 @@ class Episode extends Entity
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getDescriptionHtml(?string $serviceSlug = null): string
|
||||
{
|
||||
$descriptionHtml = '';
|
||||
if (
|
||||
$this->getPodcast()
|
||||
->partner_id !== null &&
|
||||
$this->getPodcast()
|
||||
->partner_link_url !== null &&
|
||||
$this->getPodcast()
|
||||
->partner_image_url !== null
|
||||
) {
|
||||
$descriptionHtml .= "<div><a href=\"{$this->getPartnerLink(
|
||||
$serviceSlug,
|
||||
)}\" rel=\"sponsored noopener noreferrer\" target=\"_blank\"><img src=\"{$this->getPartnerImageUrl(
|
||||
$serviceSlug,
|
||||
)}\" alt=\"Partner image\" /></a></div>";
|
||||
}
|
||||
|
||||
$descriptionHtml .= $this->attributes['description_html'];
|
||||
|
||||
if ($this->getPodcast()->episode_description_footer_html) {
|
||||
$descriptionHtml .= "<footer>{$this->getPodcast()
|
||||
->episode_description_footer_html}</footer>";
|
||||
}
|
||||
|
||||
return $descriptionHtml;
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
if ($this->description === null) {
|
||||
$this->description = trim(
|
||||
preg_replace('~\s+~', ' ', strip_tags($this->attributes['description_html'])),
|
||||
(string) preg_replace('~\s+~', ' ', strip_tags((string) $this->attributes['description_html'])),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -546,7 +501,7 @@ class Episode extends Entity
|
|||
public function getPublicationStatus(): string
|
||||
{
|
||||
if ($this->publication_status === null) {
|
||||
if ($this->published_at === null) {
|
||||
if (! $this->published_at instanceof Time) {
|
||||
$this->publication_status = 'not_published';
|
||||
} elseif ($this->getPodcast()->publication_status !== 'published') {
|
||||
$this->publication_status = 'with_podcast';
|
||||
|
|
@ -565,7 +520,7 @@ class Episode extends Entity
|
|||
*/
|
||||
public function setLocation(?Location $location = null): static
|
||||
{
|
||||
if ($location === null) {
|
||||
if (! $location instanceof Location) {
|
||||
$this->attributes['location_name'] = null;
|
||||
$this->attributes['location_geo'] = null;
|
||||
$this->attributes['location_osm'] = null;
|
||||
|
|
@ -593,89 +548,33 @@ class Episode extends Entity
|
|||
return null;
|
||||
}
|
||||
|
||||
if ($this->location === null) {
|
||||
if (! $this->location instanceof Location) {
|
||||
$this->location = new Location($this->location_name, $this->location_geo, $this->location_osm);
|
||||
}
|
||||
|
||||
return $this->location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom rss tag as XML String
|
||||
*/
|
||||
public function getCustomRssString(): string
|
||||
public function getPreviewLink(): string
|
||||
{
|
||||
if ($this->custom_rss === null) {
|
||||
return '';
|
||||
if ($this->preview_id === null) {
|
||||
// generate preview id
|
||||
if (! $previewUUID = new EpisodeModel()->setEpisodePreviewId($this->id)) {
|
||||
throw new Exception('Could not set episode preview id');
|
||||
}
|
||||
|
||||
$this->preview_id = $previewUUID;
|
||||
}
|
||||
|
||||
helper('rss');
|
||||
|
||||
$xmlNode = (new SimpleRSSElement(
|
||||
'<?xml version="1.0" encoding="utf-8"?><rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:podcast="https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"/>',
|
||||
))
|
||||
->addChild('channel')
|
||||
->addChild('item');
|
||||
array_to_rss([
|
||||
'elements' => $this->custom_rss,
|
||||
], $xmlNode);
|
||||
|
||||
return (string) str_replace(['<item>', '</item>'], '', $xmlNode->asXML());
|
||||
return url_to('episode-preview', (string) $this->preview_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves custom rss tag into json
|
||||
* Returns the episode's clip count
|
||||
*/
|
||||
public function setCustomRssString(?string $customRssString = null): static
|
||||
public function getClipCount(): int|string
|
||||
{
|
||||
if ($customRssString === '') {
|
||||
$this->attributes['custom_rss'] = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
helper('rss');
|
||||
$customRssArray = rss_to_array(
|
||||
simplexml_load_string(
|
||||
'<?xml version="1.0" encoding="utf-8"?><rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:podcast="https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"><channel><item>' .
|
||||
$customRssString .
|
||||
'</item></channel></rss>',
|
||||
),
|
||||
)['elements'][0]['elements'][0];
|
||||
|
||||
if (array_key_exists('elements', $customRssArray)) {
|
||||
$this->attributes['custom_rss'] = json_encode($customRssArray['elements']);
|
||||
} else {
|
||||
$this->attributes['custom_rss'] = null;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPartnerLink(?string $serviceSlug = null): string
|
||||
{
|
||||
$partnerLink =
|
||||
rtrim($this->getPodcast()->partner_link_url, '/') .
|
||||
'?pid=' .
|
||||
$this->getPodcast()
|
||||
->partner_id .
|
||||
'&guid=' .
|
||||
urlencode($this->attributes['guid']);
|
||||
|
||||
if ($serviceSlug !== null) {
|
||||
$partnerLink .= '&_from=' . $serviceSlug;
|
||||
}
|
||||
|
||||
return $partnerLink;
|
||||
}
|
||||
|
||||
public function getPartnerImageUrl(string $serviceSlug = null): string
|
||||
{
|
||||
return rtrim($this->getPodcast()->partner_image_url, '/') .
|
||||
'?pid=' .
|
||||
$this->getPodcast()
|
||||
->partner_id .
|
||||
'&guid=' .
|
||||
urlencode($this->attributes['guid']) .
|
||||
($serviceSlug !== null ? '&_from=' . $serviceSlug : '');
|
||||
return new ClipModel()
|
||||
->getClipCount($this->podcast_id, $this->id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ use RuntimeException;
|
|||
* @property Episode|null $episode
|
||||
* @property int $actor_id
|
||||
* @property Actor|null $actor
|
||||
* @property string $in_reply_to_id
|
||||
* @property ?string $in_reply_to_id
|
||||
* @property EpisodeComment|null $reply_to_comment
|
||||
* @property string $message
|
||||
* @property string $message_html
|
||||
|
|
@ -51,7 +51,8 @@ class EpisodeComment extends UuidEntity
|
|||
protected bool $has_replies = false;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
* @var array<int, string>
|
||||
* @phpstan-var list<string>
|
||||
*/
|
||||
protected $dates = ['created_at'];
|
||||
|
||||
|
|
@ -59,27 +60,24 @@ class EpisodeComment extends UuidEntity
|
|||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'id' => 'string',
|
||||
'uri' => 'string',
|
||||
'episode_id' => 'integer',
|
||||
'actor_id' => 'integer',
|
||||
'id' => 'string',
|
||||
'uri' => 'string',
|
||||
'episode_id' => 'integer',
|
||||
'actor_id' => 'integer',
|
||||
'in_reply_to_id' => '?string',
|
||||
'message' => 'string',
|
||||
'message_html' => 'string',
|
||||
'likes_count' => 'integer',
|
||||
'replies_count' => 'integer',
|
||||
'created_by' => 'integer',
|
||||
'is_from_post' => 'boolean',
|
||||
'message' => 'string',
|
||||
'message_html' => 'string',
|
||||
'likes_count' => 'integer',
|
||||
'replies_count' => 'integer',
|
||||
'created_by' => 'integer',
|
||||
'is_from_post' => 'boolean',
|
||||
];
|
||||
|
||||
public function getEpisode(): ?Episode
|
||||
{
|
||||
if ($this->episode_id === null) {
|
||||
throw new RuntimeException('Comment must have an episode_id before getting episode.');
|
||||
}
|
||||
|
||||
if (! $this->episode instanceof Episode) {
|
||||
$this->episode = (new EpisodeModel())->getEpisodeById($this->episode_id);
|
||||
$this->episode = new EpisodeModel()
|
||||
->getEpisodeById($this->episode_id);
|
||||
}
|
||||
|
||||
return $this->episode;
|
||||
|
|
@ -87,15 +85,9 @@ class EpisodeComment extends UuidEntity
|
|||
|
||||
/**
|
||||
* Returns the comment's actor
|
||||
*
|
||||
* @noRector ReturnTypeDeclarationRector
|
||||
*/
|
||||
public function getActor(): ?Actor
|
||||
{
|
||||
if ($this->actor_id === null) {
|
||||
throw new RuntimeException('Comment must have an actor_id before getting actor.');
|
||||
}
|
||||
|
||||
if (! $this->actor instanceof Actor) {
|
||||
$this->actor = model(ActorModel::class, false)
|
||||
->getActorById($this->actor_id);
|
||||
|
|
@ -109,12 +101,9 @@ class EpisodeComment extends UuidEntity
|
|||
*/
|
||||
public function getReplies(): array
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Comment must be created before getting replies.');
|
||||
}
|
||||
|
||||
if ($this->replies === null) {
|
||||
$this->replies = (new EpisodeCommentModel())->getCommentReplies($this->id);
|
||||
$this->replies = new EpisodeCommentModel()
|
||||
->getCommentReplies($this->id);
|
||||
}
|
||||
|
||||
return $this->replies;
|
||||
|
|
@ -125,9 +114,6 @@ class EpisodeComment extends UuidEntity
|
|||
return $this->getReplies() !== [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @noRector ReturnTypeDeclarationRector
|
||||
*/
|
||||
public function getReplyToComment(): ?self
|
||||
{
|
||||
if ($this->in_reply_to_id === null) {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class Language extends Entity
|
|||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'code' => 'string',
|
||||
'code' => 'string',
|
||||
'native_name' => 'string',
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class Like extends UuidEntity
|
|||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'actor_id' => 'integer',
|
||||
'actor_id' => 'integer',
|
||||
'comment_id' => 'string',
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ declare(strict_types=1);
|
|||
namespace App\Entities;
|
||||
|
||||
use CodeIgniter\Entity\Entity;
|
||||
use Config\Services;
|
||||
|
||||
/**
|
||||
* @property string $url
|
||||
|
|
@ -23,15 +22,9 @@ use Config\Services;
|
|||
*/
|
||||
class Location extends Entity
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const OSM_URL = 'https://www.openstreetmap.org/';
|
||||
private const string OSM_URL = 'https://www.openstreetmap.org/';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const NOMINATIM_URL = 'https://nominatim.openstreetmap.org/';
|
||||
private const string NOMINATIM_URL = 'https://nominatim.openstreetmap.org/';
|
||||
|
||||
public function __construct(
|
||||
protected string $name,
|
||||
|
|
@ -42,15 +35,18 @@ class Location extends Entity
|
|||
$longitude = null;
|
||||
if ($geo !== null) {
|
||||
$geoArray = explode(',', substr($geo, 4));
|
||||
$latitude = (float) $geoArray[0];
|
||||
$longitude = (float) $geoArray[1];
|
||||
|
||||
if (count($geoArray) === 2) {
|
||||
$latitude = (float) $geoArray[0];
|
||||
$longitude = (float) $geoArray[1];
|
||||
}
|
||||
}
|
||||
|
||||
parent::__construct([
|
||||
'name' => $name,
|
||||
'geo' => $geo,
|
||||
'osm' => $osm,
|
||||
'latitude' => $latitude,
|
||||
'name' => $name,
|
||||
'geo' => $geo,
|
||||
'osm' => $osm,
|
||||
'latitude' => $latitude,
|
||||
'longitude' => $longitude,
|
||||
]);
|
||||
}
|
||||
|
|
@ -82,7 +78,7 @@ class Location extends Entity
|
|||
*/
|
||||
public function fetchOsmLocation(): static
|
||||
{
|
||||
$client = Services::curlrequest();
|
||||
$client = service('curlrequest');
|
||||
|
||||
$response = $client->request(
|
||||
'GET',
|
||||
|
|
@ -93,12 +89,12 @@ class Location extends Entity
|
|||
[
|
||||
'headers' => [
|
||||
'User-Agent' => 'Castopod/' . CP_VERSION,
|
||||
'Accept' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
$places = json_decode($response->getBody(), false, 512, JSON_THROW_ON_ERROR);
|
||||
$places = json_decode((string) $response->getBody(), false, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
if ($places === []) {
|
||||
return $this;
|
||||
|
|
@ -106,16 +102,16 @@ class Location extends Entity
|
|||
|
||||
if (property_exists($places[0], 'lat') && $places[0]->lat !== null && (property_exists(
|
||||
$places[0],
|
||||
'lon'
|
||||
'lon',
|
||||
) && $places[0]->lon !== null)) {
|
||||
$this->attributes['geo'] = "geo:{$places[0]->lat},{$places[0]->lon}";
|
||||
}
|
||||
|
||||
if (property_exists($places[0], 'osm_type') && $places[0]->osm_type !== null && (property_exists(
|
||||
$places[0],
|
||||
'osm_id'
|
||||
'osm_id',
|
||||
) && $places[0]->osm_id !== null)) {
|
||||
$this->attributes['osm'] = strtoupper(substr($places[0]->osm_type, 0, 1)) . $places[0]->osm_id;
|
||||
$this->attributes['osm'] = strtoupper(substr((string) $places[0]->osm_type, 0, 1)) . $places[0]->osm_id;
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
|
|
|||
|
|
@ -1,140 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2021 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Entities\Media;
|
||||
|
||||
use App\Models\MediaModel;
|
||||
use CodeIgniter\Database\BaseResult;
|
||||
use CodeIgniter\Entity\Entity;
|
||||
use CodeIgniter\Files\File;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $file_path
|
||||
* @property string $file_url
|
||||
* @property string $file_directory
|
||||
* @property string $file_extension
|
||||
* @property string $file_name
|
||||
* @property int $file_size
|
||||
* @property string $file_mimetype
|
||||
* @property array|null $file_metadata
|
||||
* @property 'image'|'audio'|'video'|'document' $type
|
||||
* @property string|null $description
|
||||
* @property string|null $language_code
|
||||
* @property int $uploaded_by
|
||||
* @property int $updated_by
|
||||
*/
|
||||
class BaseMedia extends Entity
|
||||
{
|
||||
protected File $file;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $dates = ['uploaded_at', 'updated_at'];
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'file_extension' => 'string',
|
||||
'file_path' => 'string',
|
||||
'file_size' => 'int',
|
||||
'file_mimetype' => 'string',
|
||||
'file_metadata' => '?json-array',
|
||||
'type' => 'string',
|
||||
'description' => '?string',
|
||||
'language_code' => '?string',
|
||||
'uploaded_by' => 'integer',
|
||||
'updated_by' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $data
|
||||
*/
|
||||
public function __construct(array $data = null)
|
||||
{
|
||||
parent::__construct($data);
|
||||
|
||||
$this->initFileProperties();
|
||||
}
|
||||
|
||||
public function initFileProperties(): void
|
||||
{
|
||||
if ($this->file_path !== '') {
|
||||
helper('media');
|
||||
[
|
||||
'filename' => $filename,
|
||||
'dirname' => $dirname,
|
||||
'extension' => $extension,
|
||||
] = pathinfo($this->file_path);
|
||||
|
||||
$this->attributes['file_url'] = media_base_url($this->file_path);
|
||||
$this->attributes['file_name'] = $filename;
|
||||
$this->attributes['file_directory'] = $dirname;
|
||||
$this->attributes['file_extension'] = $extension;
|
||||
}
|
||||
}
|
||||
|
||||
public function setFile(File $file): self
|
||||
{
|
||||
helper('media');
|
||||
|
||||
$this->attributes['type'] = $this->type;
|
||||
$this->attributes['file_mimetype'] = $file->getMimeType();
|
||||
$this->attributes['file_metadata'] = json_encode(lstat((string) $file), JSON_INVALID_UTF8_IGNORE);
|
||||
$this->attributes['file_path'] = save_media(
|
||||
$file,
|
||||
$this->attributes['file_directory'],
|
||||
$this->attributes['file_name']
|
||||
);
|
||||
if ($filesize = filesize(media_path($this->file_path))) {
|
||||
$this->attributes['file_size'] = $filesize;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function deleteFile(): bool
|
||||
{
|
||||
helper('media');
|
||||
return unlink(media_path($this->file_path));
|
||||
}
|
||||
|
||||
public function delete(): bool|BaseResult
|
||||
{
|
||||
$mediaModel = new MediaModel();
|
||||
return $mediaModel->delete($this->id);
|
||||
}
|
||||
|
||||
public function rename(): bool
|
||||
{
|
||||
$newFilePath = $this->file_directory . '/' . (new File(''))->getRandomName() . '.' . $this->file_extension;
|
||||
|
||||
$db = db_connect();
|
||||
$db->transStart();
|
||||
|
||||
if (! (new MediaModel())->update($this->id, [
|
||||
'file_path' => $newFilePath,
|
||||
])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! rename(media_path($this->file_path), media_path($newFilePath))) {
|
||||
$db->transRollback();
|
||||
return false;
|
||||
}
|
||||
|
||||
$db->transComplete();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2021 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Entities\Media;
|
||||
|
||||
use CodeIgniter\Files\File;
|
||||
|
||||
/**
|
||||
* @property array $sizes
|
||||
*/
|
||||
class Image extends BaseMedia
|
||||
{
|
||||
protected string $type = 'image';
|
||||
|
||||
public function initFileProperties(): void
|
||||
{
|
||||
parent::initFileProperties();
|
||||
|
||||
if ($this->file_path && $this->file_metadata) {
|
||||
$this->sizes = $this->file_metadata['sizes'];
|
||||
$this->initSizeProperties();
|
||||
}
|
||||
}
|
||||
|
||||
public function initSizeProperties(): bool
|
||||
{
|
||||
helper('media');
|
||||
|
||||
foreach ($this->sizes as $name => $size) {
|
||||
$extension = array_key_exists('extension', $size) ? $size['extension'] : $this->file_extension;
|
||||
$mimetype = array_key_exists('mimetype', $size) ? $size['mimetype'] : $this->file_mimetype;
|
||||
$this->{$name . '_path'} = $this->file_directory . '/' . $this->file_name . '_' . $name . '.' . $extension;
|
||||
$this->{$name . '_url'} = media_base_url($this->{$name . '_path'});
|
||||
$this->{$name . '_mimetype'} = $mimetype;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setFile(File $file): self
|
||||
{
|
||||
parent::setFile($file);
|
||||
|
||||
if ($this->file_mimetype === 'image/jpeg' && $metadata = @exif_read_data(
|
||||
media_path($this->file_path),
|
||||
null,
|
||||
true
|
||||
)) {
|
||||
$metadata['sizes'] = $this->sizes;
|
||||
$this->attributes['file_size'] = $metadata['FILE']['FileSize'];
|
||||
} else {
|
||||
$metadata = [
|
||||
'sizes' => $this->sizes,
|
||||
];
|
||||
}
|
||||
|
||||
$this->attributes['file_metadata'] = json_encode($metadata, JSON_INVALID_UTF8_IGNORE);
|
||||
|
||||
$this->initFileProperties();
|
||||
$this->saveSizes();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function deleteFile(): bool
|
||||
{
|
||||
if (parent::deleteFile()) {
|
||||
return $this->deleteSizes();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function saveSizes(): void
|
||||
{
|
||||
// save derived sizes
|
||||
$imageService = service('image');
|
||||
foreach ($this->sizes as $name => $size) {
|
||||
$pathProperty = $name . '_path';
|
||||
$imageService
|
||||
->withFile(media_path($this->file_path))
|
||||
->resize($size['width'], $size['height']);
|
||||
$imageService->save(media_path($this->{$pathProperty}));
|
||||
}
|
||||
}
|
||||
|
||||
private function deleteSizes(): bool
|
||||
{
|
||||
// delete all derived sizes
|
||||
foreach (array_keys($this->sizes) as $name) {
|
||||
$pathProperty = $name . '_path';
|
||||
if (! unlink(media_path($this->{$pathProperty}))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2021 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Entities\Media;
|
||||
|
||||
use App\Libraries\TranscriptParser;
|
||||
use CodeIgniter\Files\File;
|
||||
|
||||
class Transcript extends BaseMedia
|
||||
{
|
||||
public ?string $json_path = null;
|
||||
|
||||
public ?string $json_url = null;
|
||||
|
||||
protected string $type = 'transcript';
|
||||
|
||||
public function initFileProperties(): void
|
||||
{
|
||||
parent::initFileProperties();
|
||||
|
||||
if ($this->file_path && $this->file_metadata && array_key_exists('json_path', $this->file_metadata)) {
|
||||
helper('media');
|
||||
|
||||
$this->json_path = media_path($this->file_metadata['json_path']);
|
||||
$this->json_url = media_base_url($this->file_metadata['json_path']);
|
||||
}
|
||||
}
|
||||
|
||||
public function setFile(File $file): self
|
||||
{
|
||||
parent::setFile($file);
|
||||
|
||||
$content = file_get_contents(media_path($this->attributes['file_path']));
|
||||
|
||||
if ($content === false) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$metadata = [];
|
||||
if ($fileMetadata = lstat((string) $file)) {
|
||||
$metadata = $fileMetadata;
|
||||
}
|
||||
|
||||
$transcriptParser = new TranscriptParser();
|
||||
$jsonFilePath = $this->attributes['file_directory'] . '/' . $this->attributes['file_name'] . '.json';
|
||||
if (($transcriptJson = $transcriptParser->loadString($content)->parseSrt()) && file_put_contents(
|
||||
media_path($jsonFilePath),
|
||||
$transcriptJson
|
||||
)) {
|
||||
// set metadata (generated json file path)
|
||||
$metadata['json_path'] = $jsonFilePath;
|
||||
}
|
||||
|
||||
$this->attributes['file_metadata'] = json_encode($metadata, JSON_INVALID_UTF8_IGNORE);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function deleteFile(): bool
|
||||
{
|
||||
if (! parent::deleteFile()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->json_path) {
|
||||
return unlink($this->json_path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -40,11 +40,11 @@ class Page extends Entity
|
|||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'title' => 'string',
|
||||
'slug' => 'string',
|
||||
'id' => 'integer',
|
||||
'title' => 'string',
|
||||
'slug' => 'string',
|
||||
'content_markdown' => 'string',
|
||||
'content_html' => 'string',
|
||||
'content_html' => 'string',
|
||||
];
|
||||
|
||||
public function getLink(): string
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entities;
|
||||
|
||||
use App\Entities\Media\Image;
|
||||
use App\Models\MediaModel;
|
||||
use App\Models\PersonModel;
|
||||
use CodeIgniter\Entity\Entity;
|
||||
use CodeIgniter\Files\File;
|
||||
use CodeIgniter\HTTP\Files\UploadedFile;
|
||||
use Modules\Media\Entities\Image;
|
||||
use Modules\Media\Models\MediaModel;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
|
|
@ -23,8 +23,8 @@ use RuntimeException;
|
|||
* @property string $full_name
|
||||
* @property string $unique_name
|
||||
* @property string|null $information_url
|
||||
* @property int $avatar_id
|
||||
* @property Image $avatar
|
||||
* @property ?int $avatar_id
|
||||
* @property ?Image $avatar
|
||||
* @property int $created_by
|
||||
* @property int $updated_by
|
||||
* @property object[]|null $roles
|
||||
|
|
@ -42,23 +42,23 @@ class Person extends Entity
|
|||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'full_name' => 'string',
|
||||
'unique_name' => 'string',
|
||||
'id' => 'integer',
|
||||
'full_name' => 'string',
|
||||
'unique_name' => 'string',
|
||||
'information_url' => '?string',
|
||||
'avatar_id' => '?int',
|
||||
'podcast_id' => '?integer',
|
||||
'episode_id' => '?integer',
|
||||
'created_by' => 'integer',
|
||||
'updated_by' => 'integer',
|
||||
'avatar_id' => '?int',
|
||||
'podcast_id' => '?integer',
|
||||
'episode_id' => '?integer',
|
||||
'created_by' => 'integer',
|
||||
'updated_by' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* Saves the person avatar in `public/media/persons/`
|
||||
*/
|
||||
public function setAvatar(UploadedFile | File $file = null): static
|
||||
public function setAvatar(UploadedFile | File|null $file = null): static
|
||||
{
|
||||
if ($file === null || ($file instanceof UploadedFile && ! $file->isValid())) {
|
||||
if (! $file instanceof File || ($file instanceof UploadedFile && ! $file->isValid())) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -66,44 +66,34 @@ class Person extends Entity
|
|||
$this->getAvatar()
|
||||
->setFile($file);
|
||||
$this->getAvatar()
|
||||
->updated_by = (int) user_id();
|
||||
(new MediaModel('image'))->updateMedia($this->getAvatar());
|
||||
->updated_by = $this->attributes['updated_by'];
|
||||
new MediaModel('image')
|
||||
->updateMedia($this->getAvatar());
|
||||
} else {
|
||||
$avatar = new Image([
|
||||
'file_name' => $this->attributes['unique_name'],
|
||||
'file_directory' => 'persons',
|
||||
'sizes' => config('Images')
|
||||
'file_key' => 'persons/' . $this->attributes['unique_name'] . '.' . $file->getExtension(),
|
||||
'sizes' => config('Images')
|
||||
->personAvatarSizes,
|
||||
'uploaded_by' => user_id(),
|
||||
'updated_by' => user_id(),
|
||||
'uploaded_by' => $this->attributes['updated_by'],
|
||||
'updated_by' => $this->attributes['updated_by'],
|
||||
]);
|
||||
$avatar->setFile($file);
|
||||
|
||||
$this->attributes['avatar_id'] = (new MediaModel('image'))->saveMedia($avatar);
|
||||
$this->attributes['avatar_id'] = new MediaModel('image')->saveMedia($avatar);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAvatar(): Image
|
||||
public function getAvatar(): ?Image
|
||||
{
|
||||
if ($this->attributes['avatar_id'] === null) {
|
||||
helper('media');
|
||||
return new Image([
|
||||
'file_path' => config('Images')
|
||||
->avatarDefaultPath,
|
||||
'file_mimetype' => config('Images')
|
||||
->avatarDefaultMimeType,
|
||||
'file_size' => 0,
|
||||
'file_metadata' => [
|
||||
'sizes' => config('Images')
|
||||
->personAvatarSizes,
|
||||
],
|
||||
]);
|
||||
if ($this->avatar_id === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->avatar === null) {
|
||||
$this->avatar = (new MediaModel('image'))->getMediaById($this->avatar_id);
|
||||
if (! $this->avatar instanceof Image) {
|
||||
$this->avatar = new MediaModel('image')
|
||||
->getMediaById($this->avatar_id);
|
||||
}
|
||||
|
||||
return $this->avatar;
|
||||
|
|
@ -119,11 +109,12 @@ class Person extends Entity
|
|||
}
|
||||
|
||||
if ($this->roles === null) {
|
||||
$this->roles = (new PersonModel())->getPersonRoles(
|
||||
$this->id,
|
||||
(int) $this->attributes['podcast_id'],
|
||||
array_key_exists('episode_id', $this->attributes) ? (int) $this->attributes['episode_id'] : null
|
||||
);
|
||||
$this->roles = new PersonModel()
|
||||
->getPersonRoles(
|
||||
$this->id,
|
||||
(int) $this->attributes['podcast_id'],
|
||||
array_key_exists('episode_id', $this->attributes) ? (int) $this->attributes['episode_id'] : null,
|
||||
);
|
||||
}
|
||||
|
||||
return $this->roles;
|
||||
|
|
|
|||
|
|
@ -10,26 +10,27 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entities;
|
||||
|
||||
use App\Entities\Media\Image;
|
||||
use App\Libraries\SimpleRSSElement;
|
||||
use App\Models\ActorModel;
|
||||
use App\Models\CategoryModel;
|
||||
use App\Models\EpisodeModel;
|
||||
use App\Models\MediaModel;
|
||||
use App\Models\PersonModel;
|
||||
use App\Models\PlatformModel;
|
||||
use App\Models\UserModel;
|
||||
use CodeIgniter\Entity\Entity;
|
||||
use CodeIgniter\Files\File;
|
||||
use CodeIgniter\HTTP\Files\UploadedFile;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use CodeIgniter\Shield\Entities\User;
|
||||
use Exception;
|
||||
use League\CommonMark\Environment\Environment;
|
||||
use League\CommonMark\Extension\Autolink\AutolinkExtension;
|
||||
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
|
||||
use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
|
||||
use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
|
||||
use League\CommonMark\MarkdownConverter;
|
||||
use Modules\Auth\Entities\User;
|
||||
use Modules\Auth\Models\UserModel;
|
||||
use Modules\Media\Entities\Image;
|
||||
use Modules\Media\Models\MediaModel;
|
||||
use Modules\Platforms\Entities\Platform;
|
||||
use Modules\Platforms\Models\PlatformModel;
|
||||
use Modules\PremiumPodcasts\Entities\Subscription;
|
||||
use Modules\PremiumPodcasts\Models\SubscriptionModel;
|
||||
use RuntimeException;
|
||||
|
|
@ -40,6 +41,7 @@ use RuntimeException;
|
|||
* @property int $actor_id
|
||||
* @property Actor|null $actor
|
||||
* @property string $handle
|
||||
* @property string $at_handle
|
||||
* @property string $link
|
||||
* @property string $feed_url
|
||||
* @property string $title
|
||||
|
|
@ -47,9 +49,9 @@ use RuntimeException;
|
|||
* @property string $description_markdown
|
||||
* @property string $description_html
|
||||
* @property int $cover_id
|
||||
* @property Image $cover
|
||||
* @property ?Image $cover
|
||||
* @property int|null $banner_id
|
||||
* @property Image|null $banner
|
||||
* @property ?Image $banner
|
||||
* @property string $language_code
|
||||
* @property int $category_id
|
||||
* @property Category|null $category
|
||||
|
|
@ -61,8 +63,6 @@ use RuntimeException;
|
|||
* @property string $owner_email
|
||||
* @property string $type
|
||||
* @property string|null $copyright
|
||||
* @property string|null $episode_description_footer_markdown
|
||||
* @property string|null $episode_description_footer_html
|
||||
* @property bool $is_blocked
|
||||
* @property bool $is_completed
|
||||
* @property bool $is_locked
|
||||
|
|
@ -72,13 +72,7 @@ use RuntimeException;
|
|||
* @property string|null $location_name
|
||||
* @property string|null $location_geo
|
||||
* @property string|null $location_osm
|
||||
* @property string|null $payment_pointer
|
||||
* @property array|null $custom_rss
|
||||
* @property string $custom_rss_string
|
||||
* @property bool $is_published_on_hubs
|
||||
* @property string|null $partner_id
|
||||
* @property string|null $partner_link_url
|
||||
* @property string|null $partner_image_url
|
||||
* @property int $created_by
|
||||
* @property int $updated_by
|
||||
* @property string $publication_status
|
||||
|
|
@ -100,6 +94,8 @@ class Podcast extends Entity
|
|||
{
|
||||
protected string $link;
|
||||
|
||||
protected string $at_handle;
|
||||
|
||||
protected ?Actor $actor = null;
|
||||
|
||||
protected ?Image $cover = null;
|
||||
|
|
@ -116,9 +112,9 @@ class Podcast extends Entity
|
|||
protected ?array $other_categories = null;
|
||||
|
||||
/**
|
||||
* @var string[]|null
|
||||
* @var int[]
|
||||
*/
|
||||
protected ?array $other_categories_ids = null;
|
||||
protected array $other_categories_ids = [];
|
||||
|
||||
/**
|
||||
* @var Episode[]|null
|
||||
|
|
@ -157,12 +153,11 @@ class Podcast extends Entity
|
|||
|
||||
protected ?Location $location = null;
|
||||
|
||||
protected string $custom_rss_string;
|
||||
|
||||
protected ?string $publication_status = null;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
* @var array<int, string>
|
||||
* @phpstan-var list<string>
|
||||
*/
|
||||
protected $dates = ['published_at', 'created_at', 'updated_at'];
|
||||
|
||||
|
|
@ -170,47 +165,42 @@ class Podcast extends Entity
|
|||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'guid' => 'string',
|
||||
'actor_id' => 'integer',
|
||||
'handle' => 'string',
|
||||
'title' => 'string',
|
||||
'description_markdown' => 'string',
|
||||
'description_html' => 'string',
|
||||
'cover_id' => 'int',
|
||||
'banner_id' => '?int',
|
||||
'language_code' => 'string',
|
||||
'category_id' => 'integer',
|
||||
'parental_advisory' => '?string',
|
||||
'publisher' => '?string',
|
||||
'owner_name' => 'string',
|
||||
'owner_email' => 'string',
|
||||
'type' => 'string',
|
||||
'copyright' => '?string',
|
||||
'episode_description_footer_markdown' => '?string',
|
||||
'episode_description_footer_html' => '?string',
|
||||
'is_blocked' => 'boolean',
|
||||
'is_completed' => 'boolean',
|
||||
'is_locked' => 'boolean',
|
||||
'id' => 'integer',
|
||||
'guid' => 'string',
|
||||
'actor_id' => 'integer',
|
||||
'handle' => 'string',
|
||||
'title' => 'string',
|
||||
'description_markdown' => 'string',
|
||||
'description_html' => 'string',
|
||||
'cover_id' => 'int',
|
||||
'banner_id' => '?int',
|
||||
'language_code' => 'string',
|
||||
'category_id' => 'integer',
|
||||
'parental_advisory' => '?string',
|
||||
'publisher' => '?string',
|
||||
'owner_name' => 'string',
|
||||
'owner_email' => 'string',
|
||||
'type' => 'string',
|
||||
'copyright' => '?string',
|
||||
'is_blocked' => 'boolean',
|
||||
'is_completed' => 'boolean',
|
||||
'is_locked' => 'boolean',
|
||||
'is_premium_by_default' => 'boolean',
|
||||
'imported_feed_url' => '?string',
|
||||
'new_feed_url' => '?string',
|
||||
'location_name' => '?string',
|
||||
'location_geo' => '?string',
|
||||
'location_osm' => '?string',
|
||||
'payment_pointer' => '?string',
|
||||
'custom_rss' => '?json-array',
|
||||
'is_published_on_hubs' => 'boolean',
|
||||
'partner_id' => '?string',
|
||||
'partner_link_url' => '?string',
|
||||
'partner_image_url' => '?string',
|
||||
'created_by' => 'integer',
|
||||
'updated_by' => 'integer',
|
||||
'imported_feed_url' => '?string',
|
||||
'new_feed_url' => '?string',
|
||||
'location_name' => '?string',
|
||||
'location_geo' => '?string',
|
||||
'location_osm' => '?string',
|
||||
'is_published_on_hubs' => 'boolean',
|
||||
'created_by' => 'integer',
|
||||
'updated_by' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* @noRector ReturnTypeDeclarationRector
|
||||
*/
|
||||
public function getAtHandle(): string
|
||||
{
|
||||
return '@' . $this->handle;
|
||||
}
|
||||
|
||||
public function getActor(): ?Actor
|
||||
{
|
||||
if ($this->actor_id === 0) {
|
||||
|
|
@ -225,9 +215,9 @@ class Podcast extends Entity
|
|||
return $this->actor;
|
||||
}
|
||||
|
||||
public function setCover(UploadedFile | File $file = null): self
|
||||
public function setCover(UploadedFile | File|null $file = null): self
|
||||
{
|
||||
if ($file === null || ($file instanceof UploadedFile && ! $file->isValid())) {
|
||||
if (! $file instanceof File || ($file instanceof UploadedFile && ! $file->isValid())) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -235,20 +225,20 @@ class Podcast extends Entity
|
|||
$this->getCover()
|
||||
->setFile($file);
|
||||
$this->getCover()
|
||||
->updated_by = (int) user_id();
|
||||
(new MediaModel('image'))->updateMedia($this->getCover());
|
||||
->updated_by = $this->attributes['updated_by'];
|
||||
new MediaModel('image')
|
||||
->updateMedia($this->getCover());
|
||||
} else {
|
||||
$cover = new Image([
|
||||
'file_name' => 'cover',
|
||||
'file_directory' => 'podcasts/' . $this->attributes['handle'],
|
||||
'sizes' => config('Images')
|
||||
'file_key' => 'podcasts/' . $this->attributes['handle'] . '/cover.' . $file->getExtension(),
|
||||
'sizes' => config('Images')
|
||||
->podcastCoverSizes,
|
||||
'uploaded_by' => user_id(),
|
||||
'updated_by' => user_id(),
|
||||
'uploaded_by' => $this->attributes['updated_by'],
|
||||
'updated_by' => $this->attributes['updated_by'],
|
||||
]);
|
||||
$cover->setFile($file);
|
||||
|
||||
$this->attributes['cover_id'] = (new MediaModel('image'))->saveMedia($cover);
|
||||
$this->attributes['cover_id'] = new MediaModel('image')->saveMedia($cover);
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
|
@ -257,15 +247,22 @@ class Podcast extends Entity
|
|||
public function getCover(): Image
|
||||
{
|
||||
if (! $this->cover instanceof Image) {
|
||||
$this->cover = (new MediaModel('image'))->getMediaById($this->cover_id);
|
||||
$cover = new MediaModel('image')
|
||||
->getMediaById($this->cover_id);
|
||||
|
||||
if (! $cover instanceof Image) {
|
||||
throw new Exception('Could not retrieve podcast cover.');
|
||||
}
|
||||
|
||||
$this->cover = $cover;
|
||||
}
|
||||
|
||||
return $this->cover;
|
||||
}
|
||||
|
||||
public function setBanner(UploadedFile | File $file = null): self
|
||||
public function setBanner(UploadedFile | File|null $file = null): self
|
||||
{
|
||||
if ($file === null || ($file instanceof UploadedFile && ! $file->isValid())) {
|
||||
if (! $file instanceof File || ($file instanceof UploadedFile && ! $file->isValid())) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -273,45 +270,34 @@ class Podcast extends Entity
|
|||
$this->getBanner()
|
||||
->setFile($file);
|
||||
$this->getBanner()
|
||||
->updated_by = (int) user_id();
|
||||
(new MediaModel('image'))->updateMedia($this->getBanner());
|
||||
->updated_by = $this->attributes['updated_by'];
|
||||
new MediaModel('image')
|
||||
->updateMedia($this->getBanner());
|
||||
} else {
|
||||
$banner = new Image([
|
||||
'file_name' => 'banner',
|
||||
'file_directory' => 'podcasts/' . $this->attributes['handle'],
|
||||
'sizes' => config('Images')
|
||||
'file_key' => 'podcasts/' . $this->attributes['handle'] . '/banner.' . $file->getExtension(),
|
||||
'sizes' => config('Images')
|
||||
->podcastBannerSizes,
|
||||
'uploaded_by' => user_id(),
|
||||
'updated_by' => user_id(),
|
||||
'uploaded_by' => $this->attributes['updated_by'],
|
||||
'updated_by' => $this->attributes['updated_by'],
|
||||
]);
|
||||
$banner->setFile($file);
|
||||
|
||||
$this->attributes['banner_id'] = (new MediaModel('image'))->saveMedia($banner);
|
||||
$this->attributes['banner_id'] = new MediaModel('image')->saveMedia($banner);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBanner(): Image
|
||||
public function getBanner(): ?Image
|
||||
{
|
||||
if ($this->banner_id === null) {
|
||||
$defaultBanner = config('Images')
|
||||
->podcastBannerDefaultPaths[service('settings')->get('App.theme')] ?? config(
|
||||
'Images'
|
||||
)->podcastBannerDefaultPaths['default'];
|
||||
return new Image([
|
||||
'file_path' => $defaultBanner['path'],
|
||||
'file_mimetype' => $defaultBanner['mimetype'],
|
||||
'file_size' => 0,
|
||||
'file_metadata' => [
|
||||
'sizes' => config('Images')
|
||||
->podcastBannerSizes,
|
||||
],
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->banner instanceof Image) {
|
||||
$this->banner = (new MediaModel('image'))->getMediaById($this->banner_id);
|
||||
$this->banner = new MediaModel('image')
|
||||
->getMediaById($this->banner_id);
|
||||
}
|
||||
|
||||
return $this->banner;
|
||||
|
|
@ -334,12 +320,9 @@ class Podcast extends Entity
|
|||
*/
|
||||
public function getEpisodes(): array
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Podcast must be created before getting episodes.');
|
||||
}
|
||||
|
||||
if ($this->episodes === null) {
|
||||
$this->episodes = (new EpisodeModel())->getPodcastEpisodes($this->id, $this->type);
|
||||
$this->episodes = new EpisodeModel()
|
||||
->getPodcastEpisodes($this->id, $this->type);
|
||||
}
|
||||
|
||||
return $this->episodes;
|
||||
|
|
@ -350,11 +333,8 @@ class Podcast extends Entity
|
|||
*/
|
||||
public function getEpisodesCount(): int|string
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Podcast must be created before getting number of episodes.');
|
||||
}
|
||||
|
||||
return (new EpisodeModel())->getPodcastEpisodesCount($this->id);
|
||||
return new EpisodeModel()
|
||||
->getPodcastEpisodesCount($this->id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -364,12 +344,9 @@ class Podcast extends Entity
|
|||
*/
|
||||
public function getPersons(): array
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Podcast must be created before getting persons.');
|
||||
}
|
||||
|
||||
if ($this->persons === null) {
|
||||
$this->persons = (new PersonModel())->getPodcastPersons($this->id);
|
||||
$this->persons = new PersonModel()
|
||||
->getPodcastPersons($this->id);
|
||||
}
|
||||
|
||||
return $this->persons;
|
||||
|
|
@ -380,12 +357,9 @@ class Podcast extends Entity
|
|||
*/
|
||||
public function getCategory(): ?Category
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Podcast must be created before getting category.');
|
||||
}
|
||||
|
||||
if (! $this->category instanceof Category) {
|
||||
$this->category = (new CategoryModel())->getCategoryById($this->category_id);
|
||||
$this->category = new CategoryModel()
|
||||
->getCategoryById($this->category_id);
|
||||
}
|
||||
|
||||
return $this->category;
|
||||
|
|
@ -398,12 +372,9 @@ class Podcast extends Entity
|
|||
*/
|
||||
public function getSubscriptions(): array
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Podcasts must be created before getting subscriptions.');
|
||||
}
|
||||
|
||||
if ($this->subscriptions === null) {
|
||||
$this->subscriptions = (new SubscriptionModel())->getPodcastSubscriptions($this->id);
|
||||
$this->subscriptions = new SubscriptionModel()
|
||||
->getPodcastSubscriptions($this->id);
|
||||
}
|
||||
|
||||
return $this->subscriptions;
|
||||
|
|
@ -416,12 +387,9 @@ class Podcast extends Entity
|
|||
*/
|
||||
public function getContributors(): array
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Podcasts must be created before getting contributors.');
|
||||
}
|
||||
|
||||
if ($this->contributors === null) {
|
||||
$this->contributors = (new UserModel())->getPodcastContributors($this->id);
|
||||
$this->contributors = new UserModel()
|
||||
->getPodcastContributors($this->id);
|
||||
}
|
||||
|
||||
return $this->contributors;
|
||||
|
|
@ -430,7 +398,7 @@ class Podcast extends Entity
|
|||
public function setDescriptionMarkdown(string $descriptionMarkdown): static
|
||||
{
|
||||
$config = [
|
||||
'html_input' => 'escape',
|
||||
'html_input' => 'escape',
|
||||
'allow_unsafe_links' => false,
|
||||
];
|
||||
|
||||
|
|
@ -448,47 +416,11 @@ class Podcast extends Entity
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setEpisodeDescriptionFooterMarkdown(?string $episodeDescriptionFooterMarkdown = null): static
|
||||
{
|
||||
if ($episodeDescriptionFooterMarkdown === null || $episodeDescriptionFooterMarkdown === '') {
|
||||
$this->attributes[
|
||||
'episode_description_footer_markdown'
|
||||
] = null;
|
||||
$this->attributes[
|
||||
'episode_description_footer_html'
|
||||
] = null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$config = [
|
||||
'html_input' => 'escape',
|
||||
'allow_unsafe_links' => false,
|
||||
];
|
||||
|
||||
$environment = new Environment($config);
|
||||
$environment->addExtension(new CommonMarkCoreExtension());
|
||||
$environment->addExtension(new AutolinkExtension());
|
||||
$environment->addExtension(new SmartPunctExtension());
|
||||
$environment->addExtension(new DisallowedRawHtmlExtension());
|
||||
|
||||
$converter = new MarkdownConverter($environment);
|
||||
|
||||
$this->attributes[
|
||||
'episode_description_footer_markdown'
|
||||
] = $episodeDescriptionFooterMarkdown;
|
||||
$this->attributes[
|
||||
'episode_description_footer_html'
|
||||
] = $converter->convert($episodeDescriptionFooterMarkdown);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
if ($this->description === null) {
|
||||
$this->description = trim(
|
||||
(string) preg_replace('~\s+~', ' ', strip_tags($this->attributes['description_html'])),
|
||||
(string) preg_replace('~\s+~', ' ', strip_tags((string) $this->attributes['description_html'])),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -498,7 +430,7 @@ class Podcast extends Entity
|
|||
public function getPublicationStatus(): string
|
||||
{
|
||||
if ($this->publication_status === null) {
|
||||
if ($this->published_at === null) {
|
||||
if (! $this->published_at instanceof Time) {
|
||||
$this->publication_status = 'not_published';
|
||||
} elseif ($this->published_at->isBefore(Time::now())) {
|
||||
$this->publication_status = 'published';
|
||||
|
|
@ -517,12 +449,9 @@ class Podcast extends Entity
|
|||
*/
|
||||
public function getPodcastingPlatforms(): array
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Podcast must be created before getting podcasting platform links.');
|
||||
}
|
||||
|
||||
if ($this->podcasting_platforms === null) {
|
||||
$this->podcasting_platforms = (new PlatformModel())->getPodcastPlatforms($this->id, 'podcasting');
|
||||
$this->podcasting_platforms = new PlatformModel()
|
||||
->getPlatforms($this->id, 'podcasting');
|
||||
}
|
||||
|
||||
return $this->podcasting_platforms;
|
||||
|
|
@ -535,12 +464,9 @@ class Podcast extends Entity
|
|||
*/
|
||||
public function getSocialPlatforms(): array
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Podcast must be created before getting social platform links.');
|
||||
}
|
||||
|
||||
if ($this->social_platforms === null) {
|
||||
$this->social_platforms = (new PlatformModel())->getPodcastPlatforms($this->id, 'social');
|
||||
$this->social_platforms = new PlatformModel()
|
||||
->getPlatforms($this->id, 'social');
|
||||
}
|
||||
|
||||
return $this->social_platforms;
|
||||
|
|
@ -553,12 +479,9 @@ class Podcast extends Entity
|
|||
*/
|
||||
public function getFundingPlatforms(): array
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Podcast must be created before getting funding platform links.');
|
||||
}
|
||||
|
||||
if ($this->funding_platforms === null) {
|
||||
$this->funding_platforms = (new PlatformModel())->getPodcastPlatforms($this->id, 'funding');
|
||||
$this->funding_platforms = new PlatformModel()
|
||||
->getPlatforms($this->id, 'funding');
|
||||
}
|
||||
|
||||
return $this->funding_platforms;
|
||||
|
|
@ -569,24 +492,20 @@ class Podcast extends Entity
|
|||
*/
|
||||
public function getOtherCategories(): array
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Podcast must be created before getting other categories.');
|
||||
}
|
||||
|
||||
if ($this->other_categories === null) {
|
||||
$this->other_categories = (new CategoryModel())->getPodcastCategories($this->id);
|
||||
$this->other_categories = new CategoryModel()
|
||||
->getPodcastCategories($this->id);
|
||||
}
|
||||
|
||||
return $this->other_categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]|string[]
|
||||
* @return int[]
|
||||
*/
|
||||
public function getOtherCategoriesIds(): array
|
||||
{
|
||||
if ($this->other_categories_ids === null) {
|
||||
// @phpstan-ignore-next-line
|
||||
if ($this->other_categories_ids === []) {
|
||||
$this->other_categories_ids = array_column($this->getOtherCategories(), 'id');
|
||||
}
|
||||
|
||||
|
|
@ -598,7 +517,7 @@ class Podcast extends Entity
|
|||
*/
|
||||
public function setLocation(?Location $location = null): static
|
||||
{
|
||||
if ($location === null) {
|
||||
if (! $location instanceof Location) {
|
||||
$this->attributes['location_name'] = null;
|
||||
$this->attributes['location_geo'] = null;
|
||||
$this->attributes['location_osm'] = null;
|
||||
|
|
@ -626,65 +545,17 @@ class Podcast extends Entity
|
|||
return null;
|
||||
}
|
||||
|
||||
if ($this->location === null) {
|
||||
if (! $this->location instanceof Location) {
|
||||
$this->location = new Location($this->location_name, $this->location_geo, $this->location_osm);
|
||||
}
|
||||
|
||||
return $this->location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom rss tag as XML String
|
||||
*/
|
||||
public function getCustomRssString(): string
|
||||
{
|
||||
if ($this->attributes['custom_rss'] === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
helper('rss');
|
||||
|
||||
$xmlNode = (new SimpleRSSElement(
|
||||
'<?xml version="1.0" encoding="utf-8"?><rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:podcast="https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"/>',
|
||||
))->addChild('channel');
|
||||
array_to_rss([
|
||||
'elements' => $this->custom_rss,
|
||||
], $xmlNode);
|
||||
|
||||
return (string) str_replace(['<channel>', '</channel>'], '', $xmlNode->asXML());
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves custom rss tag into json
|
||||
*/
|
||||
public function setCustomRssString(string $customRssString): static
|
||||
{
|
||||
if ($customRssString === '') {
|
||||
$this->attributes['custom_rss'] = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
helper('rss');
|
||||
$customRssArray = rss_to_array(
|
||||
simplexml_load_string(
|
||||
'<?xml version="1.0" encoding="utf-8"?><rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:podcast="https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"><channel>' .
|
||||
$customRssString .
|
||||
'</channel></rss>',
|
||||
),
|
||||
)['elements'][0];
|
||||
|
||||
if (array_key_exists('elements', $customRssArray)) {
|
||||
$this->attributes['custom_rss'] = json_encode($customRssArray['elements']);
|
||||
} else {
|
||||
$this->attributes['custom_rss'] = null;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIsPremium(): bool
|
||||
{
|
||||
// podcast is premium if at least one of its episodes is set as premium
|
||||
return (new EpisodeModel())->doesPodcastHavePremiumEpisodes($this->id);
|
||||
return new EpisodeModel()
|
||||
->doesPodcastHavePremiumEpisodes($this->id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,18 +26,19 @@ class Post extends FediversePost
|
|||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'id' => 'string',
|
||||
'uri' => 'string',
|
||||
'actor_id' => 'integer',
|
||||
'in_reply_to_id' => '?string',
|
||||
'reblog_of_id' => '?string',
|
||||
'episode_id' => '?integer',
|
||||
'message' => 'string',
|
||||
'message_html' => 'string',
|
||||
'id' => 'string',
|
||||
'uri' => 'string',
|
||||
'actor_id' => 'integer',
|
||||
'in_reply_to_id' => '?string',
|
||||
'reblog_of_id' => '?string',
|
||||
'episode_id' => '?integer',
|
||||
'message' => 'string',
|
||||
'message_html' => 'string',
|
||||
'is_private' => 'boolean',
|
||||
'favourites_count' => 'integer',
|
||||
'reblogs_count' => 'integer',
|
||||
'replies_count' => 'integer',
|
||||
'created_by' => 'integer',
|
||||
'reblogs_count' => 'integer',
|
||||
'replies_count' => 'integer',
|
||||
'created_by' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -50,7 +51,8 @@ class Post extends FediversePost
|
|||
}
|
||||
|
||||
if (! $this->episode instanceof Episode) {
|
||||
$this->episode = (new EpisodeModel())->getEpisodeById($this->episode_id);
|
||||
$this->episode = new EpisodeModel()
|
||||
->getEpisodeById($this->episode_id);
|
||||
}
|
||||
|
||||
return $this->episode;
|
||||
|
|
|
|||
|
|
@ -2,26 +2,43 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Fediverse\Filters;
|
||||
namespace App\Filters;
|
||||
|
||||
use CodeIgniter\Filters\FilterInterface;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Override;
|
||||
|
||||
class AllowCorsFilter implements FilterInterface
|
||||
{
|
||||
public function before(RequestInterface $request, $arguments = null): void
|
||||
/**
|
||||
* @param list<string>|null $arguments
|
||||
*
|
||||
* @return RequestInterface|ResponseInterface|string|null
|
||||
*/
|
||||
#[Override]
|
||||
public function before(RequestInterface $request, $arguments = null)
|
||||
{
|
||||
// Do something here
|
||||
return null;
|
||||
}
|
||||
|
||||
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): void
|
||||
/**
|
||||
* @param list<string>|null $arguments
|
||||
*
|
||||
* @return ResponseInterface|null
|
||||
*/
|
||||
#[Override]
|
||||
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
|
||||
{
|
||||
if (! $response->hasHeader('Cache-Control')) {
|
||||
$response->setHeader('Cache-Control', 'public, max-age=86400');
|
||||
}
|
||||
|
||||
$response->setHeader('Access-Control-Allow-Origin', '*') // for allowing any domain, insecure
|
||||
->setHeader('Access-Control-Allow-Headers', '*') // for allowing any headers, insecure
|
||||
->setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS') // allows GET and OPTIONS methods only
|
||||
->setHeader('Access-Control-Max-Age', '86400')
|
||||
->setHeader('Cache-Control', 'public, max-age=86400')
|
||||
->setStatusCode(200);
|
||||
->setHeader('Access-Control-Max-Age', '86400');
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
use App\Models\ActorModel;
|
||||
use Modules\Auth\Entities\User;
|
||||
use Modules\Fediverse\Entities\Actor;
|
||||
|
||||
if (! function_exists('user')) {
|
||||
/**
|
||||
* Returns the User instance for the current logged in user.
|
||||
*/
|
||||
function user(): ?User
|
||||
{
|
||||
$authenticate = service('authentication');
|
||||
$authenticate->check();
|
||||
return $authenticate->user();
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('set_interact_as_actor')) {
|
||||
/**
|
||||
* Sets the actor id of which the user is acting as
|
||||
*/
|
||||
function set_interact_as_actor(int $actorId): void
|
||||
{
|
||||
$authenticate = service('authentication');
|
||||
$authenticate->check();
|
||||
|
||||
$session = session();
|
||||
$session->set('interact_as_actor_id', $actorId);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('remove_interact_as_actor')) {
|
||||
/**
|
||||
* Removes the actor id of which the user is acting as
|
||||
*/
|
||||
function remove_interact_as_actor(): void
|
||||
{
|
||||
$session = session();
|
||||
$session->remove('interact_as_actor_id');
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('interact_as_actor_id')) {
|
||||
/**
|
||||
* Sets the podcast id of which the user is acting as
|
||||
*/
|
||||
function interact_as_actor_id(): int
|
||||
{
|
||||
$authenticate = service('authentication');
|
||||
$authenticate->check();
|
||||
|
||||
$session = session();
|
||||
return $session->get('interact_as_actor_id');
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('interact_as_actor')) {
|
||||
/**
|
||||
* Get the actor the user is currently interacting as
|
||||
*/
|
||||
function interact_as_actor(): Actor | false
|
||||
{
|
||||
$authenticate = service('authentication');
|
||||
$authenticate->check();
|
||||
|
||||
$session = session();
|
||||
if ($session->has('interact_as_actor_id')) {
|
||||
return model(ActorModel::class, false)->getActorById($session->get('interact_as_actor_id'));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('can_user_interact')) {
|
||||
function can_user_interact(): bool
|
||||
{
|
||||
return (bool) interact_as_actor();
|
||||
}
|
||||
}
|
||||
|
|
@ -2,14 +2,6 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
use Config\Services;
|
||||
|
||||
if (! function_exists('render_breadcrumb')) {
|
||||
/**
|
||||
* Renders the breadcrumb navigation through the Breadcrumb service
|
||||
|
|
@ -17,20 +9,18 @@ if (! function_exists('render_breadcrumb')) {
|
|||
* @param string|null $class to be added to the breadcrumb nav
|
||||
* @return string html breadcrumb
|
||||
*/
|
||||
function render_breadcrumb(string $class = null): string
|
||||
function render_breadcrumb(?string $class = null): string
|
||||
{
|
||||
$breadcrumb = Services::breadcrumb();
|
||||
return $breadcrumb->render($class);
|
||||
return service('breadcrumb')->render($class);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('replace_breadcrumb_params')) {
|
||||
/**
|
||||
* @param string[] $newParams
|
||||
* @param array<string|int,string> $newParams
|
||||
*/
|
||||
function replace_breadcrumb_params(array $newParams): void
|
||||
{
|
||||
$breadcrumb = Services::breadcrumb();
|
||||
$breadcrumb->replaceParams(esc($newParams));
|
||||
service('breadcrumb')->replaceParams($newParams);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,37 +9,13 @@ declare(strict_types=1);
|
|||
*/
|
||||
|
||||
use App\Entities\Category;
|
||||
use App\Entities\Episode;
|
||||
use App\Entities\Location;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use CodeIgniter\View\Table;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
if (! function_exists('hint_tooltip')) {
|
||||
/**
|
||||
* Hint component
|
||||
*
|
||||
* Used to produce tooltip with a question mark icon for hint texts
|
||||
*
|
||||
* @param string $hintText The hint text
|
||||
*/
|
||||
function hint_tooltip(string $hintText = '', string $class = ''): string
|
||||
{
|
||||
$tooltip =
|
||||
'<span data-tooltip="bottom" tabindex="0" title="' .
|
||||
$hintText .
|
||||
'" class="inline-block align-middle opacity-75 focus:ring-accent';
|
||||
|
||||
if ($class !== '') {
|
||||
$tooltip .= ' ' . $class;
|
||||
}
|
||||
|
||||
return $tooltip . '">' . icon('question') . '</span>';
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
if (! function_exists('data_table')) {
|
||||
/**
|
||||
* Data table component
|
||||
|
|
@ -57,14 +33,13 @@ if (! function_exists('data_table')) {
|
|||
$template = [
|
||||
'table_open' => '<table class="w-full whitespace-nowrap">',
|
||||
|
||||
'thead_open' =>
|
||||
'<thead class="text-xs font-semibold text-left uppercase text-skin-muted">',
|
||||
'thead_open' => '<thead class="text-xs font-semibold text-left uppercase text-skin-muted">',
|
||||
|
||||
'heading_cell_start' => '<th class="px-4 py-2">',
|
||||
'cell_start' => '<td class="px-4 py-2">',
|
||||
'cell_alt_start' => '<td class="px-4 py-2">',
|
||||
'cell_start' => '<td class="px-4 py-2">',
|
||||
'cell_alt_start' => '<td class="px-4 py-2">',
|
||||
|
||||
'row_start' => '<tr class="border-t border-subtle hover:bg-base">',
|
||||
'row_start' => '<tr class="border-t border-subtle hover:bg-base">',
|
||||
'row_alt_start' => '<tr class="border-t border-subtle hover:bg-base">',
|
||||
];
|
||||
|
||||
|
|
@ -91,8 +66,8 @@ if (! function_exists('data_table')) {
|
|||
$table->addRow([
|
||||
[
|
||||
'colspan' => count($tableHeaders),
|
||||
'class' => 'px-4 py-2 italic font-semibold text-center',
|
||||
'data' => lang('Common.no_data'),
|
||||
'class' => 'px-4 py-2 italic font-semibold text-center',
|
||||
'data' => lang('Common.no_data'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
|
@ -113,31 +88,29 @@ if (! function_exists('publication_pill')) {
|
|||
*/
|
||||
function publication_pill(?Time $publicationDate, string $publicationStatus, string $customClass = ''): string
|
||||
{
|
||||
$class = match ($publicationStatus) {
|
||||
'published' => 'text-pine-500 border-pine-500 bg-pine-50',
|
||||
'scheduled' => 'text-red-600 border-red-600 bg-red-50',
|
||||
'with_podcast' => 'text-blue-600 border-blue-600 bg-blue-50',
|
||||
'not_published' => 'text-gray-600 border-gray-600 bg-gray-50',
|
||||
default => 'text-gray-600 border-gray-600 bg-gray-50',
|
||||
$variant = match ($publicationStatus) {
|
||||
'published' => 'success',
|
||||
'scheduled' => 'warning',
|
||||
'with_podcast' => 'info',
|
||||
'not_published' => 'default',
|
||||
default => 'default',
|
||||
};
|
||||
|
||||
$title = match ($publicationStatus) {
|
||||
'published', 'scheduled' => (string) $publicationDate,
|
||||
'with_podcast' => lang('Episode.with_podcast_hint'),
|
||||
'with_podcast' => lang('Episode.with_podcast_hint'),
|
||||
'not_published' => '',
|
||||
default => '',
|
||||
default => '',
|
||||
};
|
||||
|
||||
$label = lang('Episode.publication_status.' . $publicationStatus);
|
||||
|
||||
return '<span ' . ($title === '' ? '' : 'title="' . $title . '"') . ' class="flex items-center px-1 font-semibold border rounded w-max ' .
|
||||
$class .
|
||||
' ' .
|
||||
$customClass .
|
||||
'">' .
|
||||
$label .
|
||||
($publicationStatus === 'with_podcast' ? '<Icon glyph="warning" class="flex-shrink-0 ml-1 text-lg" />' : '') .
|
||||
'</span>';
|
||||
// @icon("error-warning-fill")
|
||||
return '<x-Pill ' . ($title === '' ? '' : 'title="' . $title . '"') . ' variant="' . $variant . '" class="' . $customClass .
|
||||
'">' . $label . ($publicationStatus === 'with_podcast' ? icon('error-warning-fill', [
|
||||
'class' => 'flex-shrink-0 ml-1 text-lg',
|
||||
]) : '') .
|
||||
'</x-Pill>';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -156,20 +129,20 @@ if (! function_exists('publication_button')) {
|
|||
$label = lang('Episode.publish');
|
||||
$route = route_to('episode-publish', $podcastId, $episodeId);
|
||||
$variant = 'primary';
|
||||
$iconLeft = 'upload-cloud';
|
||||
$iconLeft = 'upload-cloud-fill'; // @icon("upload-cloud-fill")
|
||||
break;
|
||||
case 'with_podcast':
|
||||
case 'scheduled':
|
||||
$label = lang('Episode.publish_edit');
|
||||
$route = route_to('episode-publish_edit', $podcastId, $episodeId);
|
||||
$variant = 'warning';
|
||||
$iconLeft = 'upload-cloud';
|
||||
$iconLeft = 'upload-cloud-fill'; // @icon("upload-cloud-fill")
|
||||
break;
|
||||
case 'published':
|
||||
$label = lang('Episode.unpublish');
|
||||
$route = route_to('episode-unpublish', $podcastId, $episodeId);
|
||||
$variant = 'danger';
|
||||
$iconLeft = 'cloud-off';
|
||||
$iconLeft = 'cloud-off-fill'; // @icon("cloud-off-fill")
|
||||
break;
|
||||
default:
|
||||
$label = '';
|
||||
|
|
@ -180,7 +153,7 @@ if (! function_exists('publication_button')) {
|
|||
}
|
||||
|
||||
return <<<HTML
|
||||
<Button variant="{$variant}" uri="{$route}" iconLeft="{$iconLeft}" >{$label}</Button>
|
||||
<x-Button variant="{$variant}" uri="{$route}" iconLeft="{$iconLeft}" >{$label}</x-Button>
|
||||
HTML;
|
||||
}
|
||||
}
|
||||
|
|
@ -206,7 +179,7 @@ if (! function_exists('publication_status_banner')) {
|
|||
$bannerDisclaimer = lang('Podcast.publication_status_banner.draft_mode');
|
||||
$bannerText = lang('Podcast.publication_status_banner.scheduled', [
|
||||
'publication_date' => local_datetime($publicationDate),
|
||||
], null, false);
|
||||
]);
|
||||
$linkRoute = route_to('podcast-publish_edit', $podcastId);
|
||||
$linkLabel = lang('Podcast.publish_edit');
|
||||
break;
|
||||
|
|
@ -219,8 +192,8 @@ if (! function_exists('publication_status_banner')) {
|
|||
}
|
||||
|
||||
return <<<HTML
|
||||
<div class="flex items-center px-12 py-1 border-b bg-stripes-gray border-subtle" role="alert">
|
||||
<p class="text-gray-900">
|
||||
<div class="flex flex-wrap items-baseline px-4 py-2 border-b md:px-12 bg-stripes-default border-subtle" role="alert">
|
||||
<p class="flex items-baseline text-gray-900">
|
||||
<span class="text-xs font-semibold tracking-wide uppercase">{$bannerDisclaimer}</span>
|
||||
<span class="ml-3 text-sm">{$bannerText}</span>
|
||||
</p>
|
||||
|
|
@ -232,6 +205,58 @@ if (! function_exists('publication_status_banner')) {
|
|||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
if (! function_exists('episode_publication_status_banner')) {
|
||||
/**
|
||||
* Publication status banner component for podcasts
|
||||
*
|
||||
* Displays the appropriate banner depending on the podcast's publication status.
|
||||
*/
|
||||
function episode_publication_status_banner(Episode $episode, string $class = ''): string
|
||||
{
|
||||
switch ($episode->publication_status) {
|
||||
case 'not_published':
|
||||
$linkRoute = route_to('episode-publish', $episode->podcast_id, $episode->id);
|
||||
$publishLinkLabel = lang('Episode.publish');
|
||||
break;
|
||||
case 'scheduled':
|
||||
case 'with_podcast':
|
||||
$linkRoute = route_to('episode-publish_edit', $episode->podcast_id, $episode->id);
|
||||
$publishLinkLabel = lang('Episode.publish_edit');
|
||||
break;
|
||||
default:
|
||||
$bannerDisclaimer = '';
|
||||
$linkRoute = '';
|
||||
$publishLinkLabel = '';
|
||||
break;
|
||||
}
|
||||
|
||||
$bannerDisclaimer = lang('Episode.publication_status_banner.draft_mode');
|
||||
$bannerText = lang('Episode.publication_status_banner.text', [
|
||||
'publication_status' => $episode->publication_status,
|
||||
'publication_date' => $episode->published_at instanceof Time ? local_datetime(
|
||||
$episode->published_at,
|
||||
) : null,
|
||||
]);
|
||||
$previewLinkLabel = lang('Episode.publication_status_banner.preview');
|
||||
|
||||
return <<<HTML
|
||||
<div class="flex flex-wrap gap-4 items-baseline px-4 md:px-12 py-2 bg-stripes-default border-subtle {$class}" role="alert">
|
||||
<p class="flex items-baseline text-gray-900">
|
||||
<span class="text-xs font-semibold tracking-wide uppercase">{$bannerDisclaimer}</span>
|
||||
<span class="ml-3 text-sm">{$bannerText}</span>
|
||||
</p>
|
||||
<div class="flex items-baseline">
|
||||
<a href="{$episode->preview_link}" class="ml-1 text-sm font-semibold underline shadow-xs text-accent-base hover:text-accent-hover hover:no-underline">{$previewLinkLabel}</a>
|
||||
<span class="mx-1">•</span>
|
||||
<a href="{$linkRoute}" class="ml-1 text-sm font-semibold underline shadow-xs text-accent-base hover:text-accent-hover hover:no-underline">{$publishLinkLabel}</a>
|
||||
</div>
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
if (! function_exists('episode_numbering')) {
|
||||
/**
|
||||
* Returns relevant translated episode numbering.
|
||||
|
|
@ -242,7 +267,7 @@ if (! function_exists('episode_numbering')) {
|
|||
?int $episodeNumber = null,
|
||||
?int $seasonNumber = null,
|
||||
string $class = '',
|
||||
bool $isAbbr = false
|
||||
bool $isAbbr = false,
|
||||
): string {
|
||||
if (! $episodeNumber && ! $seasonNumber) {
|
||||
return '';
|
||||
|
|
@ -292,19 +317,20 @@ if (! function_exists('location_link')) {
|
|||
*/
|
||||
function location_link(?Location $location, string $class = ''): string
|
||||
{
|
||||
if ($location === null) {
|
||||
if (! $location instanceof Location) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return anchor(
|
||||
$location->url,
|
||||
icon('map-pin', 'mr-2 flex-shrink-0') . '<span class="truncate">' . esc($location->name) . '</span>',
|
||||
icon('map-pin-2-fill', [
|
||||
'class' => 'mr-2 flex-shrink-0',
|
||||
]) . '<span class="truncate">' . esc($location->name) . '</span>',
|
||||
[
|
||||
'class' =>
|
||||
'w-full overflow-hidden inline-flex items-baseline hover:underline focus:ring-accent' .
|
||||
'class' => 'w-full overflow-hidden inline-flex items-baseline hover:underline' .
|
||||
($class === '' ? '' : " {$class}"),
|
||||
'target' => '_blank',
|
||||
'rel' => 'noreferrer noopener',
|
||||
'rel' => 'noreferrer noopener',
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
@ -326,15 +352,15 @@ if (! function_exists('audio_player')) {
|
|||
id="castopod-vm-player"
|
||||
theme="light"
|
||||
language="{$language}"
|
||||
icons="castopod-icons"
|
||||
class="{$class} relative z-0"
|
||||
icons="castopod-vm-player-icons"
|
||||
style="--vm-player-box-shadow:0; --vm-player-theme: hsl(var(--color-accent-base)); --vm-control-focus-color: hsl(var(--color-accent-contrast)); --vm-control-spacing: 4px; --vm-menu-item-focus-bg: hsl(var(--color-background-highlight));"
|
||||
>
|
||||
<vm-audio preload="none">
|
||||
<source src="{$source}" type="{$mediaType}" />
|
||||
</vm-audio>
|
||||
<vm-ui>
|
||||
<vm-icon-library name="castopod-icons"></vm-icon-library>
|
||||
<vm-icon-library name="castopod-vm-player-icons"></vm-icon-library>
|
||||
<vm-controls full-width>
|
||||
<vm-playback-control></vm-playback-control>
|
||||
<vm-volume-control></vm-volume-control>
|
||||
|
|
@ -356,17 +382,17 @@ if (! function_exists('relative_time')) {
|
|||
function relative_time(Time $time, string $class = ''): string
|
||||
{
|
||||
$formatter = new IntlDateFormatter(service(
|
||||
'request'
|
||||
'request',
|
||||
)->getLocale(), IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE);
|
||||
$translatedDate = $time->toLocalizedString($formatter->getPattern());
|
||||
$datetime = $time->format(DateTime::ISO8601);
|
||||
$datetime = $time->format(DateTime::ATOM);
|
||||
|
||||
return <<<HTML
|
||||
<time-ago class="{$class}" datetime="{$datetime}">
|
||||
<relative-time tense="auto" class="{$class}" datetime="{$datetime}">
|
||||
<time
|
||||
datetime="{$datetime}"
|
||||
title="{$time}">{$translatedDate}</time>
|
||||
</time-ago>
|
||||
</relative-time>
|
||||
HTML;
|
||||
}
|
||||
}
|
||||
|
|
@ -377,23 +403,25 @@ if (! function_exists('local_datetime')) {
|
|||
function local_datetime(Time $time): string
|
||||
{
|
||||
$formatter = new IntlDateFormatter(service(
|
||||
'request'
|
||||
'request',
|
||||
)->getLocale(), IntlDateFormatter::MEDIUM, IntlDateFormatter::LONG);
|
||||
$translatedDate = $time->toLocalizedString($formatter->getPattern());
|
||||
$datetime = $time->format(DateTime::ISO8601);
|
||||
$datetime = $time->format(DateTime::ATOM);
|
||||
|
||||
return <<<HTML
|
||||
<local-time datetime="{$datetime}"
|
||||
weekday="long"
|
||||
month="long"
|
||||
<relative-time datetime="{$datetime}"
|
||||
prefix=""
|
||||
threshold="PT0S"
|
||||
weekday="long"
|
||||
day="numeric"
|
||||
month="long"
|
||||
year="numeric"
|
||||
hour="numeric"
|
||||
minute="numeric">
|
||||
<time
|
||||
datetime="{$datetime}"
|
||||
title="{$time}">{$translatedDate}</time>
|
||||
</local-time>
|
||||
</relative-time>
|
||||
HTML;
|
||||
}
|
||||
}
|
||||
|
|
@ -404,7 +432,7 @@ if (! function_exists('local_date')) {
|
|||
function local_date(Time $time): string
|
||||
{
|
||||
$formatter = new IntlDateFormatter(service(
|
||||
'request'
|
||||
'request',
|
||||
)->getLocale(), IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE);
|
||||
$translatedDate = $time->toLocalizedString($formatter->getPattern());
|
||||
|
||||
|
|
@ -414,7 +442,6 @@ if (! function_exists('local_date')) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
if (! function_exists('explicit_badge')) {
|
||||
|
|
@ -433,17 +460,49 @@ if (! function_exists('explicit_badge')) {
|
|||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
|
||||
if (! function_exists('category_label')) {
|
||||
function category_label(Category $category): string
|
||||
{
|
||||
$categoryLabel = '';
|
||||
if ($category->parent_id !== null) {
|
||||
$categoryLabel .= lang('Podcast.category_options.' . $category->parent->code, [], null, false) . ' › ';
|
||||
$categoryLabel .= lang('Podcast.category_options.' . $category->parent->code) . ' › ';
|
||||
}
|
||||
|
||||
return $categoryLabel . lang('Podcast.category_options.' . $category->code, [], null, false);
|
||||
return $categoryLabel . lang('Podcast.category_options.' . $category->code);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
if (! function_exists('downloads_abbr')) {
|
||||
function downloads_abbr(int $downloads): string
|
||||
{
|
||||
if ($downloads < 1000) {
|
||||
return (string) $downloads;
|
||||
}
|
||||
|
||||
$option = match (true) {
|
||||
$downloads < 1_000_000 => [
|
||||
'divider' => 1_000,
|
||||
'suffix' => 'K',
|
||||
],
|
||||
$downloads < 1_000_000_000 => [
|
||||
'divider' => 1_000_000,
|
||||
'suffix' => 'M',
|
||||
],
|
||||
default => [
|
||||
'divider' => 1_000_000_000,
|
||||
'suffix' => 'B',
|
||||
],
|
||||
};
|
||||
$formatter = new NumberFormatter(service('request')->getLocale(), NumberFormatter::DECIMAL);
|
||||
|
||||
$formatter->setPattern('#,##0.##');
|
||||
|
||||
$abbr = $formatter->format($downloads / $option['divider']) . $option['suffix'];
|
||||
|
||||
return <<<HTML
|
||||
<abbr title="{$downloads}">{$abbr}</abbr>
|
||||
HTML;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,26 +22,25 @@ if (! function_exists('form_textarea')) {
|
|||
|
||||
// Unsets default rows and cols if defined in extra field as array or string.
|
||||
if ((is_array($extra) && array_key_exists('rows', $extra)) || (is_string($extra) && stripos(
|
||||
preg_replace('~\s+~', '', $extra),
|
||||
'rows='
|
||||
(string) preg_replace('~\s+~', '', $extra),
|
||||
'rows=',
|
||||
) !== false)) {
|
||||
unset($defaults['rows']);
|
||||
}
|
||||
|
||||
if ((is_array($extra) && array_key_exists('cols', $extra)) || (is_string($extra) && stripos(
|
||||
preg_replace('~\s+~', '', $extra),
|
||||
'cols='
|
||||
(string) preg_replace('~\s+~', '', $extra),
|
||||
'cols=',
|
||||
) !== false)) {
|
||||
unset($defaults['cols']);
|
||||
}
|
||||
|
||||
return '<textarea ' . rtrim(parse_form_attributes($data, $defaults)) . stringify_attributes(
|
||||
$extra
|
||||
$extra,
|
||||
) . '>' . $val . "</textarea>\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (! function_exists('parse_form_attributes')) {
|
||||
/**
|
||||
* Parse the form attributes
|
||||
|
|
@ -61,7 +60,7 @@ if (! function_exists('parse_form_attributes')) {
|
|||
}
|
||||
}
|
||||
|
||||
if (! empty($attributes)) {
|
||||
if ($attributes !== []) {
|
||||
$default = array_merge($default, $attributes);
|
||||
}
|
||||
}
|
||||
|
|
@ -70,7 +69,7 @@ if (! function_exists('parse_form_attributes')) {
|
|||
|
||||
foreach ($default as $key => $val) {
|
||||
if (! is_bool($val)) {
|
||||
if ($key === 'name' && ! strlen($default['name'])) {
|
||||
if ($key === 'name' && ! strlen((string) $default['name'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@ declare(strict_types=1);
|
|||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
use App\Entities\Episode;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use JamesHeinrich\GetID3\WriteTags;
|
||||
use Modules\Media\FileManagers\FileManagerInterface;
|
||||
|
||||
if (! function_exists('write_audio_file_tags')) {
|
||||
/**
|
||||
|
|
@ -23,13 +24,16 @@ if (! function_exists('write_audio_file_tags')) {
|
|||
|
||||
// Initialize getID3 tag-writing module
|
||||
$tagwriter = new WriteTags();
|
||||
$tagwriter->filename = media_path($episode->audio->file_path);
|
||||
$tagwriter->filename = $episode->audio->file_name;
|
||||
|
||||
// set various options (optional)
|
||||
$tagwriter->tagformats = ['id3v2.4'];
|
||||
$tagwriter->tag_encoding = $TextEncoding;
|
||||
|
||||
$APICdata = file_get_contents(media_path($episode->cover->id3_path));
|
||||
/** @var FileManagerInterface $fileManager */
|
||||
$fileManager = service('file_manager');
|
||||
|
||||
$APICdata = (string) $fileManager->getFileContents($episode->cover->id3_key);
|
||||
|
||||
// TODO: variables used for podcast specific tags
|
||||
// $podcastUrl = $episode->podcast->link;
|
||||
|
|
@ -38,24 +42,16 @@ if (! function_exists('write_audio_file_tags')) {
|
|||
|
||||
// populate data array
|
||||
$TagData = [
|
||||
'title' => [esc($episode->title)],
|
||||
'artist' => [
|
||||
$episode->podcast->publisher === null
|
||||
? esc($episode->podcast->owner_name)
|
||||
: $episode->podcast->publisher,
|
||||
],
|
||||
'album' => [esc($episode->podcast->title)],
|
||||
'year' => [$episode->published_at !== null ? $episode->published_at->format('Y') : ''],
|
||||
'genre' => ['Podcast'],
|
||||
'comment' => [$episode->description],
|
||||
'track_number' => [(string) $episode->number],
|
||||
'title' => [esc($episode->title)],
|
||||
'artist' => [$episode->podcast->publisher ?? esc($episode->podcast->owner_name)],
|
||||
'album' => [esc($episode->podcast->title)],
|
||||
'year' => [$episode->published_at instanceof Time ? $episode->published_at->format('Y') : ''],
|
||||
'genre' => ['Podcast'],
|
||||
'comment' => [$episode->description],
|
||||
'track_number' => [(string) $episode->number],
|
||||
'copyright_message' => [$episode->podcast->copyright],
|
||||
'publisher' => [
|
||||
$episode->podcast->publisher === null
|
||||
? esc($episode->podcast->owner_name)
|
||||
: $episode->podcast->publisher,
|
||||
],
|
||||
'encoded_by' => ['Castopod'],
|
||||
'publisher' => [$episode->podcast->publisher ?? esc($episode->podcast->owner_name)],
|
||||
'encoded_by' => ['Castopod'],
|
||||
|
||||
// TODO: find a way to add the remaining tags for podcasts as the library doesn't seem to allow it
|
||||
// 'website' => [$podcast_url],
|
||||
|
|
@ -68,23 +64,21 @@ if (! function_exists('write_audio_file_tags')) {
|
|||
$TagData['attached_picture'][] = [
|
||||
// picturetypeid == Cover. More: module.tag.id3v2.php
|
||||
'picturetypeid' => 2,
|
||||
'data' => $APICdata,
|
||||
'description' => 'cover',
|
||||
'mime' => $episode->cover->file_mimetype,
|
||||
'data' => $APICdata,
|
||||
'description' => 'cover',
|
||||
'mime' => $episode->cover->file_mimetype,
|
||||
];
|
||||
|
||||
$tagwriter->tag_data = $TagData;
|
||||
|
||||
// write tags
|
||||
if ($tagwriter->WriteTags()) {
|
||||
echo 'Successfully wrote tags<br>';
|
||||
// Successfully wrote tags
|
||||
if ($tagwriter->warnings !== []) {
|
||||
echo 'There were some warnings:<br>' .
|
||||
implode('<br><br>', $tagwriter->warnings);
|
||||
log_message('warning', 'There were some warnings:' . PHP_EOL . implode(PHP_EOL, $tagwriter->warnings));
|
||||
}
|
||||
} else {
|
||||
echo 'Failed to write tags!<br>' .
|
||||
implode('<br><br>', $tagwriter->errors);
|
||||
log_message('critical', 'Failed to write tags!' . PHP_EOL . implode(PHP_EOL, $tagwriter->errors));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,139 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
use CodeIgniter\Files\File;
|
||||
use CodeIgniter\HTTP\Files\UploadedFile;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Config\Mimes;
|
||||
use Config\Services;
|
||||
|
||||
if (! function_exists('save_media')) {
|
||||
/**
|
||||
* Saves a file to the corresponding podcast folder in `public/media`
|
||||
*/
|
||||
function save_media(File | UploadedFile $file, string $folder = '', string $filename = null): string
|
||||
{
|
||||
if (($extension = $file->getExtension()) !== '') {
|
||||
$filename = $filename . '.' . $extension;
|
||||
}
|
||||
|
||||
$mediaRoot = config('App')
|
||||
->mediaRoot . '/' . $folder;
|
||||
|
||||
if (! file_exists($mediaRoot)) {
|
||||
mkdir($mediaRoot, 0777, true);
|
||||
}
|
||||
|
||||
if (! file_exists($mediaRoot . '/index.html')) {
|
||||
touch($mediaRoot . '/index.html');
|
||||
}
|
||||
|
||||
// move to media folder, overwrite file if already existing
|
||||
$file->move($mediaRoot . '/', $filename, true);
|
||||
|
||||
return $folder . '/' . $filename;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('download_file')) {
|
||||
function download_file(string $fileUrl, string $mimetype = ''): File
|
||||
{
|
||||
$client = Services::curlrequest();
|
||||
|
||||
$response = $client->get($fileUrl, [
|
||||
'headers' => [
|
||||
'User-Agent' => 'Castopod/' . CP_VERSION,
|
||||
],
|
||||
]);
|
||||
|
||||
// redirect to new file location
|
||||
$newFileUrl = $fileUrl;
|
||||
while (
|
||||
in_array(
|
||||
$response->getStatusCode(),
|
||||
[
|
||||
ResponseInterface::HTTP_MOVED_PERMANENTLY,
|
||||
ResponseInterface::HTTP_FOUND,
|
||||
ResponseInterface::HTTP_SEE_OTHER,
|
||||
ResponseInterface::HTTP_NOT_MODIFIED,
|
||||
ResponseInterface::HTTP_TEMPORARY_REDIRECT,
|
||||
ResponseInterface::HTTP_PERMANENT_REDIRECT,
|
||||
],
|
||||
true,
|
||||
)
|
||||
) {
|
||||
$newFileUrl = trim($response->header('location')->getValue());
|
||||
$response = $client->get($newFileUrl, [
|
||||
'headers' => [
|
||||
'User-Agent' => 'Castopod/' . CP_VERSION,
|
||||
],
|
||||
'http_errors' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
$fileExtension = pathinfo(parse_url($newFileUrl, PHP_URL_PATH), PATHINFO_EXTENSION);
|
||||
$extension = $fileExtension === '' ? Mimes::guessExtensionFromType($mimetype) : $fileExtension;
|
||||
$tmpFilename =
|
||||
time() .
|
||||
'_' .
|
||||
bin2hex(random_bytes(10)) .
|
||||
'.' .
|
||||
$extension;
|
||||
$tmpFilePath = WRITEPATH . 'uploads/' . $tmpFilename;
|
||||
file_put_contents($tmpFilePath, $response->getBody());
|
||||
|
||||
return new File($tmpFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('media_path')) {
|
||||
/**
|
||||
* Prefixes the root media path to a given uri
|
||||
*
|
||||
* @param string|string[] $uri URI string or array of URI segments
|
||||
*/
|
||||
function media_path(string | array $uri = ''): string
|
||||
{
|
||||
// convert segment array to string
|
||||
if (is_array($uri)) {
|
||||
$uri = implode('/', $uri);
|
||||
}
|
||||
|
||||
$uri = trim($uri, '/');
|
||||
|
||||
return config('App')->mediaRoot . '/' . $uri;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('media_base_url')) {
|
||||
/**
|
||||
* Return the media base URL to use in views
|
||||
*
|
||||
* @param string|string[] $uri URI string or array of URI segments
|
||||
*/
|
||||
function media_base_url(string | array $uri = ''): string
|
||||
{
|
||||
// convert segment array to string
|
||||
if (is_array($uri)) {
|
||||
$uri = implode('/', $uri);
|
||||
}
|
||||
|
||||
$uri = trim($uri, '/');
|
||||
|
||||
$appConfig = config('App');
|
||||
$mediaBaseUrl = $appConfig->mediaBaseURL === '' ? $appConfig->baseURL : $appConfig->mediaBaseURL;
|
||||
|
||||
return rtrim($mediaBaseUrl, '/') .
|
||||
'/' .
|
||||
$appConfig->mediaRoot .
|
||||
'/' .
|
||||
$uri;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,13 +2,18 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Entities\Person;
|
||||
use App\Entities\Podcast;
|
||||
use Cocur\Slugify\Slugify;
|
||||
use Config\Images;
|
||||
use Modules\Media\Entities\Image;
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
|
||||
if (! function_exists('get_browser_language')) {
|
||||
/**
|
||||
* Gets the browser default language using the request header key `HTTP_ACCEPT_LANGUAGE`. Returns Castopod's default
|
||||
|
|
@ -36,105 +41,8 @@ if (! function_exists('slugify')) {
|
|||
$text = substr($text, 0, strrpos(substr($text, 0, $maxLength), ' '));
|
||||
}
|
||||
|
||||
// replace non letter or digits by -
|
||||
$text = preg_replace('~[^\pL\d]+~u', '-', $text);
|
||||
|
||||
$unwanted = [
|
||||
'Š' => 'S',
|
||||
'š' => 's',
|
||||
'Đ' => 'Dj',
|
||||
'đ' => 'dj',
|
||||
'Ž' => 'Z',
|
||||
'ž' => 'z',
|
||||
'Č' => 'C',
|
||||
'č' => 'c',
|
||||
'Ć' => 'C',
|
||||
'ć' => 'c',
|
||||
'À' => 'A',
|
||||
'Á' => 'A',
|
||||
'Â' => 'A',
|
||||
'Ã' => 'A',
|
||||
'Ä' => 'A',
|
||||
'Å' => 'A',
|
||||
'Æ' => 'AE',
|
||||
'Ç' => 'C',
|
||||
'È' => 'E',
|
||||
'É' => 'E',
|
||||
'Ê' => 'E',
|
||||
'Ë' => 'E',
|
||||
'Ì' => 'I',
|
||||
'Í' => 'I',
|
||||
'Î' => 'I',
|
||||
'Ï' => 'I',
|
||||
'Ñ' => 'N',
|
||||
'Ò' => 'O',
|
||||
'Ó' => 'O',
|
||||
'Ô' => 'O',
|
||||
'Õ' => 'O',
|
||||
'Ö' => 'O',
|
||||
'Ø' => 'O',
|
||||
'Œ' => 'OE',
|
||||
'Ù' => 'U',
|
||||
'Ú' => 'U',
|
||||
'Û' => 'U',
|
||||
'Ü' => 'U',
|
||||
'Ý' => 'Y',
|
||||
'Þ' => 'B',
|
||||
'ß' => 'Ss',
|
||||
'à' => 'a',
|
||||
'á' => 'a',
|
||||
'â' => 'a',
|
||||
'ã' => 'a',
|
||||
'ä' => 'a',
|
||||
'å' => 'a',
|
||||
'æ' => 'ae',
|
||||
'ç' => 'c',
|
||||
'è' => 'e',
|
||||
'é' => 'e',
|
||||
'ê' => 'e',
|
||||
'ë' => 'e',
|
||||
'ì' => 'i',
|
||||
'í' => 'i',
|
||||
'î' => 'i',
|
||||
'ï' => 'i',
|
||||
'ð' => 'o',
|
||||
'ñ' => 'n',
|
||||
'ò' => 'o',
|
||||
'ó' => 'o',
|
||||
'ô' => 'o',
|
||||
'õ' => 'o',
|
||||
'ö' => 'o',
|
||||
'ø' => 'o',
|
||||
'œ' => 'OE',
|
||||
'ù' => 'u',
|
||||
'ú' => 'u',
|
||||
'û' => 'u',
|
||||
'ý' => 'y',
|
||||
'þ' => 'b',
|
||||
'ÿ' => 'y',
|
||||
'Ŕ' => 'R',
|
||||
'ŕ' => 'r',
|
||||
'/' => '-',
|
||||
' ' => '-',
|
||||
];
|
||||
$text = strtr($text, $unwanted);
|
||||
|
||||
// transliterate
|
||||
$text = iconv('utf-8', 'us-ascii//TRANSLIT', $text);
|
||||
|
||||
// remove unwanted characters
|
||||
$text = preg_replace('~[^\-\w]+~', '', $text);
|
||||
|
||||
// trim
|
||||
$text = trim($text, '-');
|
||||
|
||||
// remove duplicate -
|
||||
$text = preg_replace('~-+~', '-', $text);
|
||||
|
||||
// lowercase
|
||||
$text = strtolower($text);
|
||||
|
||||
return $text;
|
||||
$slugify = new Slugify();
|
||||
return $slugify->slugify($text);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -172,7 +80,6 @@ if (! function_exists('format_duration')) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
if (! function_exists('format_duration_symbol')) {
|
||||
/**
|
||||
* Formats duration in seconds to an hh(h) mm(min) ss(s) string. Doesn't show leading zeros if any.
|
||||
|
|
@ -203,22 +110,6 @@ if (! function_exists('format_duration_symbol')) {
|
|||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
if (! function_exists('podcast_uuid')) {
|
||||
/**
|
||||
* Generate UUIDv5 for podcast. For more information, see
|
||||
* https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#guid
|
||||
*/
|
||||
function podcast_uuid(string $feedUrl): string
|
||||
{
|
||||
$uuid = service('uuid');
|
||||
// 'ead4c236-bf58-58c6-a2c6-a6b28d128cb6' is the uuid of the podcast namespace
|
||||
return $uuid->uuid5('ead4c236-bf58-58c6-a2c6-a6b28d128cb6', $feedUrl)
|
||||
->toString();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
if (! function_exists('generate_random_salt')) {
|
||||
function generate_random_salt(int $length = 64): string
|
||||
{
|
||||
|
|
@ -237,9 +128,7 @@ if (! function_exists('generate_random_salt')) {
|
|||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
|
||||
if (! function_exists('file_upload_max_size')) {
|
||||
|
||||
/**
|
||||
* Returns a file size limit in bytes based on the PHP upload_max_filesize and post_max_size Adapted from:
|
||||
* https://stackoverflow.com/a/25370978
|
||||
|
|
@ -274,7 +163,7 @@ if (! function_exists('parse_size')) {
|
|||
$size = (float) preg_replace('~[^0-9\.]~', '', $size); // Remove the non-numeric characters from the size.
|
||||
if ($unit !== '') {
|
||||
// Find the position of the unit in the ordered string which is the power of magnitude to multiply a kilobyte by.
|
||||
return round($size * pow(1024, (float) stripos('bkmgtpezy', $unit[0])));
|
||||
return round($size * 1024 ** ((float) stripos('bkmgtpezy', $unit[0])));
|
||||
}
|
||||
|
||||
return round($size);
|
||||
|
|
@ -293,8 +182,90 @@ if (! function_exists('format_bytes')) {
|
|||
$pow = floor(($bytes ? log($bytes) : 0) / log($is_binary ? 1024 : 1000));
|
||||
$pow = min($pow, count($units) - 1);
|
||||
|
||||
$bytes /= pow($is_binary ? 1024 : 1000, $pow);
|
||||
$bytes /= ($is_binary ? 1024 : 1000) ** $pow;
|
||||
|
||||
return round($bytes, $precision) . $units[$pow];
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_site_icon_url')) {
|
||||
function get_site_icon_url(string $size): string
|
||||
{
|
||||
if (config('App')->siteIcon['ico'] === service('settings')->get('App.siteIcon')['ico']) {
|
||||
// return default site icon url
|
||||
return base_url(service('settings')->get('App.siteIcon')[$size]);
|
||||
}
|
||||
|
||||
return service('file_manager')->getUrl(service('settings')->get('App.siteIcon')[$size]);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_podcast_banner')) {
|
||||
function get_podcast_banner_url(Podcast $podcast, string $size): string
|
||||
{
|
||||
if (! $podcast->banner instanceof Image) {
|
||||
$defaultBanner = config('Images')
|
||||
->podcastBannerDefaultPaths[service('settings')->get('App.theme')] ?? config(
|
||||
Images::class,
|
||||
)->podcastBannerDefaultPaths['default'];
|
||||
|
||||
$sizes = config('Images')
|
||||
->podcastBannerSizes;
|
||||
|
||||
$sizeConfig = $sizes[$size];
|
||||
helper('filesystem');
|
||||
|
||||
// return default site icon url
|
||||
return base_url(
|
||||
change_file_path($defaultBanner['path'], '_' . $size, $sizeConfig['extension'] ?? null),
|
||||
);
|
||||
}
|
||||
|
||||
$sizeKey = $size . '_url';
|
||||
return $podcast->banner->{$sizeKey};
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_podcast_banner_mimetype')) {
|
||||
function get_podcast_banner_mimetype(Podcast $podcast, string $size): string
|
||||
{
|
||||
if (! $podcast->banner instanceof Image) {
|
||||
$sizes = config('Images')
|
||||
->podcastBannerSizes;
|
||||
|
||||
$sizeConfig = $sizes[$size];
|
||||
helper('filesystem');
|
||||
|
||||
// return default site icon url
|
||||
return array_key_exists('mimetype', $sizeConfig) ? $sizeConfig['mimetype'] : config(
|
||||
Images::class,
|
||||
)->podcastBannerDefaultMimeType;
|
||||
}
|
||||
|
||||
$mimetype = $size . '_mimetype';
|
||||
return $podcast->banner->{$mimetype};
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_avatar_url')) {
|
||||
function get_avatar_url(Person $person, string $size): string
|
||||
{
|
||||
if (! $person->avatar instanceof Image) {
|
||||
$defaultAvatarPath = config('Images')
|
||||
->avatarDefaultPath;
|
||||
|
||||
$sizes = config('Images')
|
||||
->personAvatarSizes;
|
||||
|
||||
$sizeConfig = $sizes[$size];
|
||||
|
||||
helper('filesystem');
|
||||
|
||||
// return default avatar url
|
||||
return base_url(change_file_path($defaultAvatarPath, '_' . $size, $sizeConfig['extension'] ?? null));
|
||||
}
|
||||
|
||||
$sizeKey = $size . '_url';
|
||||
return $person->avatar->{$sizeKey};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,30 +16,37 @@ if (! function_exists('render_page_links')) {
|
|||
*
|
||||
* @return string html pages navigation
|
||||
*/
|
||||
function render_page_links(string $class = null): string
|
||||
function render_page_links(?string $class = null, ?string $podcastHandle = null): string
|
||||
{
|
||||
$pages = (new PageModel())->findAll();
|
||||
$pages = new PageModel()
|
||||
->findAll();
|
||||
$links = anchor(route_to('home'), lang('Common.home'), [
|
||||
'class' => 'px-2 py-1 underline hover:no-underline focus:ring-accent',
|
||||
'class' => 'px-2 py-1 underline hover:no-underline',
|
||||
]);
|
||||
if ($podcastHandle !== null) {
|
||||
$links .= anchor(route_to('podcast-links', $podcastHandle), lang('Podcast.links'), [
|
||||
'class' => 'px-2 py-1 underline hover:no-underline',
|
||||
]);
|
||||
}
|
||||
|
||||
$links .= anchor(route_to('credits'), lang('Person.credits'), [
|
||||
'class' => 'px-2 py-1 underline hover:no-underline focus:ring-accent',
|
||||
'class' => 'px-2 py-1 underline hover:no-underline',
|
||||
]);
|
||||
$links .= anchor(route_to('map'), lang('Page.map.title'), [
|
||||
'class' => 'px-2 py-1 underline hover:no-underline focus:ring-accent',
|
||||
'class' => 'px-2 py-1 underline hover:no-underline',
|
||||
]);
|
||||
foreach ($pages as $page) {
|
||||
$links .= anchor($page->link, esc($page->title), [
|
||||
'class' => 'px-2 py-1 underline hover:no-underline focus:ring-accent',
|
||||
'class' => 'px-2 py-1 underline hover:no-underline',
|
||||
]);
|
||||
}
|
||||
|
||||
// if set in .env, add legal notice link at the end of page links
|
||||
if (config('App')->legalNoticeURL !== null) {
|
||||
$links .= anchor(config('App')->legalNoticeURL, lang('Common.legal_notice'), [
|
||||
'class' => 'px-2 py-1 underline hover:no-underline focus:ring-accent',
|
||||
'class' => 'px-2 py-1 underline hover:no-underline',
|
||||
'target' => '_blank',
|
||||
'rel' => 'noopener noreferrer',
|
||||
'rel' => 'noopener noreferrer',
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,16 @@ declare(strict_types=1);
|
|||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
use App\Entities\Category;
|
||||
use App\Entities\Location;
|
||||
use App\Entities\Podcast;
|
||||
use App\Libraries\SimpleRSSElement;
|
||||
use App\Libraries\RssFeed;
|
||||
use App\Models\PodcastModel;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use Config\Mimes;
|
||||
use Modules\Media\Entities\Chapters;
|
||||
use Modules\Media\Entities\Transcript;
|
||||
use Modules\Plugins\Core\Plugins;
|
||||
use Modules\PremiumPodcasts\Entities\Subscription;
|
||||
|
||||
if (! function_exists('get_rss_feed')) {
|
||||
|
|
@ -25,25 +29,21 @@ if (! function_exists('get_rss_feed')) {
|
|||
function get_rss_feed(
|
||||
Podcast $podcast,
|
||||
string $serviceSlug = '',
|
||||
Subscription $subscription = null,
|
||||
string $token = null
|
||||
?Subscription $subscription = null,
|
||||
?string $token = null,
|
||||
): string {
|
||||
/** @var Plugins $plugins */
|
||||
$plugins = service('plugins');
|
||||
|
||||
$episodes = $podcast->episodes;
|
||||
|
||||
$itunesNamespace = 'http://www.itunes.com/dtds/podcast-1.0.dtd';
|
||||
$rss = new RssFeed();
|
||||
|
||||
$podcastNamespace =
|
||||
'https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md';
|
||||
|
||||
$atomNamespace = 'http://www.w3.org/2005/Atom';
|
||||
|
||||
$rss = new SimpleRSSElement(
|
||||
"<?xml version='1.0' encoding='utf-8'?><rss version='2.0' xmlns:itunes='{$itunesNamespace}' xmlns:podcast='{$podcastNamespace}' xmlns:atom='{$atomNamespace}' xmlns:content='http://purl.org/rss/1.0/modules/content/'></rss>"
|
||||
);
|
||||
$plugins->rssBeforeChannel($podcast);
|
||||
|
||||
$channel = $rss->addChild('channel');
|
||||
|
||||
$atomLink = $channel->addChild('link', null, $atomNamespace);
|
||||
$atomLink = $channel->addChild('link', null, RssFeed::ATOM_NAMESPACE);
|
||||
$atomLink->addAttribute('href', $podcast->feed_url);
|
||||
$atomLink->addAttribute('rel', 'self');
|
||||
$atomLink->addAttribute('type', 'application/rss+xml');
|
||||
|
|
@ -52,32 +52,43 @@ if (! function_exists('get_rss_feed')) {
|
|||
$websubHubs = config('WebSub')
|
||||
->hubs;
|
||||
foreach ($websubHubs as $websubHub) {
|
||||
$atomLinkHub = $channel->addChild('link', null, $atomNamespace);
|
||||
$atomLinkHub = $channel->addChild('link', null, RssFeed::ATOM_NAMESPACE);
|
||||
$atomLinkHub->addAttribute('href', $websubHub);
|
||||
$atomLinkHub->addAttribute('rel', 'hub');
|
||||
$atomLinkHub->addAttribute('type', 'application/rss+xml');
|
||||
}
|
||||
|
||||
if ($podcast->new_feed_url !== null) {
|
||||
$channel->addChild('new-feed-url', $podcast->new_feed_url, $itunesNamespace);
|
||||
$channel->addChild('new-feed-url', $podcast->new_feed_url, RssFeed::ITUNES_NAMESPACE);
|
||||
}
|
||||
|
||||
// the last build date corresponds to the creation of the feed.xml cache
|
||||
$channel->addChild('lastBuildDate', (new Time('now'))->format(DATE_RFC1123));
|
||||
$channel->addChild('lastBuildDate', new Time('now')->format(DATE_RFC1123));
|
||||
$channel->addChild('generator', 'Castopod - https://castopod.org/');
|
||||
$channel->addChild('docs', 'https://cyber.harvard.edu/rss/rss.html');
|
||||
|
||||
$channel->addChild('guid', $podcast->guid, $podcastNamespace);
|
||||
if ($podcast->guid === '') {
|
||||
// FIXME: guid shouldn't be empty here as it should be filled upon Podcast creation
|
||||
$uuid = service('uuid');
|
||||
// 'ead4c236-bf58-58c6-a2c6-a6b28d128cb6' is the uuid of the podcast namespace
|
||||
$podcast->guid = $uuid->uuid5('ead4c236-bf58-58c6-a2c6-a6b28d128cb6', $podcast->feed_url)
|
||||
->toString();
|
||||
|
||||
new PodcastModel()
|
||||
->save($podcast);
|
||||
}
|
||||
|
||||
$channel->addChild('guid', $podcast->guid, RssFeed::PODCAST_NAMESPACE);
|
||||
$channel->addChild('title', $podcast->title, null, false);
|
||||
$channel->addChildWithCDATA('description', $podcast->description_html);
|
||||
|
||||
$itunesImage = $channel->addChild('image', null, $itunesNamespace);
|
||||
$itunesImage = $channel->addChild('image', null, RssFeed::ITUNES_NAMESPACE);
|
||||
|
||||
$itunesImage->addAttribute('href', $podcast->cover->feed_url);
|
||||
|
||||
$channel->addChild('language', $podcast->language_code);
|
||||
if ($podcast->location !== null) {
|
||||
$locationElement = $channel->addChild('location', $podcast->location->name, $podcastNamespace);
|
||||
if ($podcast->location instanceof Location) {
|
||||
$locationElement = $channel->addChild('location', $podcast->location->name, RssFeed::PODCAST_NAMESPACE);
|
||||
if ($podcast->location->geo !== null) {
|
||||
$locationElement->addAttribute('geo', $podcast->location->geo);
|
||||
}
|
||||
|
|
@ -87,38 +98,25 @@ if (! function_exists('get_rss_feed')) {
|
|||
}
|
||||
}
|
||||
|
||||
if ($podcast->payment_pointer !== null) {
|
||||
$valueElement = $channel->addChild('value', null, $podcastNamespace);
|
||||
$valueElement->addAttribute('type', 'webmonetization');
|
||||
$valueElement->addAttribute('method', '');
|
||||
$valueElement->addAttribute('suggested', '');
|
||||
$recipientElement = $valueElement->addChild('valueRecipient', null, $podcastNamespace);
|
||||
$recipientElement->addAttribute('name', $podcast->owner_name);
|
||||
$recipientElement->addAttribute('type', 'ILP');
|
||||
$recipientElement->addAttribute('address', $podcast->payment_pointer);
|
||||
$recipientElement->addAttribute('split', '100');
|
||||
}
|
||||
|
||||
$channel
|
||||
->addChild('locked', $podcast->is_locked ? 'yes' : 'no', $podcastNamespace)
|
||||
->addChild('locked', $podcast->is_locked ? 'yes' : 'no', RssFeed::PODCAST_NAMESPACE)
|
||||
->addAttribute('owner', $podcast->owner_email);
|
||||
|
||||
if ($podcast->imported_feed_url !== null) {
|
||||
$channel->addChild('previousUrl', $podcast->imported_feed_url, $podcastNamespace);
|
||||
$channel->addChild('previousUrl', $podcast->imported_feed_url, RssFeed::PODCAST_NAMESPACE);
|
||||
}
|
||||
|
||||
foreach ($podcast->podcasting_platforms as $podcastingPlatform) {
|
||||
$podcastingPlatformElement = $channel->addChild('id', null, $podcastNamespace);
|
||||
$podcastingPlatformElement = $channel->addChild('id', null, RssFeed::PODCAST_NAMESPACE);
|
||||
$podcastingPlatformElement->addAttribute('platform', $podcastingPlatform->slug);
|
||||
if ($podcastingPlatform->account_id !== null) {
|
||||
$podcastingPlatformElement->addAttribute('id', $podcastingPlatform->account_id);
|
||||
}
|
||||
|
||||
if ($podcastingPlatform->link_url !== null) {
|
||||
$podcastingPlatformElement->addAttribute('url', $podcastingPlatform->link_url);
|
||||
}
|
||||
$podcastingPlatformElement->addAttribute('url', $podcastingPlatform->link_url);
|
||||
}
|
||||
|
||||
$castopodSocialElement = $channel->addChild('social', null, $podcastNamespace);
|
||||
$castopodSocialElement = $channel->addChild('social', null, RssFeed::PODCAST_NAMESPACE);
|
||||
$castopodSocialElement->addAttribute('priority', '1');
|
||||
$castopodSocialElement->addAttribute('platform', 'castopod');
|
||||
$castopodSocialElement->addAttribute('protocol', 'activitypub');
|
||||
|
|
@ -126,7 +124,7 @@ if (! function_exists('get_rss_feed')) {
|
|||
$castopodSocialElement->addAttribute('accountUrl', $podcast->link);
|
||||
|
||||
foreach ($podcast->social_platforms as $socialPlatform) {
|
||||
$socialElement = $channel->addChild('social', null, $podcastNamespace,);
|
||||
$socialElement = $channel->addChild('social', null, RssFeed::PODCAST_NAMESPACE);
|
||||
$socialElement->addAttribute('priority', '2');
|
||||
$socialElement->addAttribute('platform', $socialPlatform->slug);
|
||||
|
||||
|
|
@ -134,7 +132,7 @@ if (! function_exists('get_rss_feed')) {
|
|||
if (in_array(
|
||||
$socialPlatform->slug,
|
||||
['mastodon', 'peertube', 'funkwhale', 'misskey', 'mobilizon', 'pixelfed', 'plume', 'writefreely'],
|
||||
true
|
||||
true,
|
||||
)) {
|
||||
$socialElement->addAttribute('protocol', 'activitypub');
|
||||
} else {
|
||||
|
|
@ -145,46 +143,44 @@ if (! function_exists('get_rss_feed')) {
|
|||
$socialElement->addAttribute('accountId', esc($socialPlatform->account_id));
|
||||
}
|
||||
|
||||
if ($socialPlatform->link_url !== null) {
|
||||
$socialElement->addAttribute('accountUrl', esc($socialPlatform->link_url));
|
||||
}
|
||||
$socialElement->addAttribute('accountUrl', esc($socialPlatform->link_url));
|
||||
|
||||
if ($socialPlatform->slug === 'mastodon') {
|
||||
$socialSignUpelement = $socialElement->addChild('socialSignUp', null, $podcastNamespace);
|
||||
$socialSignUpelement = $socialElement->addChild('socialSignUp', null, RssFeed::PODCAST_NAMESPACE);
|
||||
$socialSignUpelement->addAttribute('priority', '1');
|
||||
$socialSignUpelement->addAttribute(
|
||||
'homeUrl',
|
||||
parse_url($socialPlatform->link_url, PHP_URL_SCHEME) . '://' . parse_url(
|
||||
$socialPlatform->link_url,
|
||||
PHP_URL_HOST
|
||||
) . '/public'
|
||||
parse_url((string) $socialPlatform->link_url, PHP_URL_SCHEME) . '://' . parse_url(
|
||||
(string) $socialPlatform->link_url,
|
||||
PHP_URL_HOST,
|
||||
) . '/public',
|
||||
);
|
||||
$socialSignUpelement->addAttribute(
|
||||
'signUpUrl',
|
||||
parse_url($socialPlatform->link_url, PHP_URL_SCHEME) . '://' . parse_url(
|
||||
$socialPlatform->link_url,
|
||||
PHP_URL_HOST
|
||||
) . '/auth/sign_up'
|
||||
parse_url((string) $socialPlatform->link_url, PHP_URL_SCHEME) . '://' . parse_url(
|
||||
(string) $socialPlatform->link_url,
|
||||
PHP_URL_HOST,
|
||||
) . '/auth/sign_up',
|
||||
);
|
||||
$castopodSocialSignUpelement = $castopodSocialElement->addChild(
|
||||
'socialSignUp',
|
||||
null,
|
||||
$podcastNamespace
|
||||
RssFeed::PODCAST_NAMESPACE,
|
||||
);
|
||||
$castopodSocialSignUpelement->addAttribute('priority', '1');
|
||||
$castopodSocialSignUpelement->addAttribute(
|
||||
'homeUrl',
|
||||
parse_url($socialPlatform->link_url, PHP_URL_SCHEME) . '://' . parse_url(
|
||||
$socialPlatform->link_url,
|
||||
PHP_URL_HOST
|
||||
) . '/public'
|
||||
parse_url((string) $socialPlatform->link_url, PHP_URL_SCHEME) . '://' . parse_url(
|
||||
(string) $socialPlatform->link_url,
|
||||
PHP_URL_HOST,
|
||||
) . '/public',
|
||||
);
|
||||
$castopodSocialSignUpelement->addAttribute(
|
||||
'signUpUrl',
|
||||
parse_url($socialPlatform->link_url, PHP_URL_SCHEME) . '://' . parse_url(
|
||||
$socialPlatform->link_url,
|
||||
PHP_URL_HOST
|
||||
) . '/auth/sign_up'
|
||||
parse_url((string) $socialPlatform->link_url, PHP_URL_SCHEME) . '://' . parse_url(
|
||||
(string) $socialPlatform->link_url,
|
||||
PHP_URL_HOST,
|
||||
) . '/auth/sign_up',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -193,19 +189,17 @@ if (! function_exists('get_rss_feed')) {
|
|||
$fundingPlatformElement = $channel->addChild(
|
||||
'funding',
|
||||
$fundingPlatform->account_id,
|
||||
$podcastNamespace,
|
||||
RssFeed::PODCAST_NAMESPACE,
|
||||
);
|
||||
$fundingPlatformElement->addAttribute('platform', $fundingPlatform->slug);
|
||||
if ($fundingPlatform->link_url !== null) {
|
||||
$fundingPlatformElement->addAttribute('url', $fundingPlatform->link_url);
|
||||
}
|
||||
$fundingPlatformElement->addAttribute('url', $fundingPlatform->link_url);
|
||||
}
|
||||
|
||||
foreach ($podcast->persons as $person) {
|
||||
foreach ($person->roles as $role) {
|
||||
$personElement = $channel->addChild('person', $person->full_name, $podcastNamespace,);
|
||||
$personElement = $channel->addChild('person', $person->full_name, RssFeed::PODCAST_NAMESPACE);
|
||||
|
||||
$personElement->addAttribute('img', $person->avatar->medium_url);
|
||||
$personElement->addAttribute('img', get_avatar_url($person, 'medium'));
|
||||
|
||||
if ($person->information_url !== null) {
|
||||
$personElement->addAttribute('href', $person->information_url);
|
||||
|
|
@ -232,32 +226,26 @@ if (! function_exists('get_rss_feed')) {
|
|||
$channel->addChild(
|
||||
'explicit',
|
||||
$podcast->parental_advisory === 'explicit' ? 'true' : 'false',
|
||||
$itunesNamespace,
|
||||
RssFeed::ITUNES_NAMESPACE,
|
||||
);
|
||||
|
||||
$channel->addChild(
|
||||
'author',
|
||||
$podcast->publisher ? $podcast->publisher : $podcast->owner_name,
|
||||
$itunesNamespace,
|
||||
false
|
||||
);
|
||||
$channel->addChild('author', $podcast->publisher ?: $podcast->owner_name, RssFeed::ITUNES_NAMESPACE, false);
|
||||
$channel->addChild('link', $podcast->link);
|
||||
|
||||
$owner = $channel->addChild('owner', null, $itunesNamespace);
|
||||
$owner = $channel->addChild('owner', null, RssFeed::ITUNES_NAMESPACE);
|
||||
|
||||
$owner->addChild('name', $podcast->owner_name, $itunesNamespace, false);
|
||||
$owner->addChild('name', $podcast->owner_name, RssFeed::ITUNES_NAMESPACE, false);
|
||||
$owner->addChild('email', $podcast->owner_email, RssFeed::ITUNES_NAMESPACE);
|
||||
|
||||
$owner->addChild('email', $podcast->owner_email, $itunesNamespace);
|
||||
|
||||
$channel->addChild('type', $podcast->type, $itunesNamespace);
|
||||
$channel->addChild('type', $podcast->type, RssFeed::ITUNES_NAMESPACE);
|
||||
$podcast->copyright &&
|
||||
$channel->addChild('copyright', $podcast->copyright);
|
||||
if ($podcast->is_blocked) {
|
||||
$channel->addChild('block', 'Yes', $itunesNamespace);
|
||||
if ($podcast->is_blocked || $subscription instanceof Subscription) {
|
||||
$channel->addChild('block', 'Yes', RssFeed::ITUNES_NAMESPACE);
|
||||
}
|
||||
|
||||
if ($podcast->is_completed) {
|
||||
$channel->addChild('complete', 'Yes', $itunesNamespace);
|
||||
$channel->addChild('complete', 'Yes', RssFeed::ITUNES_NAMESPACE);
|
||||
}
|
||||
|
||||
$image = $channel->addChild('image');
|
||||
|
|
@ -265,17 +253,16 @@ if (! function_exists('get_rss_feed')) {
|
|||
$image->addChild('title', $podcast->title, null, false);
|
||||
$image->addChild('link', $podcast->link);
|
||||
|
||||
if ($podcast->custom_rss !== null) {
|
||||
array_to_rss([
|
||||
'elements' => $podcast->custom_rss,
|
||||
], $channel);
|
||||
}
|
||||
// run plugins hook at the end
|
||||
$plugins->rssAfterChannel($podcast, $channel);
|
||||
|
||||
foreach ($episodes as $episode) {
|
||||
if ($episode->is_premium && $subscription === null) {
|
||||
if ($episode->is_premium && ! $subscription instanceof Subscription) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$plugins->rssBeforeItem($episode);
|
||||
|
||||
$item = $channel->addChild('item');
|
||||
$item->addChild('title', $episode->title, null, false);
|
||||
$enclosure = $item->addChild('enclosure');
|
||||
|
|
@ -287,15 +274,15 @@ if (! function_exists('get_rss_feed')) {
|
|||
|
||||
$enclosure->addAttribute(
|
||||
'url',
|
||||
$episode->audio_analytics_url . ($enclosureParams === '' ? '' : '?' . $enclosureParams),
|
||||
$episode->audio_url . ($enclosureParams === '' ? '' : '?' . $enclosureParams),
|
||||
);
|
||||
$enclosure->addAttribute('length', (string) $episode->audio->file_size);
|
||||
$enclosure->addAttribute('type', $episode->audio->file_mimetype);
|
||||
|
||||
$item->addChild('guid', $episode->guid);
|
||||
$item->addChild('pubDate', $episode->published_at->format(DATE_RFC1123));
|
||||
if ($episode->location !== null) {
|
||||
$locationElement = $item->addChild('location', $episode->location->name, $podcastNamespace,);
|
||||
if ($episode->location instanceof Location) {
|
||||
$locationElement = $item->addChild('location', $episode->location->name, RssFeed::PODCAST_NAMESPACE);
|
||||
if ($episode->location->geo !== null) {
|
||||
$locationElement->addAttribute('geo', $episode->location->geo);
|
||||
}
|
||||
|
|
@ -305,10 +292,10 @@ if (! function_exists('get_rss_feed')) {
|
|||
}
|
||||
}
|
||||
|
||||
$item->addChildWithCDATA('description', $episode->getDescriptionHtml($serviceSlug));
|
||||
$item->addChild('duration', (string) round($episode->audio->duration), $itunesNamespace);
|
||||
$item->addChildWithCDATA('description', $episode->description_html);
|
||||
$item->addChild('duration', (string) round($episode->audio->duration), RssFeed::ITUNES_NAMESPACE);
|
||||
$item->addChild('link', $episode->link);
|
||||
$episodeItunesImage = $item->addChild('image', null, $itunesNamespace);
|
||||
$episodeItunesImage = $item->addChild('image', null, RssFeed::ITUNES_NAMESPACE);
|
||||
$episodeItunesImage->addAttribute('href', $episode->cover->feed_url);
|
||||
|
||||
$episode->parental_advisory &&
|
||||
|
|
@ -317,71 +304,89 @@ if (! function_exists('get_rss_feed')) {
|
|||
$episode->parental_advisory === 'explicit'
|
||||
? 'true'
|
||||
: 'false',
|
||||
$itunesNamespace,
|
||||
RssFeed::ITUNES_NAMESPACE,
|
||||
);
|
||||
|
||||
$episode->number &&
|
||||
$item->addChild('episode', (string) $episode->number, $itunesNamespace);
|
||||
$item->addChild('episode', (string) $episode->number, RssFeed::ITUNES_NAMESPACE);
|
||||
$episode->season_number &&
|
||||
$item->addChild('season', (string) $episode->season_number, $itunesNamespace);
|
||||
$item->addChild('episodeType', $episode->type, $itunesNamespace);
|
||||
$item->addChild('season', (string) $episode->season_number, RssFeed::ITUNES_NAMESPACE);
|
||||
$item->addChild('episodeType', $episode->type, RssFeed::ITUNES_NAMESPACE);
|
||||
|
||||
// If episode is of type trailer, add podcast:trailer tag on channel level
|
||||
if ($episode->type === 'trailer') {
|
||||
$trailer = $channel->addChild('trailer', $episode->title, RssFeed::PODCAST_NAMESPACE);
|
||||
$trailer->addAttribute('pubdate', $episode->published_at->format(DATE_RFC2822));
|
||||
$trailer->addAttribute(
|
||||
'url',
|
||||
$episode->audio_url . ($enclosureParams === '' ? '' : '?' . $enclosureParams),
|
||||
);
|
||||
$trailer->addAttribute('length', (string) $episode->audio->file_size);
|
||||
$trailer->addAttribute('type', $episode->audio->file_mimetype);
|
||||
if ($episode->season_number !== null) {
|
||||
$trailer->addAttribute('season', (string) $episode->season_number);
|
||||
}
|
||||
}
|
||||
|
||||
// add link to episode comments as podcast-activity format
|
||||
$comments = $item->addChild('comments', null, $podcastNamespace);
|
||||
$comments = $item->addChild('comments', null, RssFeed::PODCAST_NAMESPACE);
|
||||
$comments->addAttribute('uri', url_to('episode-comments', $podcast->handle, $episode->slug));
|
||||
$comments->addAttribute('contentType', 'application/podcast-activity+json');
|
||||
|
||||
if ($episode->getPosts()) {
|
||||
$socialInteractUri = $episode->getPosts()[0]
|
||||
->uri;
|
||||
$socialInteractElement = $item->addChild('socialInteract', null, $podcastNamespace);
|
||||
$socialInteractElement = $item->addChild('socialInteract', null, RssFeed::PODCAST_NAMESPACE);
|
||||
$socialInteractElement->addAttribute('uri', $socialInteractUri);
|
||||
$socialInteractElement->addAttribute('priority', '1');
|
||||
$socialInteractElement->addAttribute('platform', 'castopod');
|
||||
$socialInteractElement->addAttribute('protocol', 'activitypub');
|
||||
$socialInteractElement->addAttribute(
|
||||
'accountId',
|
||||
"@{$podcast->actor->username}@{$podcast->actor->domain}"
|
||||
"@{$podcast->actor->username}@{$podcast->actor->domain}",
|
||||
);
|
||||
$socialInteractElement->addAttribute(
|
||||
'pubDate',
|
||||
$episode->getPosts()[0]
|
||||
->published_at->format(DateTime::ISO8601)
|
||||
->published_at->format(DateTime::ISO8601),
|
||||
);
|
||||
}
|
||||
|
||||
if ($episode->transcript !== null) {
|
||||
$transcriptElement = $item->addChild('transcript', null, $podcastNamespace);
|
||||
if ($episode->transcript instanceof Transcript) {
|
||||
$transcriptElement = $item->addChild('transcript', null, RssFeed::PODCAST_NAMESPACE);
|
||||
$transcriptElement->addAttribute('url', $episode->transcript->file_url);
|
||||
$transcriptElement->addAttribute(
|
||||
'type',
|
||||
Mimes::guessTypeFromExtension(
|
||||
pathinfo($episode->transcript->file_url, PATHINFO_EXTENSION)
|
||||
pathinfo($episode->transcript->file_url, PATHINFO_EXTENSION),
|
||||
) ?? 'text/html',
|
||||
);
|
||||
// Castopod only allows for captions (SubRip files)
|
||||
$transcriptElement->addAttribute('rel', 'captions');
|
||||
// TODO: allow for multiple languages
|
||||
$transcriptElement->addAttribute('language', $podcast->language_code);
|
||||
}
|
||||
|
||||
if ($episode->getChapters() !== null) {
|
||||
$chaptersElement = $item->addChild('chapters', null, $podcastNamespace);
|
||||
if ($episode->getChapters() instanceof Chapters) {
|
||||
$chaptersElement = $item->addChild('chapters', null, RssFeed::PODCAST_NAMESPACE);
|
||||
$chaptersElement->addAttribute('url', $episode->chapters->file_url);
|
||||
$chaptersElement->addAttribute('type', 'application/json+chapters');
|
||||
}
|
||||
|
||||
foreach ($episode->soundbites as $soundbite) {
|
||||
// TODO: differentiate video from soundbites?
|
||||
$soundbiteElement = $item->addChild('soundbite', $soundbite->title, $podcastNamespace);
|
||||
$soundbiteElement->addAttribute('start_time', (string) $soundbite->start_time);
|
||||
$soundbiteElement = $item->addChild('soundbite', $soundbite->title, RssFeed::PODCAST_NAMESPACE);
|
||||
$soundbiteElement->addAttribute('startTime', (string) $soundbite->start_time);
|
||||
$soundbiteElement->addAttribute('duration', (string) round($soundbite->duration, 3));
|
||||
}
|
||||
|
||||
foreach ($episode->persons as $person) {
|
||||
foreach ($person->roles as $role) {
|
||||
$personElement = $item->addChild('person', esc($person->full_name), $podcastNamespace,);
|
||||
$personElement = $item->addChild('person', esc($person->full_name), RssFeed::PODCAST_NAMESPACE);
|
||||
|
||||
$personElement->addAttribute(
|
||||
'role',
|
||||
esc(lang("PersonsTaxonomy.persons.{$role->group}.roles.{$role->role}.label", [], 'en'),),
|
||||
esc(lang("PersonsTaxonomy.persons.{$role->group}.roles.{$role->role}.label", [], 'en')),
|
||||
);
|
||||
|
||||
$personElement->addAttribute(
|
||||
|
|
@ -389,7 +394,7 @@ if (! function_exists('get_rss_feed')) {
|
|||
esc(lang("PersonsTaxonomy.persons.{$role->group}.label", [], 'en')),
|
||||
);
|
||||
|
||||
$personElement->addAttribute('img', $person->avatar->medium_url);
|
||||
$personElement->addAttribute('img', get_avatar_url($person, 'medium'));
|
||||
|
||||
if ($person->information_url !== null) {
|
||||
$personElement->addAttribute('href', $person->information_url);
|
||||
|
|
@ -398,14 +403,10 @@ if (! function_exists('get_rss_feed')) {
|
|||
}
|
||||
|
||||
if ($episode->is_blocked) {
|
||||
$item->addChild('block', 'Yes', $itunesNamespace);
|
||||
$item->addChild('block', 'Yes', RssFeed::ITUNES_NAMESPACE);
|
||||
}
|
||||
|
||||
if ($episode->custom_rss !== null) {
|
||||
array_to_rss([
|
||||
'elements' => $episode->custom_rss,
|
||||
], $item);
|
||||
}
|
||||
$plugins->rssAfterItem($episode, $item);
|
||||
}
|
||||
|
||||
return $rss->asXML();
|
||||
|
|
@ -416,20 +417,18 @@ if (! function_exists('add_category_tag')) {
|
|||
/**
|
||||
* Adds <itunes:category> and <category> tags to node for a given category
|
||||
*/
|
||||
function add_category_tag(SimpleXMLElement $node, Category $category): void
|
||||
function add_category_tag(RssFeed $node, Category $category): void
|
||||
{
|
||||
$itunesNamespace = 'http://www.itunes.com/dtds/podcast-1.0.dtd';
|
||||
|
||||
$itunesCategory = $node->addChild('category', null, $itunesNamespace);
|
||||
$itunesCategory = $node->addChild('category', null, RssFeed::ITUNES_NAMESPACE);
|
||||
$itunesCategory->addAttribute(
|
||||
'text',
|
||||
$category->parent !== null
|
||||
$category->parent instanceof Category
|
||||
? $category->parent->apple_category
|
||||
: $category->apple_category,
|
||||
);
|
||||
|
||||
if ($category->parent !== null) {
|
||||
$itunesCategoryChild = $itunesCategory->addChild('category', null, $itunesNamespace);
|
||||
if ($category->parent instanceof Category) {
|
||||
$itunesCategoryChild = $itunesCategory->addChild('category', null, RssFeed::ITUNES_NAMESPACE);
|
||||
$itunesCategoryChild->addAttribute('text', $category->apple_category);
|
||||
$node->addChild('category', $category->parent->apple_category);
|
||||
}
|
||||
|
|
@ -437,75 +436,3 @@ if (! function_exists('add_category_tag')) {
|
|||
$node->addChild('category', $category->apple_category);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('rss_to_array')) {
|
||||
/**
|
||||
* Converts XML to array
|
||||
*
|
||||
* FIXME: param should be SimpleRSSElement
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
function rss_to_array(SimpleXMLElement $rssNode): array
|
||||
{
|
||||
$nameSpaces = [
|
||||
'',
|
||||
'http://www.itunes.com/dtds/podcast-1.0.dtd',
|
||||
'https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md',
|
||||
];
|
||||
$arrayNode = [];
|
||||
$arrayNode['name'] = $rssNode->getName();
|
||||
$arrayNode['namespace'] = $rssNode->getNamespaces(false);
|
||||
foreach ($rssNode->attributes() as $key => $value) {
|
||||
$arrayNode['attributes'][$key] = (string) $value;
|
||||
}
|
||||
|
||||
$textcontent = trim((string) $rssNode);
|
||||
if (strlen($textcontent) > 0) {
|
||||
$arrayNode['content'] = $textcontent;
|
||||
}
|
||||
|
||||
foreach ($nameSpaces as $currentNameSpace) {
|
||||
foreach ($rssNode->children($currentNameSpace) as $childXmlNode) {
|
||||
$arrayNode['elements'][] = rss_to_array($childXmlNode);
|
||||
}
|
||||
}
|
||||
|
||||
return $arrayNode;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('array_to_rss')) {
|
||||
/**
|
||||
* Inserts array (converted to XML node) in XML node
|
||||
*
|
||||
* @param array<string, mixed> $arrayNode
|
||||
* @param SimpleRSSElement $xmlNode The XML parent node where this arrayNode should be attached
|
||||
*/
|
||||
function array_to_rss(array $arrayNode, SimpleRSSElement &$xmlNode): SimpleRSSElement
|
||||
{
|
||||
if (array_key_exists('elements', $arrayNode)) {
|
||||
foreach ($arrayNode['elements'] as $childArrayNode) {
|
||||
$childXmlNode = $xmlNode->addChild(
|
||||
$childArrayNode['name'],
|
||||
$childArrayNode['content'] ?? null,
|
||||
$childArrayNode['namespace'] === []
|
||||
? null
|
||||
: current($childArrayNode['namespace'])
|
||||
);
|
||||
if (array_key_exists('attributes', $childArrayNode)) {
|
||||
foreach (
|
||||
$childArrayNode['attributes']
|
||||
as $attributeKey => $attributeValue
|
||||
) {
|
||||
$childXmlNode->addAttribute($attributeKey, $attributeValue);
|
||||
}
|
||||
}
|
||||
|
||||
array_to_rss($childArrayNode, $childXmlNode);
|
||||
}
|
||||
}
|
||||
|
||||
return $xmlNode;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,18 +8,19 @@ use App\Entities\EpisodeComment;
|
|||
use App\Entities\Page;
|
||||
use App\Entities\Podcast;
|
||||
use App\Entities\Post;
|
||||
use Melbahja\Seo\MetaTags;
|
||||
use App\Libraries\HtmlHead;
|
||||
use Melbahja\Seo\Schema;
|
||||
use Melbahja\Seo\Schema\Thing;
|
||||
use Modules\Fediverse\Entities\PreviewCard;
|
||||
|
||||
/**
|
||||
* @copyright 2021 Ad Aures
|
||||
* @copyright 2024 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
if (! function_exists('get_podcast_metatags')) {
|
||||
function get_podcast_metatags(Podcast $podcast, string $page): string
|
||||
if (! function_exists('set_podcast_metatags')) {
|
||||
function set_podcast_metatags(Podcast $podcast, string $page): void
|
||||
{
|
||||
$category = '';
|
||||
if ($podcast->category->parent_id !== null) {
|
||||
|
|
@ -29,28 +30,32 @@ if (! function_exists('get_podcast_metatags')) {
|
|||
$category .= $podcast->category->apple_category;
|
||||
|
||||
$schema = new Schema(
|
||||
new Thing('PodcastSeries', [
|
||||
'name' => $podcast->title,
|
||||
'headline' => $podcast->title,
|
||||
'url' => current_url(),
|
||||
'sameAs' => $podcast->link,
|
||||
'identifier' => $podcast->guid,
|
||||
'image' => $podcast->cover->feed_url,
|
||||
'description' => $podcast->description,
|
||||
'webFeed' => $podcast->feed_url,
|
||||
'accessMode' => 'auditory',
|
||||
'author' => $podcast->owner_name,
|
||||
'creator' => $podcast->owner_name,
|
||||
'publisher' => $podcast->publisher,
|
||||
'inLanguage' => $podcast->language_code,
|
||||
'genre' => $category,
|
||||
])
|
||||
new Thing(
|
||||
props: [
|
||||
'name' => $podcast->title,
|
||||
'headline' => $podcast->title,
|
||||
'url' => current_url(),
|
||||
'sameAs' => $podcast->link,
|
||||
'identifier' => $podcast->guid,
|
||||
'image' => $podcast->cover->feed_url,
|
||||
'description' => $podcast->description,
|
||||
'webFeed' => $podcast->feed_url,
|
||||
'accessMode' => 'auditory',
|
||||
'author' => $podcast->owner_name,
|
||||
'creator' => $podcast->owner_name,
|
||||
'publisher' => $podcast->publisher,
|
||||
'inLanguage' => $podcast->language_code,
|
||||
'genre' => $category,
|
||||
],
|
||||
type: 'PodcastSeries',
|
||||
),
|
||||
);
|
||||
|
||||
$metatags = new MetaTags();
|
||||
/** @var HtmlHead $head */
|
||||
$head = service('html_head');
|
||||
|
||||
$metatags
|
||||
->title($podcast->title . ' (@' . $podcast->handle . ') • ' . lang('Podcast.' . $page))
|
||||
$head
|
||||
->title(sprintf('%s (@%s) • %s', $podcast->title, $podcast->handle, lang('Podcast.' . $page)))
|
||||
->description(esc($podcast->description))
|
||||
->image((string) $podcast->cover->og_url)
|
||||
->canonical((string) current_url())
|
||||
|
|
@ -58,47 +63,51 @@ if (! function_exists('get_podcast_metatags')) {
|
|||
->og('image:height', (string) config('Images')->podcastCoverSizes['og']['height'])
|
||||
->og('locale', $podcast->language_code)
|
||||
->og('site_name', esc(service('settings')->get('App.siteName')))
|
||||
->push('link', [
|
||||
'rel' => 'alternate',
|
||||
->tag('link', null, [
|
||||
'rel' => 'alternate',
|
||||
'type' => 'application/activity+json',
|
||||
'href' => url_to('podcast-activity', esc($podcast->handle)),
|
||||
]);
|
||||
|
||||
if ($podcast->payment_pointer) {
|
||||
$metatags->meta('monetization', $podcast->payment_pointer);
|
||||
}
|
||||
|
||||
return '<link type="application/rss+xml" rel="alternate" title="' . esc(
|
||||
$podcast->title
|
||||
) . '" href="' . $podcast->feed_url . '" />' . PHP_EOL . $metatags->__toString() . PHP_EOL . $schema->__toString();
|
||||
])->appendRawContent('<link type="application/rss+xml" rel="alternate" title="' . esc(
|
||||
$podcast->title,
|
||||
) . '" href="' . $podcast->feed_url . '" />' . $schema);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_episode_metatags')) {
|
||||
function get_episode_metatags(Episode $episode): string
|
||||
if (! function_exists('set_episode_metatags')) {
|
||||
function set_episode_metatags(Episode $episode): void
|
||||
{
|
||||
$schema = new Schema(
|
||||
new Thing('PodcastEpisode', [
|
||||
'url' => url_to('episode', esc($episode->podcast->handle), $episode->slug),
|
||||
'name' => $episode->title,
|
||||
'image' => $episode->cover->feed_url,
|
||||
'description' => $episode->description,
|
||||
'datePublished' => $episode->published_at->format(DATE_ISO8601),
|
||||
'timeRequired' => iso8601_duration($episode->audio->duration),
|
||||
'duration' => iso8601_duration($episode->audio->duration),
|
||||
'associatedMedia' => new Thing('MediaObject', [
|
||||
'contentUrl' => $episode->audio->file_url,
|
||||
]),
|
||||
'partOfSeries' => new Thing('PodcastSeries', [
|
||||
'name' => $episode->podcast->title,
|
||||
'url' => $episode->podcast->link,
|
||||
]),
|
||||
])
|
||||
new Thing(
|
||||
props: [
|
||||
'url' => url_to('episode', esc($episode->podcast->handle), $episode->slug),
|
||||
'name' => $episode->title,
|
||||
'image' => $episode->cover->feed_url,
|
||||
'description' => $episode->description,
|
||||
'datePublished' => $episode->published_at->format(DATE_ATOM),
|
||||
'timeRequired' => iso8601_duration($episode->audio->duration),
|
||||
'duration' => iso8601_duration($episode->audio->duration),
|
||||
'associatedMedia' => new Thing(
|
||||
props: [
|
||||
'contentUrl' => $episode->audio_url,
|
||||
],
|
||||
type: 'MediaObject',
|
||||
),
|
||||
'partOfSeries' => new Thing(
|
||||
props: [
|
||||
'name' => $episode->podcast->title,
|
||||
'url' => $episode->podcast->link,
|
||||
],
|
||||
type: 'PodcastSeries',
|
||||
),
|
||||
],
|
||||
type: 'PodcastEpisode',
|
||||
),
|
||||
);
|
||||
|
||||
$metatags = new MetaTags();
|
||||
/** @var HtmlHead $head */
|
||||
$head = service('html_head');
|
||||
|
||||
$metatags
|
||||
$head
|
||||
->title($episode->title)
|
||||
->description(esc($episode->description))
|
||||
->image((string) $episode->cover->og_url, 'player')
|
||||
|
|
@ -109,68 +118,83 @@ if (! function_exists('get_episode_metatags')) {
|
|||
->og('locale', $episode->podcast->language_code)
|
||||
->og('audio', $episode->audio_opengraph_url)
|
||||
->og('audio:type', $episode->audio->file_mimetype)
|
||||
->meta('article:published_time', $episode->published_at->format(DATE_ISO8601))
|
||||
->meta('article:modified_time', $episode->updated_at->format(DATE_ISO8601))
|
||||
->meta('article:published_time', $episode->published_at->format(DATE_ATOM))
|
||||
->meta('article:modified_time', $episode->updated_at->format(DATE_ATOM))
|
||||
->twitter('audio:partner', $episode->podcast->publisher ?? '')
|
||||
->twitter('audio:artist_name', esc($episode->podcast->owner_name))
|
||||
->twitter('player', $episode->getEmbedUrl('light'))
|
||||
->twitter('player:width', (string) config('Embed')->width)
|
||||
->twitter('player:height', (string) config('Embed')->height)
|
||||
->push('link', [
|
||||
'rel' => 'alternate',
|
||||
->tag('link', null, [
|
||||
'rel' => 'alternate',
|
||||
'type' => 'application/activity+json',
|
||||
'href' => url_to('episode', $episode->podcast->handle, $episode->slug),
|
||||
]);
|
||||
|
||||
if ($episode->podcast->payment_pointer) {
|
||||
$metatags->meta('monetization', $episode->podcast->payment_pointer);
|
||||
}
|
||||
|
||||
return $metatags->__toString() . PHP_EOL . '<link rel="alternate" type="application/json+oembed" href="' . base_url(
|
||||
route_to('episode-oembed-json', $episode->podcast->handle, $episode->slug)
|
||||
) . '" title="' . esc(
|
||||
$episode->title
|
||||
) . ' oEmbed json" />' . PHP_EOL . '<link rel="alternate" type="text/xml+oembed" href="' . base_url(
|
||||
route_to('episode-oembed-xml', $episode->podcast->handle, $episode->slug)
|
||||
) . '" title="' . esc($episode->title) . ' oEmbed xml" />' . PHP_EOL . $schema->__toString();
|
||||
'href' => $episode->link,
|
||||
])
|
||||
->appendRawContent('<link rel="alternate" type="application/json+oembed" href="' . base_url(
|
||||
route_to('episode-oembed-json', $episode->podcast->handle, $episode->slug),
|
||||
) . '" title="' . esc(
|
||||
$episode->title,
|
||||
) . ' oEmbed json" />' . '<link rel="alternate" type="text/xml+oembed" href="' . base_url(
|
||||
route_to('episode-oembed-xml', $episode->podcast->handle, $episode->slug),
|
||||
) . '" title="' . esc($episode->title) . ' oEmbed xml" />' . $schema);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_post_metatags')) {
|
||||
function get_post_metatags(Post $post): string
|
||||
if (! function_exists('set_post_metatags')) {
|
||||
function set_post_metatags(Post $post): void
|
||||
{
|
||||
$socialMediaPosting = new Thing('SocialMediaPosting', [
|
||||
'@id' => url_to('post', esc($post->actor->username), $post->id),
|
||||
'datePublished' => $post->published_at->format(DATE_ISO8601),
|
||||
'author' => new Thing('Person', [
|
||||
'name' => $post->actor->display_name,
|
||||
'url' => $post->actor->uri,
|
||||
]),
|
||||
'text' => $post->message,
|
||||
]);
|
||||
$socialMediaPosting = new Thing(
|
||||
props: [
|
||||
'@id' => url_to('post', esc($post->actor->username), $post->id),
|
||||
'datePublished' => $post->published_at->format(DATE_ATOM),
|
||||
'author' => new Thing(
|
||||
props: [
|
||||
'name' => $post->actor->display_name,
|
||||
'url' => $post->actor->uri,
|
||||
],
|
||||
type: 'Person',
|
||||
),
|
||||
'text' => $post->message,
|
||||
],
|
||||
type: 'SocialMediaPosting',
|
||||
);
|
||||
|
||||
if ($post->episode_id !== null) {
|
||||
$socialMediaPosting->__set('sharedContent', new Thing('Audio', [
|
||||
'headline' => $post->episode->title,
|
||||
'url' => $post->episode->link,
|
||||
'author' => new Thing('Person', [
|
||||
'name' => $post->episode->podcast->owner_name,
|
||||
]),
|
||||
]));
|
||||
} elseif ($post->preview_card !== null) {
|
||||
$socialMediaPosting->__set('sharedContent', new Thing('WebPage', [
|
||||
'headline' => $post->preview_card->title,
|
||||
'url' => $post->preview_card->url,
|
||||
'author' => new Thing('Person', [
|
||||
'name' => $post->preview_card->author_name,
|
||||
]),
|
||||
]));
|
||||
$socialMediaPosting->__set('sharedContent', new Thing(
|
||||
props: [
|
||||
'headline' => $post->episode->title,
|
||||
'url' => $post->episode->link,
|
||||
'author' => new Thing(
|
||||
props: [
|
||||
'name' => $post->episode->podcast->owner_name,
|
||||
],
|
||||
type: 'Person',
|
||||
),
|
||||
],
|
||||
type: 'Audio',
|
||||
));
|
||||
} elseif ($post->preview_card instanceof PreviewCard) {
|
||||
$socialMediaPosting->__set('sharedContent', new Thing(
|
||||
props: [
|
||||
'headline' => $post->preview_card->title,
|
||||
'url' => $post->preview_card->url,
|
||||
'author' => new Thing(
|
||||
props: [
|
||||
'name' => $post->preview_card->author_name,
|
||||
],
|
||||
type: 'Person',
|
||||
),
|
||||
],
|
||||
type: 'WebPage',
|
||||
));
|
||||
}
|
||||
|
||||
$schema = new Schema($socialMediaPosting);
|
||||
|
||||
$metatags = new MetaTags();
|
||||
$metatags
|
||||
/** @var HtmlHead $head */
|
||||
$head = service('html_head');
|
||||
|
||||
$head
|
||||
->title(lang('Post.title', [
|
||||
'actorDisplayName' => $post->actor->display_name,
|
||||
]))
|
||||
|
|
@ -178,65 +202,70 @@ if (! function_exists('get_post_metatags')) {
|
|||
->image($post->actor->avatar_image_url)
|
||||
->canonical((string) current_url())
|
||||
->og('site_name', esc(service('settings')->get('App.siteName')))
|
||||
->push('link', [
|
||||
'rel' => 'alternate',
|
||||
->tag('link', null, [
|
||||
'rel' => 'alternate',
|
||||
'type' => 'application/activity+json',
|
||||
'href' => url_to('post', esc($post->actor->username), $post->id),
|
||||
]);
|
||||
|
||||
return $metatags->__toString() . PHP_EOL . $schema->__toString();
|
||||
])->appendRawContent((string) $schema);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_episode_comment_metatags')) {
|
||||
function get_episode_comment_metatags(EpisodeComment $episodeComment): string
|
||||
if (! function_exists('set_episode_comment_metatags')) {
|
||||
function set_episode_comment_metatags(EpisodeComment $episodeComment): void
|
||||
{
|
||||
$schema = new Schema(new Thing('SocialMediaPosting', [
|
||||
'@id' => url_to(
|
||||
'episode-comment',
|
||||
esc($episodeComment->actor->username),
|
||||
$episodeComment->episode->slug,
|
||||
$episodeComment->id
|
||||
),
|
||||
'datePublished' => $episodeComment->created_at->format(DATE_ISO8601),
|
||||
'author' => new Thing('Person', [
|
||||
'name' => $episodeComment->actor->display_name,
|
||||
'url' => $episodeComment->actor->uri,
|
||||
]),
|
||||
'text' => $episodeComment->message,
|
||||
'upvoteCount' => $episodeComment->likes_count,
|
||||
]));
|
||||
$schema = new Schema(new Thing(
|
||||
props: [
|
||||
'@id' => url_to(
|
||||
'episode-comment',
|
||||
esc($episodeComment->actor->username),
|
||||
$episodeComment->episode->slug,
|
||||
$episodeComment->id,
|
||||
),
|
||||
'datePublished' => $episodeComment->created_at->format(DATE_ATOM),
|
||||
'author' => new Thing(
|
||||
props: [
|
||||
'name' => $episodeComment->actor->display_name,
|
||||
'url' => $episodeComment->actor->uri,
|
||||
],
|
||||
type: 'Person',
|
||||
),
|
||||
'text' => $episodeComment->message,
|
||||
'upvoteCount' => $episodeComment->likes_count,
|
||||
],
|
||||
type: 'SocialMediaPosting',
|
||||
));
|
||||
|
||||
$metatags = new MetaTags();
|
||||
$metatags
|
||||
/** @var HtmlHead $head */
|
||||
$head = service('html_head');
|
||||
|
||||
$head
|
||||
->title(lang('Comment.title', [
|
||||
'actorDisplayName' => $episodeComment->actor->display_name,
|
||||
'episodeTitle' => $episodeComment->episode->title,
|
||||
'episodeTitle' => $episodeComment->episode->title,
|
||||
]))
|
||||
->description($episodeComment->message)
|
||||
->image($episodeComment->actor->avatar_image_url)
|
||||
->canonical((string) current_url())
|
||||
->og('site_name', esc(service('settings')->get('App.siteName')))
|
||||
->push('link', [
|
||||
'rel' => 'alternate',
|
||||
->tag('link', null, [
|
||||
'rel' => 'alternate',
|
||||
'type' => 'application/activity+json',
|
||||
'href' => url_to(
|
||||
'episode-comment',
|
||||
$episodeComment->actor->username,
|
||||
$episodeComment->episode->slug,
|
||||
$episodeComment->id
|
||||
$episodeComment->id,
|
||||
),
|
||||
]);
|
||||
|
||||
return $metatags->__toString() . PHP_EOL . $schema->__toString();
|
||||
])->appendRawContent((string) $schema);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_follow_metatags')) {
|
||||
function get_follow_metatags(Actor $actor): string
|
||||
if (! function_exists('set_follow_metatags')) {
|
||||
function set_follow_metatags(Actor $actor): void
|
||||
{
|
||||
$metatags = new MetaTags();
|
||||
$metatags
|
||||
/** @var HtmlHead $head */
|
||||
$head = service('html_head');
|
||||
$head
|
||||
->title(lang('Podcast.followTitle', [
|
||||
'actorDisplayName' => $actor->display_name,
|
||||
]))
|
||||
|
|
@ -244,16 +273,15 @@ if (! function_exists('get_follow_metatags')) {
|
|||
->image($actor->avatar_image_url)
|
||||
->canonical((string) current_url())
|
||||
->og('site_name', esc(service('settings')->get('App.siteName')));
|
||||
|
||||
return $metatags->__toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_remote_actions_metatags')) {
|
||||
function get_remote_actions_metatags(Post $post, string $action): string
|
||||
if (! function_exists('set_remote_actions_metatags')) {
|
||||
function set_remote_actions_metatags(Post $post, string $action): void
|
||||
{
|
||||
$metatags = new MetaTags();
|
||||
$metatags
|
||||
/** @var HtmlHead $head */
|
||||
$head = service('html_head');
|
||||
$head
|
||||
->title(lang('Fediverse.' . $action . '.title', [
|
||||
'actorDisplayName' => $post->actor->display_name,
|
||||
],))
|
||||
|
|
@ -261,42 +289,40 @@ if (! function_exists('get_remote_actions_metatags')) {
|
|||
->image($post->actor->avatar_image_url)
|
||||
->canonical((string) current_url())
|
||||
->og('site_name', esc(service('settings')->get('App.siteName')));
|
||||
|
||||
return $metatags->__toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_home_metatags')) {
|
||||
function get_home_metatags(): string
|
||||
if (! function_exists('set_home_metatags')) {
|
||||
function set_home_metatags(): void
|
||||
{
|
||||
$metatags = new MetaTags();
|
||||
$metatags
|
||||
/** @var HtmlHead $head */
|
||||
$head = service('html_head');
|
||||
$head
|
||||
->title(service('settings')->get('App.siteName'))
|
||||
->description(esc(service('settings')->get('App.siteDescription')))
|
||||
->image(service('settings')->get('App.siteIcon')['512'])
|
||||
->image(get_site_icon_url('512'))
|
||||
->canonical((string) current_url())
|
||||
->og('site_name', esc(service('settings')->get('App.siteName')));
|
||||
|
||||
return $metatags->__toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_page_metatags')) {
|
||||
function get_page_metatags(Page $page): string
|
||||
if (! function_exists('set_page_metatags')) {
|
||||
function set_page_metatags(Page $page): void
|
||||
{
|
||||
$metatags = new MetaTags();
|
||||
$metatags
|
||||
/** @var HtmlHead $head */
|
||||
$head = service('html_head');
|
||||
$head
|
||||
->title(
|
||||
$page->title . service('settings')->get('App.siteTitleSeparator') . service(
|
||||
'settings'
|
||||
)->get('App.siteName')
|
||||
'settings',
|
||||
)->get('App.siteName'),
|
||||
)
|
||||
->description(esc(service('settings')->get('App.siteDescription')))
|
||||
->image(service('settings')->get('App.siteIcon')['512'])
|
||||
->image(get_site_icon_url('512'))
|
||||
->canonical((string) current_url())
|
||||
->og('site_name', esc(service('settings')->get('App.siteName')));
|
||||
|
||||
return $metatags->__toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,39 +8,6 @@ declare(strict_types=1);
|
|||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
if (! function_exists('icon')) {
|
||||
/**
|
||||
* Returns the inline svg icon
|
||||
*
|
||||
* @param string $name name of the icon file without the .svg extension
|
||||
* @param string $class to be added to the svg string
|
||||
* @param string|null $type type of icon to be added
|
||||
* @return string svg contents
|
||||
*/
|
||||
function icon(string $name, string $class = '', string $type = null): string
|
||||
{
|
||||
if ($type !== null) {
|
||||
$name = $type . '/' . $name;
|
||||
}
|
||||
|
||||
try {
|
||||
$svgContents = file_get_contents('assets/icons/' . $name . '.svg');
|
||||
} catch (Exception) {
|
||||
if ($type !== null) {
|
||||
return icon('default', $class, $type);
|
||||
}
|
||||
|
||||
return '□';
|
||||
}
|
||||
|
||||
if ($class !== '') {
|
||||
return str_replace('<svg', '<svg class="' . $class . '"', $svgContents);
|
||||
}
|
||||
|
||||
return $svgContents;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('svg')) {
|
||||
/**
|
||||
* Returns the inline svg image
|
||||
|
|
|
|||
|
|
@ -16,13 +16,14 @@ if (! function_exists('host_url')) {
|
|||
*/
|
||||
function host_url(): ?string
|
||||
{
|
||||
if (isset($_SERVER['HTTP_HOST'])) {
|
||||
$superglobals = service('superglobals');
|
||||
if ($superglobals->server('HTTP_HOST') !== null) {
|
||||
$protocol =
|
||||
(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ||
|
||||
$_SERVER['SERVER_PORT'] === 443
|
||||
($superglobals->server('HTTPS') !== null && $superglobals->server('HTTPS') !== 'off') ||
|
||||
(int) $superglobals->server('SERVER_PORT') === 443
|
||||
? 'https://'
|
||||
: 'http://';
|
||||
return $protocol . $_SERVER['HTTP_HOST'] . '/';
|
||||
return $protocol . $superglobals->server('HTTP_HOST') . '/';
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
@ -40,7 +41,9 @@ if (! function_exists('current_domain')) {
|
|||
*/
|
||||
function current_domain(): string
|
||||
{
|
||||
/** @var URI $uri */
|
||||
$uri = current_url(true);
|
||||
|
||||
return $uri->getHost() . ($uri->getPort() ? ':' . $uri->getPort() : '');
|
||||
}
|
||||
}
|
||||
|
|
@ -74,7 +77,7 @@ if (! function_exists('extract_params_from_episode_uri')) {
|
|||
|
||||
return [
|
||||
'podcastHandle' => $matches['podcastHandle'],
|
||||
'episodeSlug' => $matches['episodeSlug'],
|
||||
'episodeSlug' => $matches['episodeSlug'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@
|
|||
+ fr/***
|
||||
+ pl/***
|
||||
+ de/***
|
||||
+ pt-BR/***
|
||||
+ nn-NO/***
|
||||
+ pt-br/***
|
||||
+ nn-no/***
|
||||
+ es/***
|
||||
+ zh-Hans/***
|
||||
+ zh-hans/***
|
||||
+ ca/***
|
||||
+ br/***
|
||||
+ sr-latn/***
|
||||
- **
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ return [
|
|||
'back_to_episodes' => 'العودة إلى حلقات {podcast}',
|
||||
'comments' => 'التعليقات',
|
||||
'activity' => 'النشاط',
|
||||
'chapters' => 'Chapters',
|
||||
'transcript' => 'Transcript',
|
||||
'description' => 'وصف الحلقة',
|
||||
'number_of_comments' => '{numberOfComments, plural,
|
||||
one {# comment}
|
||||
|
|
@ -30,4 +32,19 @@ return [
|
|||
}',
|
||||
'all_podcast_episodes' => 'كافة حلقات البودكاست',
|
||||
'back_to_podcast' => 'العودة إلى البودكاست',
|
||||
'preview' => [
|
||||
'title' => 'Preview',
|
||||
'not_published' => 'Not published',
|
||||
'text' => '{publication_status, select,
|
||||
published {This episode is not yet published.}
|
||||
scheduled {This episode is scheduled for publication on {publication_date}.}
|
||||
with_podcast {This episode will be published at the same time as the podcast.}
|
||||
other {This episode is not yet published.}
|
||||
}',
|
||||
'publish' => 'Publish',
|
||||
'publish_edit' => 'Edit publication',
|
||||
],
|
||||
'no_chapters' => 'No chapters are available for this episode.',
|
||||
'download_transcript' => 'Download transcript ({extension})',
|
||||
'no_transcript' => 'No transcript available for this episode.',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ return [
|
|||
one {# post}
|
||||
other {# posts}
|
||||
}',
|
||||
'links' => 'Links',
|
||||
'activity' => 'النشاط',
|
||||
'episodes' => 'الحلقات',
|
||||
'episodes_title' => 'حلقات {podcastTitle}',
|
||||
|
|
@ -50,4 +51,5 @@ return [
|
|||
other {# persons}
|
||||
}',
|
||||
'persons_list' => 'أشخاص',
|
||||
'castopod_website' => 'Castopod (website)',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -19,18 +19,16 @@ return [
|
|||
],
|
||||
'likes' => '{numberOfLikes, plural,
|
||||
one {# muiañ-karet}
|
||||
2 {# vuiañ-karet}
|
||||
22 {# vuiañ-karet}
|
||||
32 {# vuiañ-karet}
|
||||
42 {# vuiañ-karet}
|
||||
52 {# vuiañ-karet}
|
||||
62 {# vuiañ-karet}
|
||||
82 {# vuiañ-karet}
|
||||
two {# vuiañ-karet}
|
||||
few {# muiañ-karet}
|
||||
many {# muiañ-karet}
|
||||
other {# muiañ-karet}
|
||||
}',
|
||||
'replies' => '{numberOfReplies, plural,
|
||||
0 {respont ebet}
|
||||
one {# respont}
|
||||
two {# respont}
|
||||
few {# respont}
|
||||
many {# respont}
|
||||
other {# respont}
|
||||
}',
|
||||
'like' => 'Muiañ-karet',
|
||||
|
|
|
|||
|
|
@ -16,27 +16,41 @@ return [
|
|||
'season_episode' => 'Koulzad {seasonNumber} rann {episodeNumber}',
|
||||
'season_episode_abbr' => 'K{seasonNumber}:R{episodeNumber}',
|
||||
'persons' => '{personsCount, plural,
|
||||
0 {den ebet}
|
||||
one {# den}
|
||||
two {# zen}
|
||||
few {# den}
|
||||
many {# den}
|
||||
other {# den}
|
||||
22 {# zen}
|
||||
32 {# zen}
|
||||
42 {# zen}
|
||||
52 {# zen}
|
||||
62 {# zen}
|
||||
82 {# zen}
|
||||
}',
|
||||
'persons_list' => 'Emellerien·ezed',
|
||||
'back_to_episodes' => 'Mont da rannoù {podcast}',
|
||||
'comments' => 'Evezhiadennoù',
|
||||
'activity' => 'Oberiantiz',
|
||||
'chapters' => 'Chabistroù',
|
||||
'transcript' => 'Transcript',
|
||||
'description' => 'Deskrivadur ar rann',
|
||||
'number_of_comments' => '{numberOfComments, plural,
|
||||
0 {evezhiadenn ebet}
|
||||
one {# evezhiadenn}
|
||||
two {# evezhiadenn}
|
||||
few {# evezhiadenn}
|
||||
many {# evezhiadenn}
|
||||
other {# evezhiadenn}
|
||||
}',
|
||||
'all_podcast_episodes' => 'Holl rannoù ar podkast',
|
||||
'back_to_podcast' => 'Mont d\'ar podkast en-dro',
|
||||
'preview' => [
|
||||
'title' => 'Rakwel',
|
||||
'not_published' => 'Diembann',
|
||||
'text' => '{publication_status, select,
|
||||
published {N\'eo ket bet embannet ar rann-mañ c\'hoazh.}
|
||||
scheduled {Raktreset eo an embann a-benn an/ar {publication_date}.}
|
||||
with_podcast {Ar rann-mañ a vo embannet war un dro gant ar podkast.}
|
||||
other {N\'eo ket bet embannet ar rann-mañ c\'hoazh.}
|
||||
}',
|
||||
'publish' => 'Embann',
|
||||
'publish_edit' => 'Kemmañ an embannadur',
|
||||
],
|
||||
'no_chapters' => 'N\'eus chabistr ebet evit ar rann.',
|
||||
'download_transcript' => 'Download transcript ({extension})',
|
||||
'no_transcript' => 'No transcript available for this episode.',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -9,108 +9,49 @@ declare(strict_types=1);
|
|||
*/
|
||||
|
||||
return [
|
||||
'feed' => 'Lanv RSS ar podkast',
|
||||
'feed' => 'Gwazh RSS ar podkast',
|
||||
'season' => 'Koulzad {seasonNumber}',
|
||||
'list_of_episodes_year' => 'Rannoù {year} ({episodeCount})',
|
||||
'list_of_episodes_season' =>
|
||||
'Rannoù koulzad {seasonNumber} ({episodeCount})',
|
||||
'no_episode' => 'N\'eo bet kavet rann ebet!',
|
||||
'follow' => 'Heuliañ',
|
||||
'followTitle' => 'Heuliañ {actorDisplayName} war ar c\'hevrebed!',
|
||||
'followTitle' => 'Heuliañ {actorDisplayName} war ar fediverse!',
|
||||
'followers' => '{numberOfFollowers, plural,
|
||||
0 {heulier·ez ebet}
|
||||
one {# heulier·ez}
|
||||
two {# heulier·ez}
|
||||
few {# heulier·ez}
|
||||
many {# heulier·ez}
|
||||
other {# heulier·ez}
|
||||
}',
|
||||
'posts' => '{numberOfPosts, plural,
|
||||
0 {kemennadenn ebet}
|
||||
1 {# gemennadenn}
|
||||
2 {# gemennadenn}
|
||||
3 {# c\'hemennadenn}
|
||||
4 {# c\'hemennadenn}
|
||||
9 {# c\'hemennadenn}
|
||||
one {# gemennadenn}
|
||||
two {# gemennadenn}
|
||||
few {# c\'hemennadenn}
|
||||
many {# kemennadenn}
|
||||
other {# kemennadenn}
|
||||
21 {# gemennadenn}
|
||||
22 {# gemennadenn}
|
||||
23 {# c\'hemennadenn}
|
||||
24 {# c\'hemennadenn}
|
||||
29 {# c\'hemennadenn}
|
||||
31 {# gemennadenn}
|
||||
32 {# gemennadenn}
|
||||
33 {# c\'hemennadenn}
|
||||
34 {# c\'hemennadenn}
|
||||
39 {# c\'hemennadenn}
|
||||
41 {# gemennadenn}
|
||||
42 {# gemennadenn}
|
||||
43 {# c\'hemennadenn}
|
||||
44 {# c\'hemennadenn}
|
||||
49 {# c\'hemennadenn}
|
||||
51 {# gemennadenn}
|
||||
52 {# gemennadenn}
|
||||
53 {# c\'hemennadenn}
|
||||
54 {# c\'hemennadenn}
|
||||
59 {# c\'hemennadenn}
|
||||
61 {# gemennadenn}
|
||||
62 {# gemennadenn}
|
||||
63 {# c\'hemennadenn}
|
||||
64 {# c\'hemennadenn}
|
||||
69 {# c\'hemennadenn}
|
||||
81 {# gemennadenn}
|
||||
82 {# gemennadenn}
|
||||
83 {# c\'hemennadenn}
|
||||
84 {# c\'hemennadenn}
|
||||
89 {# c\'hemennadenn}
|
||||
}',
|
||||
'activity' => 'Oberiantiz',
|
||||
'links' => 'Liammoù',
|
||||
'activity' => 'Obererezh',
|
||||
'episodes' => 'Rannoù',
|
||||
'episodes_title' => 'Rannoù {podcastTitle}',
|
||||
'about' => 'A-zivout',
|
||||
'stats' => [
|
||||
'title' => 'Stadegoù',
|
||||
'number_of_seasons' => '{0, plural,
|
||||
0 {koulzad ebet}
|
||||
1 {# c\'houlzad}
|
||||
2 {# goulzad}
|
||||
3 {# c\'houlzad}
|
||||
4 {# c\'houlzad}
|
||||
9 {# c\'houlzad}
|
||||
other {# koulzad}
|
||||
21 {# c\'houlzad}
|
||||
22 {# goulzad}
|
||||
23 {# c\'houlzad}
|
||||
24 {# c\'houlzad}
|
||||
29 {# c\'houlzad}
|
||||
31 {# c\'houlzad}
|
||||
32 {# goulzad}
|
||||
33 {# c\'houlzad}
|
||||
34 {# c\'houlzad}
|
||||
39 {# c\'houlzad}
|
||||
41 {# c\'houlzad}
|
||||
42 {# goulzad}
|
||||
43 {# c\'houlzad}
|
||||
44 {# c\'houlzad}
|
||||
49 {# c\'houlzad}
|
||||
51 {# c\'houlzad}
|
||||
52 {# goulzad}
|
||||
53 {# c\'houlzad}
|
||||
54 {# c\'houlzad}
|
||||
59 {# c\'houlzad}
|
||||
61 {# c\'houlzad}
|
||||
62 {# goulzad}
|
||||
63 {# c\'houlzad}
|
||||
64 {# c\'houlzad}
|
||||
69 {# c\'houlzad}
|
||||
81 {# c\'houlzad}
|
||||
82 {# goulzad}
|
||||
83 {# c\'houlzad}
|
||||
84 {# c\'houlzad}
|
||||
89 {# c\'houlzad}
|
||||
}',
|
||||
one {# c\'houlzad}
|
||||
two {# goulzad}
|
||||
few {# c\'houlzad}
|
||||
many {# koulzad}
|
||||
other {# koulzad}
|
||||
}',
|
||||
'number_of_episodes' => '{0, plural,
|
||||
0 {rann ebet}
|
||||
one {# rann}
|
||||
other {# rann}
|
||||
}',
|
||||
one {# rann}
|
||||
two {# rann}
|
||||
few {# rann}
|
||||
many {# rann}
|
||||
other {# rann}
|
||||
}',
|
||||
'first_published_at' => 'Embannet eo bet ar rann gentañ d\'ar/d\'an {0, date, medium}',
|
||||
],
|
||||
'sponsor' => 'Harpit',
|
||||
|
|
@ -118,16 +59,12 @@ return [
|
|||
'find_on' => 'Kavit {podcastTitle} war',
|
||||
'listen_on' => 'Selaouit war',
|
||||
'persons' => '{personsCount, plural,
|
||||
0 {den ebet}
|
||||
one {# den}
|
||||
two {# zen}
|
||||
few {# den}
|
||||
many {# den}
|
||||
other {# den}
|
||||
22 {# zen}
|
||||
32 {# zen}
|
||||
42 {# zen}
|
||||
52 {# zen}
|
||||
62 {# zen}
|
||||
82 {# zen}
|
||||
}',
|
||||
'persons_list' => 'Emellerien·ezed',
|
||||
'castopod_website' => 'Castopod (lec\'hienn)',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ return [
|
|||
'back_to_episodes' => 'Tornar als episodis de {podcast}',
|
||||
'comments' => 'Comentaris',
|
||||
'activity' => 'Activitat',
|
||||
'chapters' => 'Chapters',
|
||||
'transcript' => 'Transcript',
|
||||
'description' => 'Descripció de l\'episodi',
|
||||
'number_of_comments' => '{numberOfComments, plural,
|
||||
one {# comentari}
|
||||
|
|
@ -30,4 +32,19 @@ return [
|
|||
}',
|
||||
'all_podcast_episodes' => 'Tots els episodis del podcast',
|
||||
'back_to_podcast' => 'Tornar al podcast',
|
||||
'preview' => [
|
||||
'title' => 'Preview',
|
||||
'not_published' => 'Not published',
|
||||
'text' => '{publication_status, select,
|
||||
published {This episode is not yet published.}
|
||||
scheduled {This episode is scheduled for publication on {publication_date}.}
|
||||
with_podcast {This episode will be published at the same time as the podcast.}
|
||||
other {This episode is not yet published.}
|
||||
}',
|
||||
'publish' => 'Publish',
|
||||
'publish_edit' => 'Edit publication',
|
||||
],
|
||||
'no_chapters' => 'No chapters are available for this episode.',
|
||||
'download_transcript' => 'Download transcript ({extension})',
|
||||
'no_transcript' => 'No transcript available for this episode.',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ return [
|
|||
one {# publicació}
|
||||
other {# publicacions}
|
||||
}',
|
||||
'links' => 'Enllaços',
|
||||
'activity' => 'Activitat',
|
||||
'episodes' => 'Episodis',
|
||||
'episodes_title' => 'Episodis de {podcastTitle}',
|
||||
|
|
@ -50,4 +51,5 @@ return [
|
|||
other {# persones}
|
||||
}',
|
||||
'persons_list' => 'Persones',
|
||||
'castopod_website' => 'Castopod (website)',
|
||||
];
|
||||
|
|
|
|||
34
app/Language/da/Comment.php
Normal file
34
app/Language/da/Comment.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'title' => "{actorDisplayName}s kommentar til {episodeTitle}",
|
||||
'back_to_comments' => 'Tilbage til kommentarer',
|
||||
'form' => [
|
||||
'episode_message_placeholder' => 'Skriv en kommentar…',
|
||||
'reply_to_placeholder' => 'Svar til @{actorUsername}',
|
||||
'submit' => 'Send',
|
||||
'submit_reply' => 'Svar',
|
||||
],
|
||||
'likes' => '{numberOfLikes, plural,
|
||||
one {# kan lide}
|
||||
other {# kan lide}
|
||||
}',
|
||||
'replies' => '{numberOfReplies, plural,
|
||||
one {# svar}
|
||||
other {# svar}
|
||||
}',
|
||||
'like' => 'Synes godt om',
|
||||
'reply' => 'Svar',
|
||||
'view_replies' => 'Se svar ({numberOfReplies})',
|
||||
'block_actor' => 'Blokér bruger @{actorUsername}',
|
||||
'block_domain' => 'Blokér domænet @{actorDomain}',
|
||||
'delete' => 'Slet kommentar',
|
||||
];
|
||||
30
app/Language/da/Common.php
Normal file
30
app/Language/da/Common.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'yes' => 'Ja',
|
||||
'no' => 'Nej',
|
||||
'cancel' => 'Annuller',
|
||||
'optional' => 'Valg',
|
||||
'close' => 'Luk',
|
||||
'home' => 'Hjem',
|
||||
'explicit' => 'Eksplicit',
|
||||
'powered_by' => 'Drevet af {castopod}',
|
||||
'go_back' => 'Gå tilbage',
|
||||
'play_episode_button' => [
|
||||
'play' => 'Afspil',
|
||||
'playing' => 'Spiller',
|
||||
],
|
||||
'read_more' => 'Læs mere',
|
||||
'read_less' => 'Læs mindre',
|
||||
'see_more' => 'Se mere',
|
||||
'see_less' => 'Se mindre',
|
||||
'legal_notice' => 'Juridiske oplysninger',
|
||||
];
|
||||
50
app/Language/da/Episode.php
Normal file
50
app/Language/da/Episode.php
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'season' => 'Sæson {seasonNumber}',
|
||||
'season_abbr' => 'S{seasonNumber}',
|
||||
'number' => 'Episode {episodeNumber}',
|
||||
'number_abbr' => 'Ep. {episodeNumber}',
|
||||
'season_episode' => 'Sæson {seasonNumber} episode {episodeNumber}',
|
||||
'season_episode_abbr' => 'S{seasonNumber}:E{episodeNumber}',
|
||||
'persons' => '{personsCount, plural,
|
||||
one {# person}
|
||||
other {# personer}
|
||||
}',
|
||||
'persons_list' => 'Personer',
|
||||
'back_to_episodes' => 'Tilbage til episoderne af {podcast}',
|
||||
'comments' => 'Kommentarer',
|
||||
'activity' => 'Aktivitet',
|
||||
'chapters' => 'Chapters',
|
||||
'transcript' => 'Transcript',
|
||||
'description' => 'Episodebeskrivelse',
|
||||
'number_of_comments' => '{numberOfComments, plural,
|
||||
one {# kommentar}
|
||||
other {# kommentarer}
|
||||
}',
|
||||
'all_podcast_episodes' => 'Alle podcastepisoder',
|
||||
'back_to_podcast' => 'Tilbage til podcast',
|
||||
'preview' => [
|
||||
'title' => 'Preview',
|
||||
'not_published' => 'Not published',
|
||||
'text' => '{publication_status, select,
|
||||
published {This episode is not yet published.}
|
||||
scheduled {This episode is scheduled for publication on {publication_date}.}
|
||||
with_podcast {This episode will be published at the same time as the podcast.}
|
||||
other {This episode is not yet published.}
|
||||
}',
|
||||
'publish' => 'Publish',
|
||||
'publish_edit' => 'Edit publication',
|
||||
],
|
||||
'no_chapters' => 'No chapters are available for this episode.',
|
||||
'download_transcript' => 'Download transcript ({extension})',
|
||||
'no_transcript' => 'No transcript available for this episode.',
|
||||
];
|
||||
37
app/Language/da/Fediverse.php
Normal file
37
app/Language/da/Fediverse.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2021 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'your_handle' => 'Dit handle',
|
||||
'your_handle_hint' => 'Indtast det @brugernavn@domæne, du ønsker at handle fra.',
|
||||
'follow' => [
|
||||
'label' => 'Følg',
|
||||
'title' => 'Følg {actorDisplayName}',
|
||||
'subtitle' => 'Du er ved at følge:',
|
||||
'accountNotFound' => 'Brugeren blev ikke fundet.',
|
||||
'remoteFollowNotAllowed' => 'Det ser ud til, at kontoserveren ikke tillader eksterne følgere…',
|
||||
'submit' => 'Fortsæt for at følge',
|
||||
],
|
||||
'favourite' => [
|
||||
'title' => "Markér {actorDisplayName}s opslag som favorit",
|
||||
'subtitle' => 'Du er ved at favoritmarkere:',
|
||||
'submit' => 'Fortsæt for at favoritmarkere',
|
||||
],
|
||||
'reblog' => [
|
||||
'title' => "Del {actorDisplayName}s opslag",
|
||||
'subtitle' => 'Du er ved at dele:',
|
||||
'submit' => 'Fortsæt for at dele',
|
||||
],
|
||||
'reply' => [
|
||||
'title' => "Svar på {actorDisplayName}s opslag",
|
||||
'subtitle' => 'Du er ved at svare på:',
|
||||
'submit' => 'Fortsæt for at svare',
|
||||
],
|
||||
];
|
||||
20
app/Language/da/Home.php
Normal file
20
app/Language/da/Home.php
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'all_podcasts' => 'Alle podcasts',
|
||||
'sort_by' => 'Sortér efter',
|
||||
'sort_options' => [
|
||||
'activity' => 'Nylig aktivitet',
|
||||
'created_desc' => 'Nyeste først',
|
||||
'created_asc' => 'Ældste først',
|
||||
],
|
||||
'no_podcast' => 'Ingen podcasts fundet',
|
||||
];
|
||||
17
app/Language/da/Page.php
Normal file
17
app/Language/da/Page.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'back_to_home' => 'Tilbage til startsiden',
|
||||
'map' => [
|
||||
'title' => 'Kort',
|
||||
'description' => 'Opdag episoder om {siteName} der er placeret på et kort! Rejs gennem kortet og hør episoder der handler om bestemte steder.',
|
||||
],
|
||||
];
|
||||
55
app/Language/da/Podcast.php
Normal file
55
app/Language/da/Podcast.php
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'feed' => 'RSS podcast feed',
|
||||
'season' => 'Sæson {seasonNumber}',
|
||||
'list_of_episodes_year' => '{year} episoder ({episodeCount})',
|
||||
'list_of_episodes_season' =>
|
||||
'Sæson {seasonNumber} episoder ({episodeCount})',
|
||||
'no_episode' => 'Ingen afsnit fundet!',
|
||||
'follow' => 'Følg',
|
||||
'followTitle' => 'Følg {actorDisplayName} i fediverset!',
|
||||
'followers' => '{numberOfFollowers, plural,
|
||||
one {# følger}
|
||||
other {# føgere}
|
||||
}',
|
||||
'posts' => '{numberOfPosts, plural,
|
||||
one {# indlæg}
|
||||
other {# indlæg}
|
||||
}',
|
||||
'links' => 'Links',
|
||||
'activity' => 'Aktivitet',
|
||||
'episodes' => 'Episoder',
|
||||
'episodes_title' => 'Episoder af {podcastTitle}',
|
||||
'about' => 'Om',
|
||||
'stats' => [
|
||||
'title' => 'Statistikker',
|
||||
'number_of_seasons' => '{0, plural,
|
||||
one {# sæson}
|
||||
other {# sæsoner}
|
||||
}',
|
||||
'number_of_episodes' => '{0, plural,
|
||||
one {# episode}
|
||||
other {# episoder}
|
||||
}',
|
||||
'first_published_at' => 'Første episode offentliggjort den {0, date, medium}',
|
||||
],
|
||||
'sponsor' => 'Sponsor',
|
||||
'funding_links' => 'Finansieringslinks til {podcastTitle}',
|
||||
'find_on' => 'Find {podcastTitle} på',
|
||||
'listen_on' => 'Lyt på',
|
||||
'persons' => '{personsCount, plural,
|
||||
one {# person}
|
||||
other {# personer}
|
||||
}',
|
||||
'persons_list' => 'Personer',
|
||||
'castopod_website' => 'Castopod (website)',
|
||||
];
|
||||
40
app/Language/da/Post.php
Normal file
40
app/Language/da/Post.php
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'title' => "{actorDisplayName}'s indlæg",
|
||||
'back_to_actor_posts' => 'Tilbage til {actor} indlæg',
|
||||
'actor_shared' => '{actor} delt',
|
||||
'reply_to' => 'Svar @{actorUsername}',
|
||||
'form' => [
|
||||
'message_placeholder' => 'Skriv en besked…',
|
||||
'episode_message_placeholder' => 'Skriv en besked til episoden…',
|
||||
'episode_url_placeholder' => 'Episode URL',
|
||||
'reply_to_placeholder' => 'Svar @{actorUsername}',
|
||||
'submit' => 'Send',
|
||||
'submit_reply' => 'Svar',
|
||||
],
|
||||
'favourites' => '{numberOfFavourites, plural,
|
||||
one {# kan lide}
|
||||
other {# kan lide}
|
||||
}',
|
||||
'reblogs' => '{numberOfReblogs, plural,
|
||||
one {# del}
|
||||
other {# delinger}
|
||||
}',
|
||||
'replies' => '{numberOfReplies, plural,
|
||||
one {# svar}
|
||||
other {# svar}
|
||||
}',
|
||||
'expand' => 'Udvid opslag',
|
||||
'block_actor' => 'Blokér bruger @{actorUsername}',
|
||||
'block_domain' => 'Blokér domænet @{actorDomain}',
|
||||
'delete' => 'Slet indlæg',
|
||||
];
|
||||
|
|
@ -9,26 +9,26 @@ declare(strict_types=1);
|
|||
*/
|
||||
|
||||
return [
|
||||
'title' => "{actorDisplayName}'s Kommentar zu {episodeTitle}",
|
||||
'title' => "Kommentar von {actorDisplayName} für {episodeTitle}",
|
||||
'back_to_comments' => 'Zurück zu den Kommentaren',
|
||||
'form' => [
|
||||
'episode_message_placeholder' => 'Schreibe einen Kommentar…',
|
||||
'reply_to_placeholder' => 'Antwort zu @{actorUsername}',
|
||||
'reply_to_placeholder' => 'Antworten auf @{actorUsername}',
|
||||
'submit' => 'Senden',
|
||||
'submit_reply' => 'Antwort senden',
|
||||
],
|
||||
'likes' => '{numberOfLikes, plural,
|
||||
one {# Like}
|
||||
other {# Likes}
|
||||
one {# Beitrag}
|
||||
other {# Beiträge}
|
||||
}',
|
||||
'replies' => '{numberOfReplies, plural,
|
||||
one {# Antwort}
|
||||
other {# Antworten}
|
||||
}',
|
||||
'like' => 'Liken',
|
||||
'reply' => 'Antwort',
|
||||
'like' => 'Gefällt mir',
|
||||
'reply' => 'Antworten',
|
||||
'view_replies' => 'Antworten anzeigen ({numberOfReplies})',
|
||||
'block_actor' => '@{actorUsername} blockieren',
|
||||
'block_actor' => 'Benutzer @{actorUsername} blockieren',
|
||||
'block_domain' => 'Domain @{actorDomain} blockieren',
|
||||
'delete' => 'Kommentar löschen',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -16,14 +16,14 @@ return [
|
|||
'close' => 'Schließen',
|
||||
'home' => 'Startseite',
|
||||
'explicit' => 'Anstößig',
|
||||
'powered_by' => 'Betrieben durch {castopod}',
|
||||
'powered_by' => 'Betrieben mit {castopod}',
|
||||
'go_back' => 'Zurück',
|
||||
'play_episode_button' => [
|
||||
'play' => 'Abspielen',
|
||||
'playing' => 'Wird wiedergegeben',
|
||||
],
|
||||
'read_more' => 'Mehr lesen',
|
||||
'read_less' => 'Weniger lesen',
|
||||
'read_more' => 'Weiterlesen',
|
||||
'read_less' => 'Weniger anzeigen',
|
||||
'see_more' => 'Mehr anzeigen',
|
||||
'see_less' => 'Weniger anzeigen',
|
||||
'legal_notice' => 'Impressum',
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ declare(strict_types=1);
|
|||
return [
|
||||
'season' => 'Staffel {seasonNumber}',
|
||||
'season_abbr' => 'S{seasonNumber}',
|
||||
'number' => 'Episode {episodeNumber}',
|
||||
'number' => 'Folge {episodeNumber}',
|
||||
'number_abbr' => 'E {episodeNumber}',
|
||||
'season_episode' => 'Staffel {seasonNumber} Folge {episodeNumber}',
|
||||
'season_episode' => 'Staffel {seasonNumber} Episode {episodeNumber}',
|
||||
'season_episode_abbr' => 'S{seasonNumber}:E{episodeNumber}',
|
||||
'persons' => '{personsCount, plural,
|
||||
one {# Mitwirkender}
|
||||
|
|
@ -23,6 +23,8 @@ return [
|
|||
'back_to_episodes' => 'Zurück zu Episoden von {podcast}',
|
||||
'comments' => 'Kommentare',
|
||||
'activity' => 'Aktivitäten',
|
||||
'chapters' => 'Kapitel',
|
||||
'transcript' => 'Protokoll',
|
||||
'description' => 'Beschreibung der Episode',
|
||||
'number_of_comments' => '{numberOfComments, plural,
|
||||
one {# Kommentar}
|
||||
|
|
@ -30,4 +32,19 @@ return [
|
|||
}',
|
||||
'all_podcast_episodes' => 'Alle Podcast-Episoden',
|
||||
'back_to_podcast' => 'Zurück zum Podcast',
|
||||
'preview' => [
|
||||
'title' => 'Vorschau',
|
||||
'not_published' => 'Nicht veröffentlicht',
|
||||
'text' => '{publication_status, select,
|
||||
published {Diese Episode ist noch nicht veröffentlicht.}
|
||||
scheduled {Diese Episode ist für die Veröffentlichung geplant am {publication_date}.}
|
||||
with_podcast {Diese Episode wird zur gleichen Zeit wie der Podcast veröffentlicht.}
|
||||
other {Diese Episode ist noch nicht veröffentlicht.}
|
||||
}',
|
||||
'publish' => 'Veröffentlichen',
|
||||
'publish_edit' => 'Veröffentlichung bearbeiten',
|
||||
],
|
||||
'no_chapters' => 'Für diese Episode sind keine Kapitel verfügbar.',
|
||||
'download_transcript' => 'Protokoll herunterladen ({extension})',
|
||||
'no_transcript' => 'Für diese Episode ist kein Protokoll verfügbar.',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ declare(strict_types=1);
|
|||
|
||||
return [
|
||||
'your_handle' => 'Handle',
|
||||
'your_handle_hint' => '@name@domain eingeben, womit Sie agieren möchten.',
|
||||
'your_handle_hint' => '@name@domain eingeben, in deren Name Sie agieren möchten.',
|
||||
'follow' => [
|
||||
'label' => 'Folge',
|
||||
'title' => 'Folge {actorDisplayName}',
|
||||
|
|
@ -20,18 +20,18 @@ return [
|
|||
'submit' => 'Weiter zum Folgen',
|
||||
],
|
||||
'favourite' => [
|
||||
'title' => "{actorDisplayName}'s Beitrag favorisieren",
|
||||
'title' => "Beitrag von {actorDisplayName} favorisieren",
|
||||
'subtitle' => 'Sie werden favorisieren:',
|
||||
'submit' => 'Weiter zum Favorisieren',
|
||||
'submit' => 'Zum Favorisieren fortfahren',
|
||||
],
|
||||
'reblog' => [
|
||||
'title' => "{actorDisplayName}'s Beitrag teilen",
|
||||
'title' => "Den Beitrag von {actorDisplayName} teilen",
|
||||
'subtitle' => 'Sie werden teilen:',
|
||||
'submit' => 'Weiter zum Teilen',
|
||||
],
|
||||
'reply' => [
|
||||
'title' => "Auf {actorDisplayName}'s Beitrag antworten",
|
||||
'subtitle' => 'Sie werden antworten auf:',
|
||||
'title' => "Auf den Beitrag von {actorDisplayName} antworten",
|
||||
'subtitle' => 'Sie antworten auf:',
|
||||
'submit' => 'Weiter zum Antworten',
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -16,5 +16,5 @@ return [
|
|||
'created_desc' => 'Neueste zuerst',
|
||||
'created_asc' => 'Älteste zuerst',
|
||||
],
|
||||
'no_podcast' => 'Keinen Podcast gefunden',
|
||||
'no_podcast' => 'Keine Podcasts gefunden',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -11,29 +11,30 @@ declare(strict_types=1);
|
|||
return [
|
||||
'feed' => 'RSS-Feed',
|
||||
'season' => 'Staffel {seasonNumber}',
|
||||
'list_of_episodes_year' => '{year} Folgen ({episodeCount})',
|
||||
'list_of_episodes_year' => '({episodeCount}) Episoden in {year}',
|
||||
'list_of_episodes_season' =>
|
||||
'Staffel {seasonNumber} Folgen ({episodeCount})',
|
||||
'no_episode' => 'Keine Folge gefunden',
|
||||
'Staffel {seasonNumber} Episode ({episodeCount})',
|
||||
'no_episode' => 'Keine Episode gefunden!',
|
||||
'follow' => 'Folgen',
|
||||
'followTitle' => 'Folge {actorDisplayName} im Fediversum',
|
||||
'followTitle' => 'Folge {actorDisplayName} im Fediversum!',
|
||||
'followers' => '{numberOfFollowers, plural,
|
||||
one {# follower}
|
||||
other {# followers}
|
||||
one {# Follower}
|
||||
other {# Follower}
|
||||
}',
|
||||
'posts' => '{numberOfPosts, plural,
|
||||
one {# post}
|
||||
other {# posts}
|
||||
one {# Beitrag}
|
||||
other {# Beiträge}
|
||||
}',
|
||||
'links' => 'Links',
|
||||
'activity' => 'Aktivitäten',
|
||||
'episodes' => 'Episoden',
|
||||
'episodes_title' => 'Folgen von {podcastTitle}',
|
||||
'episodes_title' => 'Episoden von {podcastTitle}',
|
||||
'about' => 'Über',
|
||||
'stats' => [
|
||||
'title' => 'Statistiken',
|
||||
'number_of_seasons' => '{0, plural,
|
||||
one {# season}
|
||||
other {# seasons}
|
||||
one {# Staffel}
|
||||
other {# Staffeln}
|
||||
}',
|
||||
'number_of_episodes' => '{0, plural,
|
||||
one {# Episode}
|
||||
|
|
@ -50,4 +51,5 @@ return [
|
|||
other {# Personen}
|
||||
}',
|
||||
'persons_list' => 'Mitwirkende',
|
||||
'castopod_website' => 'Castopod (Webseite)',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@ declare(strict_types=1);
|
|||
*/
|
||||
|
||||
return [
|
||||
'title' => "{actorDisplayName}'s Beitrag",
|
||||
'back_to_actor_posts' => 'Zurück zu {actor}\'s Beiträge',
|
||||
'title' => "Beitrag von {actorDisplayName}",
|
||||
'back_to_actor_posts' => 'Zurück zu den Beiträgen von {actor}',
|
||||
'actor_shared' => '{actor} teilte',
|
||||
'reply_to' => 'Antorten auf @{actorUsername}',
|
||||
'reply_to' => 'Antworten auf @{actorUsername}',
|
||||
'form' => [
|
||||
'message_placeholder' => 'Scheibe eine Nachricht…',
|
||||
'episode_message_placeholder' => 'Schreibe eine Nachricht für die Episode…',
|
||||
'episode_message_placeholder' => 'Schreibe eine Nachricht für die Folge…',
|
||||
'episode_url_placeholder' => 'URL der Episode',
|
||||
'reply_to_placeholder' => 'Antworten auf @{actorUsername}',
|
||||
'submit' => 'Senden',
|
||||
|
|
|
|||
|
|
@ -26,5 +26,5 @@ return [
|
|||
'read_less' => 'Διαβάστε λιγότερα',
|
||||
'see_more' => 'Εμφάνιση περισσότερων',
|
||||
'see_less' => 'Δείτε λιγότερα',
|
||||
'legal_notice' => 'Legal notice',
|
||||
'legal_notice' => 'Νομικές επισημάνσεις',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ return [
|
|||
'back_to_episodes' => 'Επιστροφή στα επεισόδια του {podcast}',
|
||||
'comments' => 'Σχόλια',
|
||||
'activity' => 'Δραστηριότητα',
|
||||
'chapters' => 'Chapters',
|
||||
'transcript' => 'Transcript',
|
||||
'description' => 'Περιγραφή επεισοδίου',
|
||||
'number_of_comments' => '{numberOfComments, plural,
|
||||
one {# σχόλιο}
|
||||
|
|
@ -30,4 +32,19 @@ return [
|
|||
}',
|
||||
'all_podcast_episodes' => 'Όλα τα επεισόδια του podcast',
|
||||
'back_to_podcast' => 'Μετάβαση πίσω στο podcast',
|
||||
'preview' => [
|
||||
'title' => 'Preview',
|
||||
'not_published' => 'Not published',
|
||||
'text' => '{publication_status, select,
|
||||
published {This episode is not yet published.}
|
||||
scheduled {This episode is scheduled for publication on {publication_date}.}
|
||||
with_podcast {This episode will be published at the same time as the podcast.}
|
||||
other {This episode is not yet published.}
|
||||
}',
|
||||
'publish' => 'Publish',
|
||||
'publish_edit' => 'Edit publication',
|
||||
],
|
||||
'no_chapters' => 'No chapters are available for this episode.',
|
||||
'download_transcript' => 'Download transcript ({extension})',
|
||||
'no_transcript' => 'No transcript available for this episode.',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ return [
|
|||
one {# δημοσίευση}
|
||||
other {# δημοσιεύσεις}
|
||||
}',
|
||||
'links' => 'Links',
|
||||
'activity' => 'Δραστηριότητα',
|
||||
'episodes' => 'Επεισόδια',
|
||||
'episodes_title' => 'Επεισόδια του {podcastTitle}',
|
||||
|
|
@ -50,4 +51,5 @@ return [
|
|||
other {# άτομα}
|
||||
}',
|
||||
'persons_list' => 'Άτομα',
|
||||
'castopod_website' => 'Castopod (website)',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ return [
|
|||
'back_to_episodes' => 'Back to episodes of {podcast}',
|
||||
'comments' => 'Comments',
|
||||
'activity' => 'Activity',
|
||||
'chapters' => 'Chapters',
|
||||
'transcript' => 'Transcript',
|
||||
'description' => 'Episode description',
|
||||
'number_of_comments' => '{numberOfComments, plural,
|
||||
one {# comment}
|
||||
|
|
@ -30,4 +32,19 @@ return [
|
|||
}',
|
||||
'all_podcast_episodes' => 'All podcast episodes',
|
||||
'back_to_podcast' => 'Go back to podcast',
|
||||
'preview' => [
|
||||
'title' => 'Preview',
|
||||
'not_published' => 'Not published',
|
||||
'text' => '{publication_status, select,
|
||||
published {This episode is not yet published.}
|
||||
scheduled {This episode is scheduled for publication on {publication_date}.}
|
||||
with_podcast {This episode will be published at the same time as the podcast.}
|
||||
other {This episode is not yet published.}
|
||||
}',
|
||||
'publish' => 'Publish',
|
||||
'publish_edit' => 'Edit publication',
|
||||
],
|
||||
'no_chapters' => 'No chapters are available for this episode.',
|
||||
'download_transcript' => 'Download transcript ({extension})',
|
||||
'no_transcript' => 'No transcript available for this episode.',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ declare(strict_types=1);
|
|||
*/
|
||||
|
||||
return [
|
||||
'your_handle' => 'Your handle',
|
||||
'your_handle' => 'Your Fediverse handle',
|
||||
'your_handle_hint' => 'Enter the @username@domain you want to act from.',
|
||||
'follow' => [
|
||||
'label' => 'Follow',
|
||||
|
|
|
|||
|
|
@ -17,14 +17,15 @@ return [
|
|||
'no_episode' => 'No episode found!',
|
||||
'follow' => 'Follow',
|
||||
'followTitle' => 'Follow {actorDisplayName} on the fediverse!',
|
||||
'followers' => '{numberOfFollowers, plural,
|
||||
one {# follower}
|
||||
other {# followers}
|
||||
'fediverseFollowers' => '{numberOfFollowers, plural,
|
||||
one {# Fediverse follower}
|
||||
other {# Fediverse followers}
|
||||
}',
|
||||
'posts' => '{numberOfPosts, plural,
|
||||
one {# post}
|
||||
other {# posts}
|
||||
}',
|
||||
'links' => 'Links',
|
||||
'activity' => 'Activity',
|
||||
'episodes' => 'Episodes',
|
||||
'episodes_title' => 'Episodes of {podcastTitle}',
|
||||
|
|
@ -41,7 +42,7 @@ return [
|
|||
}',
|
||||
'first_published_at' => 'First episode published on {0, date, medium}',
|
||||
],
|
||||
'sponsor' => 'Sponsor',
|
||||
'funding' => 'Funding',
|
||||
'funding_links' => 'Funding links for {podcastTitle}',
|
||||
'find_on' => 'Find {podcastTitle} on',
|
||||
'listen_on' => 'Listen on',
|
||||
|
|
@ -50,4 +51,5 @@ return [
|
|||
other {# persons}
|
||||
}',
|
||||
'persons_list' => 'Persons',
|
||||
'links_mainpage' => 'Podcast (main page)',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -37,4 +37,7 @@ return [
|
|||
'block_actor' => 'Block user @{actorUsername}',
|
||||
'block_domain' => 'Block domain @{actorDomain}',
|
||||
'delete' => 'Delete post',
|
||||
'is_public' => 'Post is public',
|
||||
'is_private' => 'Post is private',
|
||||
'cannot_reblog' => 'This private post cannot be shared.',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ return [
|
|||
'back_to_episodes' => 'Volver a los episodios de {podcast}',
|
||||
'comments' => 'Comentarios',
|
||||
'activity' => 'Actividad',
|
||||
'chapters' => 'Chapters',
|
||||
'transcript' => 'Transcript',
|
||||
'description' => 'Descripción del episodio',
|
||||
'number_of_comments' => '{numberOfComments, plural,
|
||||
one {# comentario}
|
||||
|
|
@ -30,4 +32,19 @@ return [
|
|||
}',
|
||||
'all_podcast_episodes' => 'Todos los episodios del podcast',
|
||||
'back_to_podcast' => 'Regresar al podcast',
|
||||
'preview' => [
|
||||
'title' => 'Preview',
|
||||
'not_published' => 'Sin publicar',
|
||||
'text' => '{publication_status, select,
|
||||
published {This episode is not yet published.}
|
||||
scheduled {This episode is scheduled for publication on {publication_date}.}
|
||||
with_podcast {This episode will be published at the same time as the podcast.}
|
||||
other {This episode is not yet published.}
|
||||
}',
|
||||
'publish' => 'Publish',
|
||||
'publish_edit' => 'Edit publication',
|
||||
],
|
||||
'no_chapters' => 'No chapters are available for this episode.',
|
||||
'download_transcript' => 'Download transcript ({extension})',
|
||||
'no_transcript' => 'No transcript available for this episode.',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ return [
|
|||
one {# publicación}
|
||||
other {# publicaciones}
|
||||
}',
|
||||
'links' => 'Links',
|
||||
'activity' => 'Actividad',
|
||||
'episodes' => 'Episodios',
|
||||
'episodes_title' => 'Episodios de {podcastTitle}',
|
||||
|
|
@ -50,4 +51,5 @@ return [
|
|||
other {# personas}
|
||||
}',
|
||||
'persons_list' => 'Personas',
|
||||
'castopod_website' => 'Castopod (website)',
|
||||
];
|
||||
|
|
|
|||
34
app/Language/eu/Comment.php
Normal file
34
app/Language/eu/Comment.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Ad Aures
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'title' => "{actorDisplayName}'s comment for {episodeTitle}",
|
||||
'back_to_comments' => 'Back to comments',
|
||||
'form' => [
|
||||
'episode_message_placeholder' => 'Write a comment…',
|
||||
'reply_to_placeholder' => 'Reply to @{actorUsername}',
|
||||
'submit' => 'Send',
|
||||
'submit_reply' => 'Reply',
|
||||
],
|
||||
'likes' => '{numberOfLikes, plural,
|
||||
one {# like}
|
||||
other {# likes}
|
||||
}',
|
||||
'replies' => '{numberOfReplies, plural,
|
||||
one {# reply}
|
||||
other {# replies}
|
||||
}',
|
||||
'like' => 'Like',
|
||||
'reply' => 'Reply',
|
||||
'view_replies' => 'View replies ({numberOfReplies})',
|
||||
'block_actor' => 'Block user @{actorUsername}',
|
||||
'block_domain' => 'Block domain @{actorDomain}',
|
||||
'delete' => 'Delete comment',
|
||||
];
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue