mirror of
https://github.com/ad-aures/castopod.git
synced 2026-04-08 09:16:43 +02:00
Compare commits
No commits in common. "develop" and "alpha" have entirely different histories.
2997 changed files with 80308 additions and 200273 deletions
|
|
@ -1,575 +0,0 @@
|
||||||
{
|
|
||||||
"projectName": "castopod",
|
|
||||||
"projectOwner": "adaures",
|
|
||||||
"repoType": "gitlab",
|
|
||||||
"repoHost": "https://code.castopod.org",
|
|
||||||
"files": ["README.md"],
|
|
||||||
"imageSize": 100,
|
|
||||||
"commit": false,
|
|
||||||
"contributors": [
|
|
||||||
{
|
|
||||||
"login": "yassinedoghri",
|
|
||||||
"name": "Yassine Doghri",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/11021441?v=4",
|
|
||||||
"profile": "https://yassinedoghri.com",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"bug",
|
|
||||||
"doc",
|
|
||||||
"review",
|
|
||||||
"maintenance",
|
|
||||||
"content",
|
|
||||||
"design",
|
|
||||||
"a11y",
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
},
|
|
||||||
"question",
|
|
||||||
"mentoring",
|
|
||||||
"infra",
|
|
||||||
"ideas",
|
|
||||||
"projectManagement",
|
|
||||||
{
|
|
||||||
"type": "blog",
|
|
||||||
"url": "https://blog.castopod.org/author/yassinedoghri/"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "benjamin",
|
|
||||||
"name": "Benjamin Bellamy",
|
|
||||||
"avatar_url": "https://code.castopod.org/uploads/-/system/user/avatar/2/avatar.png",
|
|
||||||
"profile": "https://code.castopod.org/benjamin",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"bug",
|
|
||||||
"review",
|
|
||||||
"content",
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
},
|
|
||||||
"question",
|
|
||||||
"infra",
|
|
||||||
"ideas",
|
|
||||||
{
|
|
||||||
"type": "blog",
|
|
||||||
"url": "https://blog.castopod.org/author/benjamin-bellamy/"
|
|
||||||
},
|
|
||||||
"projectManagement",
|
|
||||||
"talk"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "ola",
|
|
||||||
"name": "Ola Hneini",
|
|
||||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
|
||||||
"profile": "https://github.com/ola-hn",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"review",
|
|
||||||
"doc",
|
|
||||||
"maintenance",
|
|
||||||
"question",
|
|
||||||
"ideas"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "rdelaage",
|
|
||||||
"name": "Romain de Laage",
|
|
||||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
|
||||||
"profile": "https://mamot.fr/@rdelaage",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"infra",
|
|
||||||
"doc",
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
},
|
|
||||||
"ideas"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "Lyonel",
|
|
||||||
"name": "Lyonel Bernard",
|
|
||||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
|
||||||
"profile": "https://twitter.com/lyonelbernard",
|
|
||||||
"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"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "ernestoacostame",
|
|
||||||
"name": "Ernesto Acosta",
|
|
||||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
|
||||||
"profile": "https://ernestoacosta.me/",
|
|
||||||
"contributions": [
|
|
||||||
"bug",
|
|
||||||
"audio",
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
},
|
|
||||||
"question",
|
|
||||||
"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"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "cecillie",
|
|
||||||
"name": "Cécile Ricordeau",
|
|
||||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
|
||||||
"profile": "https://www.cecillie.fr/",
|
|
||||||
"contributions": ["design"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "PatrykMis",
|
|
||||||
"name": "Patryk Miś",
|
|
||||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
|
||||||
"profile": "https://code.castopod.org/PatrykMis",
|
|
||||||
"contributions": [
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "mspanc",
|
|
||||||
"name": "Marcin Lewandowski",
|
|
||||||
"avatar_url": "https://secure.gravatar.com/avatar/eed8337939641eac5ad0b570bd6acf96?s=80&d=identicon",
|
|
||||||
"profile": "https://code.castopod.org/mspanc",
|
|
||||||
"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"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "patryk",
|
|
||||||
"name": "Patryk Karczmarczyk",
|
|
||||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
|
||||||
"profile": "https://code.castopod.org/patryk",
|
|
||||||
"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"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "cExplorer",
|
|
||||||
"name": "cExplorer",
|
|
||||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
|
||||||
"profile": "https://code.castopod.org/cExplorer",
|
|
||||||
"contributions": [
|
|
||||||
"bug",
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "imacrea",
|
|
||||||
"name": "ImaCrea",
|
|
||||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
|
||||||
"profile": "https://code.castopod.org/imacrea",
|
|
||||||
"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"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "rocky",
|
|
||||||
"name": "rocky III",
|
|
||||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
|
||||||
"profile": "https://code.castopod.org/rocky",
|
|
||||||
"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"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "cyrilledel",
|
|
||||||
"name": "Delhaye Cyrille",
|
|
||||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
|
||||||
"profile": "https://code.castopod.org/cyrilledel",
|
|
||||||
"contributions": ["bug", "ideas"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "otetranome",
|
|
||||||
"name": "João Leandro",
|
|
||||||
"avatar_url": "https://code.castopod.org/uploads/-/system/user/avatar/113/avatar.png",
|
|
||||||
"profile": "https://twitter.com/otetranome",
|
|
||||||
"contributions": [
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
},
|
|
||||||
"ideas"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "achouvardas",
|
|
||||||
"name": "Angelos Chouvardas",
|
|
||||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
|
||||||
"profile": "https://achouvardas.eu/",
|
|
||||||
"contributions": [
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "eivind",
|
|
||||||
"name": "Eivind",
|
|
||||||
"avatar_url": "https://mastodon.fjerland.no/system/accounts/avatars/107/769/768/295/192/222/original/e5c985fea6487dcb.jpg",
|
|
||||||
"profile": "https://mastodon.fjerland.no/@eivind",
|
|
||||||
"contributions": [
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "forght",
|
|
||||||
"name": "forght",
|
|
||||||
"avatar_url": "https://crowdin-static.downloads.crowdin.com/avatar/15073833/large/82d1e2e443a6df7edc43a7405dfeeb75_default.png",
|
|
||||||
"profile": "https://crowdin.com/profile/forght",
|
|
||||||
"contributions": [
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "glottis0q",
|
|
||||||
"name": "glottis0q",
|
|
||||||
"avatar_url": "https://crowdin-static.downloads.crowdin.com/avatar/15209934/large/8b17ef6a7399f0b82a8198f87c224195.png",
|
|
||||||
"profile": "https://crowdin.com/profile/glottis0q",
|
|
||||||
"contributions": [
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "BoFFire",
|
|
||||||
"name": "ButterflyOfFire",
|
|
||||||
"avatar_url": "https://static.mstdn.fr/static/accounts/avatars/000/065/901/original/5908e93ad5447f15.png",
|
|
||||||
"profile": "https://mstdn.fr/@ButterflyOfFire",
|
|
||||||
"contributions": [
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "lil5",
|
|
||||||
"name": "Lucian I. Last",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/17646836?v=4",
|
|
||||||
"profile": "https://github.com/lil5",
|
|
||||||
"contributions": [
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "LuuzViir",
|
|
||||||
"name": "LuuzViir",
|
|
||||||
"avatar_url": "https://crowdin-static.downloads.crowdin.com/avatar/13166188/large/d03ab0abc7ce354b210d836955cd3805_default.png",
|
|
||||||
"profile": "https://crowdin.com/profile/luuzviir",
|
|
||||||
"contributions": [
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "cthtc",
|
|
||||||
"name": "CTHTC",
|
|
||||||
"avatar_url": "https://crowdin-static.downloads.crowdin.com/avatar/15211502/large/ed0651060cb8474a9519b5168bd377c1_default.png",
|
|
||||||
"profile": "https://crowdin.com/profile/cthtc",
|
|
||||||
"contributions": [
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "retrograde",
|
|
||||||
"name": "Russian Retro",
|
|
||||||
"avatar_url": "https://crowdin-static.downloads.crowdin.com/avatar/15021651/large/b10c4057f85bf4de49c7fdf01354ecde.jpeg",
|
|
||||||
"profile": "https://crowdin.com/profile/retrograde",
|
|
||||||
"contributions": [
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "mareklach",
|
|
||||||
"name": "Marek L'ach",
|
|
||||||
"avatar_url": "https://crowdin-static.downloads.crowdin.com/avatar/13572324/large/3eeba8d569c247ace33862bf4ef4748f.jpeg",
|
|
||||||
"profile": "https://crowdin.com/profile/mareklach",
|
|
||||||
"contributions": [
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "GunChleoc",
|
|
||||||
"name": "GunChleoc",
|
|
||||||
"avatar_url": "https://crowdin-static.downloads.crowdin.com/avatar/13043878/large/3223f7b606296a8b1c92c5de39c459a2_default.png",
|
|
||||||
"profile": "https://crowdin.com/profile/gunchleoc",
|
|
||||||
"contributions": [
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "GabiSnow",
|
|
||||||
"name": "GabiSnow",
|
|
||||||
"avatar_url": "https://crowdin-static.downloads.crowdin.com/avatar/15214858/large/5b083bdf9c9e9de67cc6ee72a6c8db18_default.png",
|
|
||||||
"profile": "https://crowdin.com/profile/gabisnow",
|
|
||||||
"contributions": [
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "bendaha",
|
|
||||||
"name": "bendaha",
|
|
||||||
"avatar_url": "https://crowdin-static.downloads.crowdin.com/avatar/15331656/large/cd92450d2c20202299fb3a0075903e20_default.png",
|
|
||||||
"profile": "https://crowdin.com/profile/bendaha",
|
|
||||||
"contributions": [
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "samuelroland",
|
|
||||||
"name": "Samuel Roland",
|
|
||||||
"avatar_url": "https://crowdin-static.downloads.crowdin.com/avatar/14980053/large/3e154a37d03d6e98ae402ed3f930f4f5.png",
|
|
||||||
"profile": "https://crowdin.com/profile/samuelroland",
|
|
||||||
"contributions": [
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "dimregnier",
|
|
||||||
"name": "Dimitri Regnier",
|
|
||||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
|
||||||
"profile": "https://dimitriregnier.net/",
|
|
||||||
"contributions": ["ideas"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "irithys",
|
|
||||||
"name": "irithys",
|
|
||||||
"avatar_url": "https://crowdin-static.downloads.crowdin.com/avatar/15405614/large/3086461c47cce0a0c031925e5f943412.png",
|
|
||||||
"profile": "https://im.irithys.com/@thy",
|
|
||||||
"contributions": [
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "caos30",
|
|
||||||
"name": "Sergi",
|
|
||||||
"avatar_url": "https://castopod.org/assets/images/castopod-avatar.jpg",
|
|
||||||
"profile": "https://twitter.com/caos30",
|
|
||||||
"contributions": [
|
|
||||||
{
|
|
||||||
"type": "translation",
|
|
||||||
"url": "https://translate.castopod.org"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
####################################################
|
|
||||||
# Castopod development Docker file
|
|
||||||
####################################################
|
|
||||||
# ⚠️ NOT optimized for production
|
|
||||||
# should be used only for development purposes
|
|
||||||
#---------------------------------------------------
|
|
||||||
FROM php:8.5-fpm
|
|
||||||
|
|
||||||
LABEL maintainer="Yassine Doghri <yassine@doghri.fr>"
|
|
||||||
|
|
||||||
# Install composer
|
|
||||||
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
|
||||||
|
|
||||||
# Install server requirements
|
|
||||||
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
|
|
||||||
&& apt-get update \
|
|
||||||
&& apt-get install --yes --no-install-recommends nodejs \
|
|
||||||
# gnupg to sign commits with gpg
|
|
||||||
gnupg \
|
|
||||||
openssh-client \
|
|
||||||
# cron for scheduled tasks
|
|
||||||
cron \
|
|
||||||
# unzip used by composer
|
|
||||||
unzip \
|
|
||||||
# required libraries to install php extensions using
|
|
||||||
# https://github.com/mlocati/docker-php-extension-installer (included in php's docker image)
|
|
||||||
libicu-dev \
|
|
||||||
libpng-dev \
|
|
||||||
libwebp-dev \
|
|
||||||
libjpeg62-turbo-dev \
|
|
||||||
libfreetype6-dev \
|
|
||||||
zlib1g-dev \
|
|
||||||
libzip-dev \
|
|
||||||
# ffmpeg for video encoding
|
|
||||||
ffmpeg \
|
|
||||||
# intl for Internationalization
|
|
||||||
&& docker-php-ext-install intl \
|
|
||||||
&& docker-php-ext-install zip \
|
|
||||||
# gd for image processing
|
|
||||||
&& docker-php-ext-configure gd --with-webp --with-jpeg --with-freetype \
|
|
||||||
&& docker-php-ext-install gd \
|
|
||||||
&& docker-php-ext-install exif \
|
|
||||||
&& docker-php-ext-enable exif \
|
|
||||||
# redis extension for cache
|
|
||||||
&& pecl install -o -f redis \
|
|
||||||
&& rm -rf /tmp/pear \
|
|
||||||
&& docker-php-ext-enable redis \
|
|
||||||
# mysqli for database access
|
|
||||||
&& docker-php-ext-install mysqli \
|
|
||||||
&& docker-php-ext-enable mysqli
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
* * * * * /usr/local/bin/php /workspaces/castopod/spark tasks:run >> /dev/null 2>&1
|
|
||||||
|
|
@ -1,70 +1,41 @@
|
||||||
// For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at:
|
// 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
|
// https://github.com/microsoft/vscode-dev-containers/tree/v0.117.1/containers/docker-existing-dockerfile
|
||||||
{
|
{
|
||||||
"name": "castopod.local",
|
"name": "Castopod Host dev",
|
||||||
"dockerComposeFile": ["./docker-compose.yml"],
|
"dockerComposeFile": ["../docker-compose.yml", "./docker-compose.yml"],
|
||||||
"service": "app",
|
"service": "app",
|
||||||
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
|
"workspaceFolder": "/castopod-host",
|
||||||
"postCreateCommand": "composer install && pnpm install && pnpm run build:static && php spark migrate --all && php spark db:seed DevSeeder",
|
"postCreateCommand": "composer install && npm install && npm run build:static",
|
||||||
"postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder} && crontab .devcontainer/crontab && cron && php spark serve --host 0.0.0.0 --port ${APP_PORT:-8080}",
|
"postStartCommand": "crontab ./crontab && cron && php spark serve --host 0.0.0.0",
|
||||||
"postAttachCommand": "crontab .devcontainer/crontab && service cron reload",
|
"postAttachCommand": "crontab ./crontab && service cron reload",
|
||||||
"shutdownAction": "stopCompose",
|
"shutdownAction": "stopCompose",
|
||||||
"features": {
|
"settings": {
|
||||||
"ghcr.io/devcontainers/features/git:1": {},
|
"terminal.integrated.defaultProfile.linux": "bash",
|
||||||
"ghcr.io/guiyomh/features/vim:0": {},
|
"editor.formatOnSave": true,
|
||||||
"ghcr.io/NicoVIII/devcontainer-features/pnpm:2": {}
|
"[php]": {
|
||||||
},
|
"editor.defaultFormatter": "bmewburn.vscode-intelephense-client",
|
||||||
"customizations": {
|
"editor.formatOnSave": false
|
||||||
"vscode": {
|
},
|
||||||
"settings": {
|
"color-highlight.markerType": "dot-before",
|
||||||
"editor.formatOnSave": true,
|
"files.associations": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"*.xml.dist": "xml",
|
||||||
"[php]": {
|
"spark": "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"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"extensions": [
|
||||||
|
"mikestead.dotenv",
|
||||||
|
"bmewburn.vscode-intelephense-client",
|
||||||
|
"streetsidesoftware.code-spell-checker",
|
||||||
|
"naumovs.color-highlight",
|
||||||
|
"heybourn.headwind",
|
||||||
|
"wayou.vscode-todo-highlight",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"bradlc.vscode-tailwindcss",
|
||||||
|
"jamesbirtles.svelte-vscode",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"stylelint.vscode-stylelint",
|
||||||
|
"eamodio.gitlens",
|
||||||
|
"breezelin.phpstan",
|
||||||
|
"kasik96.latte"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,76 +1,10 @@
|
||||||
|
version: "3"
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
volumes:
|
volumes:
|
||||||
- ../..:/workspaces:cached
|
# Mounts the project folder to '/workspace'. While this file is in .devcontainer,
|
||||||
- ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
|
# mounts are relative to the first file in the list, which is a level up.
|
||||||
environment:
|
- .:/castopod-host:cached
|
||||||
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
|
|
||||||
|
|
||||||
mariadb:
|
# Overrides default command so things don't shut down after the process ends.
|
||||||
image: mariadb:10.2
|
command: /bin/sh -c "while sleep 1000; do :; done"
|
||||||
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:
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
CREATE DATABASE IF NOT EXISTS `test`;
|
|
||||||
GRANT ALL ON `test`.* TO 'castopod'@'%';
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
file_uploads = On
|
|
||||||
memory_limit = 512M
|
|
||||||
upload_max_filesize = 500M
|
|
||||||
post_max_size = 512M
|
|
||||||
max_execution_time = 300
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
.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
|
|
||||||
35
.env.example
35
.env.example
|
|
@ -2,7 +2,7 @@
|
||||||
# Example Environment Configuration file
|
# Example Environment Configuration file
|
||||||
#
|
#
|
||||||
# This file can be used as a starting point for
|
# This file can be used as a starting point for
|
||||||
# your Castopod instance settings.
|
# your Castopod Host instance settings.
|
||||||
#
|
#
|
||||||
# For manual configuration:
|
# For manual configuration:
|
||||||
# - copy this file's contents to a file named `.env`
|
# - copy this file's contents to a file named `.env`
|
||||||
|
|
@ -14,10 +14,9 @@
|
||||||
# Instance configuration
|
# Instance configuration
|
||||||
#--------------------------------------------------------------------
|
#--------------------------------------------------------------------
|
||||||
app.baseURL="https://YOUR_DOMAIN_NAME/"
|
app.baseURL="https://YOUR_DOMAIN_NAME/"
|
||||||
media.baseURL="https://YOUR_MEDIA_DOMAIN_NAME/"
|
app.mediaBaseURL="https://YOUR_MEDIA_DOMAIN_NAME/"
|
||||||
admin.gateway="cp-admin"
|
app.adminGateway="cp-admin"
|
||||||
auth.gateway="cp-auth"
|
app.authGateway="cp-auth"
|
||||||
analytics.salt="RANDOM_STRING_OF_64_CHARACTERS"
|
|
||||||
|
|
||||||
#--------------------------------------------------------------------
|
#--------------------------------------------------------------------
|
||||||
# Database configuration
|
# Database configuration
|
||||||
|
|
@ -28,14 +27,6 @@ database.default.username="root"
|
||||||
database.default.password="****"
|
database.default.password="****"
|
||||||
database.default.DBPrefix="cp_"
|
database.default.DBPrefix="cp_"
|
||||||
|
|
||||||
#--------------------------------------------------------------------
|
|
||||||
# Email configuration
|
|
||||||
#--------------------------------------------------------------------
|
|
||||||
# email.fromEmail="your_email_address"
|
|
||||||
# email.SMTPHost="your_smtp_host"
|
|
||||||
# email.SMTPUser="your_smtp_user"
|
|
||||||
# email.SMTPPass="your_smtp_password"
|
|
||||||
|
|
||||||
#--------------------------------------------------------------------
|
#--------------------------------------------------------------------
|
||||||
# Cache configuration (advanced)
|
# Cache configuration (advanced)
|
||||||
#
|
#
|
||||||
|
|
@ -50,21 +41,3 @@ cache.handler="file"
|
||||||
# cache.redis.password=null
|
# cache.redis.password=null
|
||||||
# cache.redis.port=6379
|
# cache.redis.port=6379
|
||||||
# cache.redis.database=0
|
# 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
|
|
||||||
|
|
||||||
|
|
|
||||||
18
.eslintrc.json
Normal file
18
.eslintrc.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"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": {}
|
||||||
|
}
|
||||||
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
|
|
@ -1 +0,0 @@
|
||||||
open_collective: castopod
|
|
||||||
66
.gitignore
vendored
66
.gitignore
vendored
|
|
@ -60,14 +60,10 @@ writable/logs/*
|
||||||
writable/session/*
|
writable/session/*
|
||||||
!writable/session/index.html
|
!writable/session/index.html
|
||||||
|
|
||||||
writable/temp/*
|
|
||||||
!writable/temp/index.html
|
|
||||||
|
|
||||||
writable/uploads/*
|
writable/uploads/*
|
||||||
!writable/uploads/index.html
|
!writable/uploads/index.html
|
||||||
|
|
||||||
writable/debugbar/*
|
writable/debugbar/*
|
||||||
!writable/debugbar/index.html
|
|
||||||
|
|
||||||
php_errors.log
|
php_errors.log
|
||||||
|
|
||||||
|
|
@ -86,7 +82,6 @@ tests/coverage*
|
||||||
|
|
||||||
# Don't save phpunit under version control.
|
# Don't save phpunit under version control.
|
||||||
phpunit
|
phpunit
|
||||||
.phpunit.cache
|
|
||||||
|
|
||||||
#-------------------------
|
#-------------------------
|
||||||
# Composer
|
# Composer
|
||||||
|
|
@ -107,15 +102,15 @@ _modules/*
|
||||||
.idea/
|
.idea/
|
||||||
*.iml
|
*.iml
|
||||||
|
|
||||||
# NetBeans
|
# Netbeans
|
||||||
/nbproject/
|
nbproject/
|
||||||
/build/
|
build/
|
||||||
/nbbuild/
|
nbbuild/
|
||||||
/dist/
|
dist/
|
||||||
/nbdist/
|
nbdist/
|
||||||
/nbactions.xml
|
nbactions.xml
|
||||||
/nb-configuration.xml
|
nb-configuration.xml
|
||||||
/.nb-gradle/
|
.nb-gradle/
|
||||||
|
|
||||||
# Sublime Text
|
# Sublime Text
|
||||||
*.tmlanguage.cache
|
*.tmlanguage.cache
|
||||||
|
|
@ -128,16 +123,14 @@ _modules/*
|
||||||
|
|
||||||
# Visual Studio Code
|
# Visual Studio Code
|
||||||
.vscode/
|
.vscode/
|
||||||
.history/
|
|
||||||
tmp/
|
|
||||||
|
|
||||||
/results/
|
/results/
|
||||||
/phpunit*.xml
|
/phpunit*.xml
|
||||||
|
/.phpunit.*.cache
|
||||||
|
|
||||||
# js package manager
|
# npm
|
||||||
yarn.lock
|
yarn.lock
|
||||||
node_modules
|
node_modules
|
||||||
.pnpm-store
|
|
||||||
|
|
||||||
# JS
|
# JS
|
||||||
.cache
|
.cache
|
||||||
|
|
@ -147,21 +140,14 @@ public/*
|
||||||
!public/media
|
!public/media
|
||||||
!public/.htaccess
|
!public/.htaccess
|
||||||
!public/favicon.ico
|
!public/favicon.ico
|
||||||
!public/icon*
|
|
||||||
!public/castopod-banner*
|
|
||||||
!public/castopod-avatar*
|
|
||||||
!public/index.php
|
!public/index.php
|
||||||
!public/robots.txt
|
!public/robots.txt
|
||||||
!public/.well-known
|
|
||||||
!public/.well-known/GDPR.yml
|
|
||||||
|
|
||||||
public/assets/*
|
|
||||||
!public/assets/index.html
|
|
||||||
|
|
||||||
# public media folder
|
# public media folder
|
||||||
|
public/media/*
|
||||||
|
!public/media/index.html
|
||||||
!public/media/podcasts
|
!public/media/podcasts
|
||||||
!public/media/persons
|
!public/media/persons
|
||||||
!public/media/site
|
|
||||||
|
|
||||||
public/media/podcasts/*
|
public/media/podcasts/*
|
||||||
!public/media/podcasts/index.html
|
!public/media/podcasts/index.html
|
||||||
|
|
@ -169,19 +155,19 @@ public/media/podcasts/*
|
||||||
public/media/persons/*
|
public/media/persons/*
|
||||||
!public/media/persons/index.html
|
!public/media/persons/index.html
|
||||||
|
|
||||||
public/media/site/*
|
|
||||||
!public/media/site/index.html
|
|
||||||
|
|
||||||
# Generated files
|
# Generated files
|
||||||
modules/Admin/Language/*/PersonsTaxonomy.php
|
app/Language/en/PersonsTaxonomy.php
|
||||||
|
app/Language/fr/PersonsTaxonomy.php
|
||||||
|
|
||||||
# Castopod bundle & packages
|
#-------------------------
|
||||||
castopod/
|
# Docker volumes
|
||||||
castopod-*.zip
|
#-------------------------
|
||||||
castopod-*.tar.gz
|
|
||||||
|
|
||||||
# Plugins
|
mariadb
|
||||||
plugins/*
|
phpmyadmin
|
||||||
!plugins/.gitkeep
|
sessions
|
||||||
writable/plugins.json
|
|
||||||
writable/plugins-lock.json
|
# Castopod Host bundle & packages
|
||||||
|
castopod-host/
|
||||||
|
castopod-host-*.zip
|
||||||
|
castopod-host-*.tar.gz
|
||||||
|
|
|
||||||
125
.gitlab-ci.yml
125
.gitlab-ci.yml
|
|
@ -1,52 +1,30 @@
|
||||||
image: code.castopod.org:5050/adaures/castopod:ci-php8.5
|
image: code.podlibre.org:5050/podlibre/castopod-host:latest
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- prepare
|
- prepare
|
||||||
- quality
|
- quality
|
||||||
- bundle
|
- bundle
|
||||||
- release
|
- release
|
||||||
- deploy
|
|
||||||
- build
|
|
||||||
|
|
||||||
php-dependencies:
|
php-dependencies:
|
||||||
stage: prepare
|
stage: prepare
|
||||||
script:
|
script:
|
||||||
# Install all php dependencies
|
# Install all php dependencies
|
||||||
- composer install --prefer-dist --no-ansi --no-interaction --no-progress --ignore-platform-reqs
|
- composer install --prefer-dist --no-ansi --no-interaction --no-progress --ignore-platform-reqs
|
||||||
cache:
|
|
||||||
key:
|
|
||||||
files:
|
|
||||||
- composer.lock
|
|
||||||
paths:
|
|
||||||
- .composer-cache
|
|
||||||
artifacts:
|
artifacts:
|
||||||
expire_in: 30 mins
|
|
||||||
paths:
|
paths:
|
||||||
- vendor/
|
- vendor/
|
||||||
rules:
|
expire_in: 30 mins
|
||||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
|
||||||
when: never
|
|
||||||
- when: on_success
|
|
||||||
|
|
||||||
js-dependencies:
|
js-dependencies:
|
||||||
stage: prepare
|
stage: prepare
|
||||||
script:
|
script:
|
||||||
# Install all js dependencies
|
# Install all npm dependencies
|
||||||
- pnpm install
|
- npm ci
|
||||||
cache:
|
|
||||||
key:
|
|
||||||
files:
|
|
||||||
- pnpm-lock.yaml
|
|
||||||
paths:
|
|
||||||
- .pnpm-store
|
|
||||||
artifacts:
|
artifacts:
|
||||||
expire_in: 30 mins
|
|
||||||
paths:
|
paths:
|
||||||
- node_modules/
|
- node_modules/
|
||||||
rules:
|
expire_in: 30 mins
|
||||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
|
||||||
when: never
|
|
||||||
- when: on_success
|
|
||||||
|
|
||||||
lint-commit-msg:
|
lint-commit-msg:
|
||||||
stage: quality
|
stage: quality
|
||||||
|
|
@ -56,10 +34,6 @@ lint-commit-msg:
|
||||||
- ./scripts/lint-commit-msg.sh
|
- ./scripts/lint-commit-msg.sh
|
||||||
dependencies:
|
dependencies:
|
||||||
- js-dependencies
|
- js-dependencies
|
||||||
rules:
|
|
||||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
|
||||||
when: never
|
|
||||||
- if: $CI_COMMIT_BRANCH =~ /^(develop|main|alpha|beta|next)$/
|
|
||||||
|
|
||||||
lint-php:
|
lint-php:
|
||||||
stage: quality
|
stage: quality
|
||||||
|
|
@ -72,46 +46,25 @@ lint-php:
|
||||||
- vendor/bin/rector process --dry-run --ansi
|
- vendor/bin/rector process --dry-run --ansi
|
||||||
dependencies:
|
dependencies:
|
||||||
- php-dependencies
|
- php-dependencies
|
||||||
rules:
|
|
||||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
|
||||||
when: never
|
|
||||||
- when: on_success
|
|
||||||
|
|
||||||
lint-js:
|
lint-js:
|
||||||
stage: quality
|
stage: quality
|
||||||
script:
|
script:
|
||||||
- pnpm run format
|
- npm run prettier
|
||||||
- pnpm run typecheck
|
- npm run typecheck
|
||||||
- pnpm run lint
|
- npm run lint
|
||||||
- pnpm run lint:css
|
- npm run lint:css
|
||||||
dependencies:
|
dependencies:
|
||||||
- js-dependencies
|
- js-dependencies
|
||||||
rules:
|
|
||||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
|
||||||
when: never
|
|
||||||
- when: on_success
|
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
stage: quality
|
stage: quality
|
||||||
services:
|
|
||||||
- mariadb:10.11
|
|
||||||
variables:
|
|
||||||
MYSQL_ROOT_PASSWORD: "R00Tp4ssW0RD"
|
|
||||||
MYSQL_DATABASE: "test"
|
|
||||||
MYSQL_USER: "castopod"
|
|
||||||
MYSQL_PASSWORD: "castopod"
|
|
||||||
script:
|
script:
|
||||||
- echo "SHOW DATABASES;" | mariadb --user=root --password="$MYSQL_ROOT_PASSWORD" --host=mariadb "$MYSQL_DATABASE" --skip_ssl
|
|
||||||
|
|
||||||
# run phpunit without code coverage
|
# run phpunit without code coverage
|
||||||
# TODO: add code coverage
|
# TODO: add code coverage
|
||||||
- vendor/bin/phpunit --no-coverage
|
- vendor/bin/phpunit --no-coverage
|
||||||
dependencies:
|
dependencies:
|
||||||
- php-dependencies
|
- php-dependencies
|
||||||
rules:
|
|
||||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
|
||||||
when: never
|
|
||||||
- when: on_success
|
|
||||||
|
|
||||||
bundle:
|
bundle:
|
||||||
stage: bundle
|
stage: bundle
|
||||||
|
|
@ -123,21 +76,19 @@ bundle:
|
||||||
# make scripts/bundle.sh executable
|
# make scripts/bundle.sh executable
|
||||||
- chmod +x ./scripts/bundle.sh
|
- chmod +x ./scripts/bundle.sh
|
||||||
|
|
||||||
# bundle castopod with commit ref as version
|
# bundle castopod-host with commit ref as version
|
||||||
- ./scripts/bundle.sh ${CI_COMMIT_REF_SLUG}_${CI_COMMIT_SHORT_SHA}
|
- ./scripts/bundle.sh ${CI_COMMIT_REF_SLUG}_${CI_COMMIT_SHORT_SHA}
|
||||||
dependencies:
|
dependencies:
|
||||||
- php-dependencies
|
- php-dependencies
|
||||||
- js-dependencies
|
- js-dependencies
|
||||||
artifacts:
|
artifacts:
|
||||||
name: "castopod-${CI_COMMIT_REF_SLUG}_${CI_COMMIT_SHORT_SHA}"
|
name: "castopod-host-${CI_COMMIT_REF_SLUG}_${CI_COMMIT_SHORT_SHA}"
|
||||||
paths:
|
paths:
|
||||||
- castopod
|
- castopod-host
|
||||||
rules:
|
except:
|
||||||
- if: $CI_PROJECT_NAMESPACE != "adaures"
|
- main
|
||||||
when: never
|
- beta
|
||||||
- if: $CI_COMMIT_BRANCH =~ /^(main|alpha|beta|next)$/ || $CI_COMMIT_TAG
|
- alpha
|
||||||
when: never
|
|
||||||
- when: on_success
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
stage: release
|
stage: release
|
||||||
|
|
@ -154,45 +105,11 @@ release:
|
||||||
- chmod +x ./scripts/package.sh
|
- chmod +x ./scripts/package.sh
|
||||||
|
|
||||||
# run semantic-release script (configured in `.releaserc.json` file)
|
# run semantic-release script (configured in `.releaserc.json` file)
|
||||||
- pnpm run release
|
- npm run release
|
||||||
dependencies:
|
dependencies:
|
||||||
- php-dependencies
|
- php-dependencies
|
||||||
- js-dependencies
|
- js-dependencies
|
||||||
artifacts:
|
only:
|
||||||
paths:
|
- main
|
||||||
- castopod
|
- alpha
|
||||||
rules:
|
- beta
|
||||||
- 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
|
|
||||||
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
|
|
||||||
rules:
|
|
||||||
- if: $CI_COMMIT_MESSAGE =~ /^chore\(release\):/
|
|
||||||
when: never
|
|
||||||
- when: on_success
|
|
||||||
|
|
||||||
docker:
|
|
||||||
stage: build
|
|
||||||
trigger:
|
|
||||||
include: docker/production/.gitlab-ci.yml
|
|
||||||
strategy: depend
|
|
||||||
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]
|
1. [First step]
|
||||||
2. [Second step]
|
2. [Second step]
|
||||||
3. [and so on…]
|
3. [and so on...]
|
||||||
|
|
||||||
### Expected behavior
|
### Expected behavior
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ logs, and code as it's very hard to read otherwise.
|
||||||
- OS: [e.g. Ubuntu server]
|
- OS: [e.g. Ubuntu server]
|
||||||
- Browser: [e.g. chrome, safari]
|
- Browser: [e.g. chrome, safari]
|
||||||
- Web server: [eg. Apache]
|
- Web server: [eg. Apache]
|
||||||
- [any other relevant context…]
|
- [any other relevant context...]
|
||||||
|
|
||||||
### Possible fixes
|
### Possible fixes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
### Before submitting an issue
|
|
||||||
|
|
||||||
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 release.
|
|
||||||
|
|
||||||
3. **Isolate the problem** — ideally create a
|
|
||||||
[reduced test case](https://css-tricks.com/reduced-test-cases/) and a live
|
|
||||||
example.
|
|
||||||
|
|
||||||
4. **Select an issue template** — choose a template from `bug` or
|
|
||||||
`feature-request` and fill out the info you deem necessary. The more context
|
|
||||||
we get, the easier it is to implement the feature or fix the bug you report.
|
|
||||||
|
|
||||||
Check out the [CONTRIBUTING manual](../../CONTRIBUTING.md) for more info.
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
### Is your feature request related to a problem? Please describe
|
### Is your feature request related to a problem? Please describe
|
||||||
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always
|
A clear and concise description of what the problem is. Ex. I'm always
|
||||||
frustrated when […]
|
frustrated when [...]
|
||||||
|
|
||||||
### Describe the solution you'd like
|
### Describe the solution you'd like
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1,4 @@
|
||||||
pnpm exec commitlint --verbose --edit "$1"
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npx --no-install commitlint --verbose --edit "$1"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
# CaptainHook 5.10.0
|
# CaptainHook 5.10.0
|
||||||
|
|
||||||
INTERACTIVE="--no-interaction"
|
INTERACTIVE="--no-interaction"
|
||||||
|
|
||||||
vendor/bin/captainhook $INTERACTIVE --configuration=captainhook.json --bootstrap=vendor/autoload.php hook:pre-commit "$@" <&0
|
vendor/bin/captainhook $INTERACTIVE --configuration=captainhook.json --bootstrap=vendor/autoload.php hook:pre-commit "$@" <&0
|
||||||
|
|
||||||
pnpm run typecheck
|
npm run typecheck
|
||||||
pnpm exec lint-staged
|
npx lint-staged
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
# CaptainHook 5.10.0
|
# CaptainHook 5.10.0
|
||||||
|
|
||||||
INTERACTIVE="--no-interaction"
|
INTERACTIVE="--no-interaction"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"trailingComma": "es5",
|
"trailingComma": "es5",
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": ["*.md", "*.mdx"],
|
"files": "*.md",
|
||||||
"options": {
|
"options": {
|
||||||
"proseWrap": "always"
|
"proseWrap": "always"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,86 +1,17 @@
|
||||||
{
|
{
|
||||||
"branches": [
|
"branches": [
|
||||||
"main",
|
"main",
|
||||||
{
|
{ "name": "alpha", "prerelease": true },
|
||||||
"name": "alpha",
|
{ "name": "beta", "prerelease": true }
|
||||||
"prerelease": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "beta",
|
|
||||||
"prerelease": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "next",
|
|
||||||
"prerelease": true
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
[
|
"@semantic-release/commit-analyzer",
|
||||||
"@semantic-release/commit-analyzer",
|
"@semantic-release/release-notes-generator",
|
||||||
{
|
|
||||||
"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/changelog",
|
||||||
[
|
[
|
||||||
"@semantic-release/exec",
|
"@semantic-release/exec",
|
||||||
{
|
{
|
||||||
"prepareCmd": "./scripts/bundle.sh ${nextRelease.version} && ./scripts/package.sh ${nextRelease.version} && pnpm exec prettier --write CHANGELOG.md"
|
"prepareCmd": "./scripts/bundle.sh ${nextRelease.version} && ./scripts/package.sh ${nextRelease.version} && npx prettier --write CHANGELOG.md"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"@semantic-release/npm",
|
"@semantic-release/npm",
|
||||||
|
|
@ -93,22 +24,21 @@
|
||||||
"package.json",
|
"package.json",
|
||||||
"package-lock.json",
|
"package-lock.json",
|
||||||
"CHANGELOG.md"
|
"CHANGELOG.md"
|
||||||
],
|
]
|
||||||
"message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"@semantic-release/gitlab",
|
"@semantic-release/gitlab",
|
||||||
{
|
{
|
||||||
"gitlabUrl": "https://code.castopod.org/",
|
"gitlabUrl": "https://code.podlibre.org/",
|
||||||
"assets": [
|
"assets": [
|
||||||
{
|
{
|
||||||
"path": "castopod-*.zip",
|
"path": "castopod-host-*.zip",
|
||||||
"label": "Castopod Package (zip)"
|
"label": "Castopod Host Package (zip)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "castopod-*.tar.gz",
|
"path": "castopod-host-*.tar.gz",
|
||||||
"label": "Castopod Package (tar.gz)"
|
"label": "Castopod Host Package (tar.gz)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,14 @@
|
||||||
# rsync filter rules to copy required files for Castopod's bundle
|
# rsync filter rules to copy required files for Castopod Host's bundle
|
||||||
|
|
||||||
+ resources/icons/***
|
- app/Views/_assets/
|
||||||
+ resources/
|
|
||||||
+ app/***
|
+ app/***
|
||||||
+ modules/***
|
|
||||||
+ plugins/***
|
|
||||||
+ public/***
|
+ public/***
|
||||||
+ themes/***
|
|
||||||
+ vendor/***
|
+ vendor/***
|
||||||
+ writable/***
|
+ writable/***
|
||||||
+ .env.example
|
+ .env.example
|
||||||
|
+ DEPENDENCIES.md
|
||||||
+ LICENSE.md
|
+ LICENSE.md
|
||||||
+ README.md
|
+ README.md
|
||||||
+ spark
|
+ INSTALL.md
|
||||||
+ php-icons.php
|
+ UPDATE.md
|
||||||
- **
|
- **
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"extends": "stylelint-config-standard",
|
"extends": "stylelint-config-recommended",
|
||||||
"rules": {
|
"rules": {
|
||||||
"at-rule-no-unknown": [
|
"at-rule-no-unknown": [
|
||||||
true,
|
true,
|
||||||
|
|
@ -10,24 +10,10 @@
|
||||||
"responsive",
|
"responsive",
|
||||||
"variants",
|
"variants",
|
||||||
"screen",
|
"screen",
|
||||||
"layer",
|
"layer"
|
||||||
"config"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"at-rule-no-deprecated": [
|
"no-descending-specificity": null
|
||||||
true,
|
|
||||||
{
|
|
||||||
"ignoreAtRules": ["apply"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"function-no-unknown": [
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
"ignoreFunctions": ["theme"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"no-descending-specificity": null,
|
|
||||||
"selector-class-pattern": null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Authors
|
# Authors
|
||||||
|
|
||||||
- [Benjamin Bellamy](https://code.castopod.org/benjamin) <ben@castopod.org>
|
- [Benjamin Bellamy](https://code.podlibre.org/benjamin) <ben@podlibre.org>
|
||||||
- [Yassine Doghri](https://code.castopod.org/yassine) <yassine@castopod.org>
|
- [Yassine Doghri](https://code.podlibre.org/yassine) <yassine@podlibre.org>
|
||||||
|
|
|
||||||
4465
CHANGELOG.md
4465
CHANGELOG.md
|
|
@ -1,3887 +1,868 @@
|
||||||
## [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)
|
# [1.0.0-alpha.80](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.79...v1.0.0-alpha.80) (2021-12-29)
|
||||||
|
|
||||||
### 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
|
### 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
|
- add application/octet-stream mimetype to mp3 and m4a extensions to prevent
|
||||||
ext_in error
|
ext_in error
|
||||||
([339bef8](https://code.castopod.org/adaures/castopod/commit/339bef878e54983d86e91e6ff7a931a843d321b3)),
|
([339bef8](https://code.podlibre.org/podlibre/castopod-host/commit/339bef878e54983d86e91e6ff7a931a843d321b3)),
|
||||||
closes [#145](https://code.castopod.org/adaures/castopod/issues/145)
|
closes [#145](https://code.podlibre.org/podlibre/castopod-host/issues/145)
|
||||||
- add category_label component to include parent category in about podcast page
|
|
||||||
([74e7d68](https://code.castopod.org/adaures/castopod/commit/74e7d68ac834885c4b89ee6e7d60db2157165799))
|
# [1.0.0-alpha.79](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.78...v1.0.0-alpha.79) (2021-12-20)
|
||||||
- add explicit int conversion when formatting episode duration
|
|
||||||
([1253096](https://code.castopod.org/adaures/castopod/commit/1253096197a0d30692bdafa7152f250cd9a71acf))
|
### Bug Fixes
|
||||||
- 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
|
- **import:** set episode and season numbers to null when not present in item
|
||||||
tag
|
tag
|
||||||
([3211398](https://code.castopod.org/adaures/castopod/commit/3211398c78b1b28b76a46427ee07874bbf84a85d))
|
([3211398](https://code.podlibre.org/podlibre/castopod-host/commit/3211398c78b1b28b76a46427ee07874bbf84a85d))
|
||||||
- **import:** use <image><url> tag when no <itunes:image> is present
|
|
||||||
([20e607a](https://code.castopod.org/adaures/castopod/commit/20e607afb755bc75056041738fa7cbf6723d754c))
|
# [1.0.0-alpha.78](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.77...v1.0.0-alpha.78) (2021-12-15)
|
||||||
- include missing variables on public ui's episode page and remote_actions
|
|
||||||
([193b373](https://code.castopod.org/adaures/castopod/commit/193b373bc94a5270acae99b637aa84b6cb2dedfe))
|
### Bug Fixes
|
||||||
- **input-component:** unset required attribute to prevent rendering it when
|
|
||||||
false
|
- **import:** add extension when downloading file without + truncate slug if too
|
||||||
([db9ac13](https://code.castopod.org/adaures/castopod/commit/db9ac13860bce58235a5da275910bea605a00626))
|
long
|
||||||
- **install:** add password validation when creating super admin
|
([c5f18bb](https://code.podlibre.org/podlibre/castopod-host/commit/c5f18bb6dc08a758ff735454bbe9cfa45a68c09b))
|
||||||
([5a2ca0c](https://code.castopod.org/adaures/castopod/commit/5a2ca0cc4ae85cc15960201c86f131cb822f714f))
|
|
||||||
- **install:** redirect manually to install wizard on first visit
|
# [1.0.0-alpha.77](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.76...v1.0.0-alpha.77) (2021-11-23)
|
||||||
([2ceaaca](https://code.castopod.org/adaures/castopod/commit/2ceaaca44f1b82fc64d961e2fb4f4aaeade7e736))
|
|
||||||
- **install:** redirect to host_url install route on instanceConfig validation
|
### Bug Fixes
|
||||||
error
|
|
||||||
([99250b1](https://code.castopod.org/adaures/castopod/commit/99250b1868657c249a447399c7ebc69e00d43d1a))
|
- **cors:** add preflight option routes for episode, podcast and status objects
|
||||||
- **install:** redirect to input baseUrl after instance config
|
([a281abf](https://code.podlibre.org/podlibre/castopod-host/commit/a281abfda475388a07943c169dab460cc2d4f944))
|
||||||
([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
|
- **podcast-import:** move guid attribute declaration for Episode entity to
|
||||||
include slug data
|
include slug data
|
||||||
([5d02ae3](https://code.castopod.org/adaures/castopod/commit/5d02ae39908a9d743627135b372bf981134c4328))
|
([5d02ae3](https://code.podlibre.org/podlibre/castopod-host/commit/5d02ae39908a9d743627135b372bf981134c4328))
|
||||||
- **podcast:** use markdown description value for editor + set prose class to
|
|
||||||
about description
|
# [1.0.0-alpha.76](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.75...v1.0.0-alpha.76) (2021-10-26)
|
||||||
([f304d97](https://code.castopod.org/adaures/castopod/commit/f304d97b14e0ef383509cb3bba50beb55bf701ba)),
|
|
||||||
closes [#156](https://code.castopod.org/adaures/castopod/issues/156)
|
### Bug Fixes
|
||||||
- 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
|
- replace hardcoded style links with vite service + set default value for remote
|
||||||
transcript url
|
transcript url
|
||||||
([3f2e056](https://code.castopod.org/adaures/castopod/commit/3f2e05608e43d47bbb518a9acfaf56ec3eefafb4)),
|
([3f2e056](https://code.podlibre.org/podlibre/castopod-host/commit/3f2e05608e43d47bbb518a9acfaf56ec3eefafb4)),
|
||||||
closes [#149](https://code.castopod.org/adaures/castopod/issues/149)
|
closes [#149](https://code.podlibre.org/podlibre/castopod-host/issues/149)
|
||||||
[#150](https://code.castopod.org/adaures/castopod/issues/150)
|
[#150](https://code.podlibre.org/podlibre/castopod-host/issues/150)
|
||||||
- replace website key for webpages in breadcrumb translate file
|
|
||||||
([50e32ff](https://code.castopod.org/adaures/castopod/commit/50e32ff75636c1d4c5d945a267e884cb26ad7191))
|
# [1.0.0-alpha.75](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.74...v1.0.0-alpha.75) (2021-10-05)
|
||||||
- restore default podcast icon on public website
|
|
||||||
([342778b](https://code.castopod.org/adaures/castopod/commit/342778bac3c684328d72633961df1a2ebdc1330e))
|
### Bug Fixes
|
||||||
- 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
|
- **rss:** cast number type values to string in rss_helper
|
||||||
([7180ae9](https://code.castopod.org/adaures/castopod/commit/7180ae9ec700930b69c04ed91f8eceea16ad77ce)),
|
([7180ae9](https://code.podlibre.org/podlibre/castopod-host/commit/7180ae9ec700930b69c04ed91f8eceea16ad77ce)),
|
||||||
closes [#148](https://code.castopod.org/adaures/castopod/issues/148)
|
closes [#148](https://code.podlibre.org/podlibre/castopod-host/issues/148)
|
||||||
- **rss:** do not escape podcast and episode titles in the xml
|
|
||||||
([0dd3b7e](https://code.castopod.org/adaures/castopod/commit/0dd3b7e0bf00d5a9eb80c93cba1efcada59ec3c1)),
|
# [1.0.0-alpha.74](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.73...v1.0.0-alpha.74) (2021-09-28)
|
||||||
closes [#138](https://code.castopod.org/adaures/castopod/issues/138)
|
|
||||||
[#71](https://code.castopod.org/adaures/castopod/issues/71)
|
### Features
|
||||||
- **rss:** remove escaping for publisher and owner name
|
|
||||||
([6fc6347](https://code.castopod.org/adaures/castopod/commit/6fc6347846c126618cb7ff50164181650308d0c0))
|
- **platforms:** add missing newpodcastapps.com's platforms
|
||||||
- **rss:** round episode durations and soundbites
|
([92dd370](https://code.podlibre.org/podlibre/castopod-host/commit/92dd370e2f9a464edd26cddcde96d0e16f91548d))
|
||||||
([c9fb987](https://code.castopod.org/adaures/castopod/commit/c9fb987fcfbe17069ec68fdbc823777079ce574b)),
|
|
||||||
closes [#214](https://code.castopod.org/adaures/castopod/issues/214)
|
# [1.0.0-alpha.73](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.72...v1.0.0-alpha.73) (2021-09-22)
|
||||||
- **rss:** set ❬itunes:author❭ tag to owner_name if publisher not specified
|
|
||||||
([2271c14](https://code.castopod.org/adaures/castopod/commit/2271c1445b1ded12bc53b5d23b5e59d12b17c71a)),
|
### Bug Fixes
|
||||||
closes [#96](https://code.castopod.org/adaures/castopod/issues/96)
|
|
||||||
- **rss:** use originalPath instead of originalMediaPath in Image library
|
- **map:** update episode markers query to discard unpublished episodes
|
||||||
([b4012b7](https://code.castopod.org/adaures/castopod/commit/b4012b7d2ed6b34b69ad767570dd33f0dc7db920))
|
([b3caac4](https://code.podlibre.org/podlibre/castopod-host/commit/b3caac45b12a23e4289d00133d2ad7915d084c44))
|
||||||
- save transcript and chapters files to podcasts folder
|
|
||||||
([63f49c7](https://code.castopod.org/adaures/castopod/commit/63f49c719f672b615c5a8893d3868dffcd332e47))
|
# [1.0.0-alpha.72](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.71...v1.0.0-alpha.72) (2021-09-20)
|
||||||
- **search-episodes:** add fallback sql query using LIKE for search query with
|
|
||||||
less than 4 characters
|
### Bug Fixes
|
||||||
([e66bf44](https://code.castopod.org/adaures/castopod/commit/e66bf44341175bc5a10fbf7dfa00b351e76136c2)),
|
|
||||||
closes [#236](https://code.castopod.org/adaures/castopod/issues/236)
|
- rename field status to task_status to get scheduled activities
|
||||||
- **security:** add csrf filter + prevent xss attacks by escaping user input
|
([4ff82a5](https://code.podlibre.org/podlibre/castopod-host/commit/4ff82a5f0a38dbbc9e272fca7df70ea5a190e334))
|
||||||
([cd2e1e1](https://code.castopod.org/adaures/castopod/commit/cd2e1e1dc37c53d32d00971c451c4800b8fd6107))
|
|
||||||
- set cache expiration to next note publish to show note on publication date
|
# [1.0.0-alpha.71](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.70...v1.0.0-alpha.71) (2021-09-17)
|
||||||
([0a66de3](https://code.castopod.org/adaures/castopod/commit/0a66de3e6c17d4ac94ee8e13bd00ceaf64b1303e))
|
|
||||||
- set episode description footer to null when empty value
|
### Features
|
||||||
([3a7d97d](https://code.castopod.org/adaures/castopod/commit/3a7d97d660046d80698611311ff3708110d2af82))
|
|
||||||
- set episode duration translation to hardcoded english
|
- **map:** display geolocated episodes on a map page
|
||||||
([c39efc9](https://code.castopod.org/adaures/castopod/commit/c39efc9489180662edcebd142d4476c0617ea97f)),
|
([4357cc2](https://code.podlibre.org/podlibre/castopod-host/commit/4357cc25ccc585ce398035c1c25d566b6a9df775))
|
||||||
closes [#64](https://code.castopod.org/adaures/castopod/issues/64)
|
|
||||||
- set episode guid upon episode creation
|
# [1.0.0-alpha.70](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.69...v1.0.0-alpha.70) (2021-08-31)
|
||||||
([ad8b153](https://code.castopod.org/adaures/castopod/commit/ad8b153f2a3b1a3b1751bf63785c4950e1516e6b)),
|
|
||||||
closes [#48](https://code.castopod.org/adaures/castopod/issues/48)
|
### Bug Fixes
|
||||||
- set episode numbers during import + remove all custom form_helpers + minor ui
|
|
||||||
issues
|
- **partner:** set correct image URL
|
||||||
([99a3b8d](https://code.castopod.org/adaures/castopod/commit/99a3b8d33e00482da50dd62bdaa9215a351a56e4))
|
([61554be](https://code.podlibre.org/podlibre/castopod-host/commit/61554be12a64d59ab99fab810b1b05632b408f3a))
|
||||||
- set interact_as_actor for user upon password reset
|
|
||||||
([ad8f5f5](https://code.castopod.org/adaures/castopod/commit/ad8f5f5a0fac7b0b9cc10a0b86200f014aca7553)),
|
# [1.0.0-alpha.69](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.68...v1.0.0-alpha.69) (2021-08-23)
|
||||||
closes [#178](https://code.castopod.org/adaures/castopod/issues/178)
|
|
||||||
- set localized slug_field key as string in french language
|
### Bug Fixes
|
||||||
([17fb29b](https://code.castopod.org/adaures/castopod/commit/17fb29b20993b7deee4e252e0e3a4a2459ee0d98))
|
|
||||||
- set location to null when getting empty string
|
- **import:** cast description's SimpleXMLElement to string
|
||||||
([71b1b5f](https://code.castopod.org/adaures/castopod/commit/71b1b5f775af475b1dc78328330e277f565e41b6))
|
([02d17be](https://code.podlibre.org/podlibre/castopod-host/commit/02d17be4ffe229fc6657207d31eba0543b5f1a4c))
|
||||||
- set storage limit as disk_total_space instead of free space
|
|
||||||
([7512e2e](https://code.castopod.org/adaures/castopod/commit/7512e2ed1ff5656cd63a4fc2524296dbb8b4164a))
|
# [1.0.0-alpha.68](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.67...v1.0.0-alpha.68) (2021-08-19)
|
||||||
- **settings:** add .jpg extension to site-icon file input to display all jpeg
|
|
||||||
images
|
### Bug Fixes
|
||||||
([f611a16](https://code.castopod.org/adaures/castopod/commit/f611a16cd0c1a389e1c5a287eaec9d2a927a4bb6))
|
|
||||||
- **socialinteract:** move social interact uri into uri attribute + update
|
- **analytics:** redirect to mp3 file even when referer was not set
|
||||||
social data upon import
|
([9fc388d](https://code.podlibre.org/podlibre/castopod-host/commit/9fc388d154f29c335dedcd624abe8c1751762c07))
|
||||||
([12b2200](https://code.castopod.org/adaures/castopod/commit/12b22008a237185cb736fc29352fab22421dad16))
|
|
||||||
- sort episodes by published_at with unpublished episodes at the begining
|
# [1.0.0-alpha.67](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.66...v1.0.0-alpha.67) (2021-07-24)
|
||||||
([1686f84](https://code.castopod.org/adaures/castopod/commit/1686f840d16f2bd3d71d7f222a59b8e6a838fd6e)),
|
|
||||||
closes [#249](https://code.castopod.org/adaures/castopod/issues/249)
|
### Features
|
||||||
- sort episodic podcasts by season
|
|
||||||
([d7b6794](https://code.castopod.org/adaures/castopod/commit/d7b6794f68f9a01fd606a407c6eb4c12d15dee74))
|
- allow cross origin requests on episode comments
|
||||||
- **themes:** update themes stylesheet route and remove css extension
|
([e12f95a](https://code.podlibre.org/podlibre/castopod-host/commit/e12f95aca13c6d54489a9cfd99d4cd2490fe83ab))
|
||||||
([e4e7e00](https://code.castopod.org/adaures/castopod/commit/e4e7e0005e931967dd6162588f1c5913dbf4603e))
|
|
||||||
- **types:** update fake seeders types + fix bugs
|
# [1.0.0-alpha.66](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.65...v1.0.0-alpha.66) (2021-07-24)
|
||||||
([76a4bf3](https://code.castopod.org/adaures/castopod/commit/76a4bf344160df679db29e236e7df7822970fb60))
|
|
||||||
- **ui:** remove empty tooltip when hovering on sponsor button
|
### Features
|
||||||
([40aa661](https://code.castopod.org/adaures/castopod/commit/40aa661289e1d1517fffcea5d257183bc9c458e4))
|
|
||||||
- unpublish episode before deleting it + add validation step before deletion
|
- **rss:** add podcast:comments tag to link to episode comments
|
||||||
([f75bd76](https://code.castopod.org/adaures/castopod/commit/f75bd76458eeb01a2d37912695e33f77d03b7a69)),
|
([32e8c7c](https://code.podlibre.org/podlibre/castopod-host/commit/32e8c7c16a61ffe08e2f3bfbdeda556811a0358c))
|
||||||
closes [#112](https://code.castopod.org/adaures/castopod/issues/112)
|
|
||||||
[#55](https://code.castopod.org/adaures/castopod/issues/55)
|
# [1.0.0-alpha.65](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.64...v1.0.0-alpha.65) (2021-07-22)
|
||||||
- update .htaccess for shared hosting config
|
|
||||||
([2379826](https://code.castopod.org/adaures/castopod/commit/2379826352e2f4b5060910bf9f29268610102f2e))
|
### Bug Fixes
|
||||||
- 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
|
- update conditions when checking for empty max_episodes and season_number
|
||||||
([fbad0b5](https://code.castopod.org/adaures/castopod/commit/fbad0b59f68c65eba2fdcd5a8d3b312b622e9a45))
|
([fbad0b5](https://code.podlibre.org/podlibre/castopod-host/commit/fbad0b59f68c65eba2fdcd5a8d3b312b622e9a45))
|
||||||
- update form_textarea to prevent escaping value
|
|
||||||
([78548b5](https://code.castopod.org/adaures/castopod/commit/78548b5cd75ea7d6688d1945ff5449ea4f6bec68))
|
# [1.0.0-alpha.64](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.63...v1.0.0-alpha.64) (2021-07-12)
|
||||||
- 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
|
### Features
|
||||||
|
|
||||||
- **activitypub:** add Podcast actor and PodcastEpisode object with comments
|
- **activitypub:** add Podcast actor and PodcastEpisode object with comments
|
||||||
([9e1e5d2](https://code.castopod.org/adaures/castopod/commit/9e1e5d2e862d6a3345d11ca7f96b955c76bfa013))
|
([9e1e5d2](https://code.podlibre.org/podlibre/castopod-host/commit/9e1e5d2e862d6a3345d11ca7f96b955c76bfa013))
|
||||||
- add about page in admin with instance info + database update button
|
|
||||||
([d0836f3](https://code.castopod.org/adaures/castopod/commit/d0836f3ee360a836f815c59ea755f288501dc517))
|
# [1.0.0-alpha.63](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.62...v1.0.0-alpha.63) (2021-07-12)
|
||||||
- add alternate rss feed link tag to podcast page head
|
|
||||||
([a973c09](https://code.castopod.org/adaures/castopod/commit/a973c097d54a3d0186c4079b9d4d3e81aae38505)),
|
### Features
|
||||||
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
|
- build hashed static files to renew browser cache
|
||||||
([37c54d2](https://code.castopod.org/adaures/castopod/commit/37c54d247749bdf8f528babd4a78f24d48051063)),
|
([37c54d2](https://code.podlibre.org/podlibre/castopod-host/commit/37c54d247749bdf8f528babd4a78f24d48051063)),
|
||||||
closes [#107](https://code.castopod.org/adaures/castopod/issues/107)
|
closes [#107](https://code.podlibre.org/podlibre/castopod-host/issues/107)
|
||||||
- **cache:** add podcast and episode pages to cache + clear them after insert or
|
|
||||||
update
|
# [1.0.0-alpha.62](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.61...v1.0.0-alpha.62) (2021-07-02)
|
||||||
([da0f047](https://code.castopod.org/adaures/castopod/commit/da0f0472819007e02e5da37399f2377772c618b9))
|
|
||||||
- **categories:** create model, entity, migrations and seeds
|
### Bug Fixes
|
||||||
([f73b042](https://code.castopod.org/adaures/castopod/commit/f73b042cc091be82abdbbca8992080875d526972))
|
|
||||||
- **clips:** setup clip entities and model + save video clip to have it
|
- **episode:** replace guid's empty string value to null
|
||||||
generated in the background
|
([441052a](https://code.podlibre.org/podlibre/castopod-host/commit/441052af8d99e6e317edefd1e58ad71799357088))
|
||||||
([2f6fdf9](https://code.castopod.org/adaures/castopod/commit/2f6fdf9091d52ca49709fc82621ba1c6dd0e817d))
|
|
||||||
- **comments:** add comments to episodes + update naming of status to post
|
# [1.0.0-alpha.61](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.60...v1.0.0-alpha.61) (2021-06-23)
|
||||||
([bb4752c](https://code.castopod.org/adaures/castopod/commit/bb4752c35e086664f5fd75fdc0d56546a1e356f6))
|
|
||||||
- **comments:** add like / undo like to comment + add comment page
|
### Bug Fixes
|
||||||
([0c187ef](https://code.castopod.org/adaures/castopod/commit/0c187ef7a9278a60bcc6e5ee4d69d948b51e5c54))
|
|
||||||
- **components:** add custom view renderer with ComponentRenderer adapted from
|
- **release:** add missing version number to castopod-host package
|
||||||
bonfire2
|
([8f3e9d9](https://code.podlibre.org/podlibre/castopod-host/commit/8f3e9d90c14545d3f84d4469b26a53db4554b4dc))
|
||||||
([a95de8b](https://code.castopod.org/adaures/castopod/commit/a95de8bab010f6b01c598da72191abe97e473687))
|
- **ux:** allow for empty message upon episode publication and warn user on
|
||||||
- create optimized & resized images upon upload
|
submit
|
||||||
([02e4441](https://code.castopod.org/adaures/castopod/commit/02e4441f98f27e9534e5b9b63279153d14632ccd)),
|
([33d01b8](https://code.podlibre.org/podlibre/castopod-host/commit/33d01b8d4fd6ebf24e9f011aa705c456c846956c)),
|
||||||
closes [#6](https://code.castopod.org/adaures/castopod/issues/6)
|
closes [#129](https://code.podlibre.org/podlibre/castopod-host/issues/129)
|
||||||
- **custom-rss:** add custom xml tag injection in rss feed for ❬channel❭ and
|
|
||||||
❬item❭
|
# [1.0.0-alpha.60](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.59...v1.0.0-alpha.60) (2021-06-21)
|
||||||
([6ecdaad](https://code.castopod.org/adaures/castopod/commit/6ecdaad911d06b7f7a2b7d24710968c7eb9118f6))
|
|
||||||
- **datetime-picker:** set material_green theme to flatpickr
|
### Features
|
||||||
([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
|
- **rss:** add ˂podcast:guid˃ tag for channel
|
||||||
([1fab10e](https://code.castopod.org/adaures/castopod/commit/1fab10eb0d63bb7c3edf34ffe691e2aec2c2e43c))
|
([1fab10e](https://code.podlibre.org/podlibre/castopod-host/commit/1fab10eb0d63bb7c3edf34ffe691e2aec2c2e43c))
|
||||||
- **rss:** add podcast-namespace tags for platforms + previousUrl tag
|
|
||||||
([dbba8dc](https://code.castopod.org/adaures/castopod/commit/dbba8dc58133967c778514268cbfed8098ed1dbc)),
|
# [1.0.0-alpha.59](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.58...v1.0.0-alpha.59) (2021-06-15)
|
||||||
closes [#73](https://code.castopod.org/adaures/castopod/issues/73)
|
|
||||||
[#75](https://code.castopod.org/adaures/castopod/issues/75)
|
### Bug Fixes
|
||||||
[#76](https://code.castopod.org/adaures/castopod/issues/76)
|
|
||||||
[#80](https://code.castopod.org/adaures/castopod/issues/80)
|
- check that additional files are valid when creating episode
|
||||||
- **rss:** add podcast:comments tag to link to episode comments
|
([eac5bc8](https://code.podlibre.org/podlibre/castopod-host/commit/eac5bc876de125e1fe08d1b89f767a04fc0fbfb6))
|
||||||
([32e8c7c](https://code.castopod.org/adaures/castopod/commit/32e8c7c16a61ffe08e2f3bfbdeda556811a0358c))
|
|
||||||
- **rss:** add podcast:location tag
|
# [1.0.0-alpha.58](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.57...v1.0.0-alpha.58) (2021-06-11)
|
||||||
([c0a2282](https://code.castopod.org/adaures/castopod/commit/c0a22829bd87d48535a86e60c6cd7280e44683a2))
|
|
||||||
- **rss:** add rss feed route without the `.xml` extension
|
### Bug Fixes
|
||||||
([94c0b7c](https://code.castopod.org/adaures/castopod/commit/94c0b7c15920dae9ade5cdc79c7996dbfe82ba05)),
|
|
||||||
closes [#247](https://code.castopod.org/adaures/castopod/issues/247)
|
- cast actor_id to pass as int to set_interact_as_actor() function
|
||||||
- **rss:** add soundbites according to the podcastindex specs
|
([56a8e5d](https://code.podlibre.org/podlibre/castopod-host/commit/56a8e5d7dd615322aeb007e730801c65d0b02e5c))
|
||||||
([6b34617](https://code.castopod.org/adaures/castopod/commit/6b34617d07c70522cb941e96d91d9987493413eb)),
|
- **analytics:** set duration field to precise decimal as episode's audio file
|
||||||
closes [#83](https://code.castopod.org/adaures/castopod/issues/83)
|
duration
|
||||||
- **rss:** add transcript and chapters support
|
([d772685](https://code.podlibre.org/podlibre/castopod-host/commit/d77268540569b2be9d91d5e09aefb3ff5ac2b071))
|
||||||
([e769d83](https://code.castopod.org/adaures/castopod/commit/e769d83a932c169e52a630a17cd4dd8ac5cebaf6)),
|
- **analytics:** update migrations to set decimal precision for latitude and
|
||||||
closes [#72](https://code.castopod.org/adaures/castopod/issues/72)
|
longitude
|
||||||
[#82](https://code.castopod.org/adaures/castopod/issues/82)
|
([714d6b5](https://code.podlibre.org/podlibre/castopod-host/commit/714d6b5d4950e52cf1c3170bb59954f98ffd48bd))
|
||||||
- **rss:** generate rss feed from podcast entity
|
- check for database connection and podcasts table existence before redirecting
|
||||||
([c815ecd](https://code.castopod.org/adaures/castopod/commit/c815ecd6640931fee0895f80908a3ddfac482666))
|
to install
|
||||||
- **rss:** update monetization tag so that it meets PodcastIndex requirements
|
([eb74e81](https://code.podlibre.org/podlibre/castopod-host/commit/eb74e81c3d93581e310b391cd029e62a0d690a8a))
|
||||||
([4c7ecbe](https://code.castopod.org/adaures/castopod/commit/4c7ecbee83950e5f9f2482cedaab18a1ac9bfc9e))
|
- save transcript and chapters files to podcasts folder
|
||||||
- **select:** enhance select input with choices.js
|
([63f49c7](https://code.podlibre.org/podlibre/castopod-host/commit/63f49c719f672b615c5a8893d3868dffcd332e47))
|
||||||
([910d457](https://code.castopod.org/adaures/castopod/commit/910d457cf843e0fc334b3505a4727d51633395ac))
|
- set cache expiration to next note publish to show note on publication date
|
||||||
|
([0a66de3](https://code.podlibre.org/podlibre/castopod-host/commit/0a66de3e6c17d4ac94ee8e13bd00ceaf64b1303e))
|
||||||
|
- set episode description footer to null when empty value
|
||||||
|
([3a7d97d](https://code.podlibre.org/podlibre/castopod-host/commit/3a7d97d660046d80698611311ff3708110d2af82))
|
||||||
|
- set location to null when getting empty string
|
||||||
|
([71b1b5f](https://code.podlibre.org/podlibre/castopod-host/commit/71b1b5f775af475b1dc78328330e277f565e41b6))
|
||||||
|
- update condition in home controller to redirect to install page
|
||||||
|
([33f1b91](https://code.podlibre.org/podlibre/castopod-host/commit/33f1b91d55dd0652c979d50fc85879dbf88a4a42))
|
||||||
|
- **activity-pub:** cache issues when navigating to activity stream urls
|
||||||
|
([7bcbfb3](https://code.podlibre.org/podlibre/castopod-host/commit/7bcbfb32f7cca08d111be46c7f1640e372d4a4b0))
|
||||||
|
- **activity-pub:** get database records using new model instances
|
||||||
|
([92536dd](https://code.podlibre.org/podlibre/castopod-host/commit/92536ddb3812214a9c5682b92e547e5c1998a5d7))
|
||||||
|
- **category:** remove uncategorized option to enforce users in choosing a
|
||||||
|
category
|
||||||
|
([8c64f25](https://code.podlibre.org/podlibre/castopod-host/commit/8c64f25a0e72fec03d25544797d32623b2276fce))
|
||||||
|
- **install:** redirect manually to install wizard on first visit
|
||||||
|
([2ceaaca](https://code.podlibre.org/podlibre/castopod-host/commit/2ceaaca44f1b82fc64d961e2fb4f4aaeade7e736))
|
||||||
|
- **types:** update fake seeders types + fix bugs
|
||||||
|
([76a4bf3](https://code.podlibre.org/podlibre/castopod-host/commit/76a4bf344160df679db29e236e7df7822970fb60))
|
||||||
|
- update broken contributor dropdown fields
|
||||||
|
([e5b7515](https://code.podlibre.org/podlibre/castopod-host/commit/e5b75150234bd7f19e01def93425d3bda7379dd3))
|
||||||
|
- **ux:** redirect user to install page on database error in home page
|
||||||
|
([9017e30](https://code.podlibre.org/podlibre/castopod-host/commit/9017e30bf41bed8c2be65091bbc5fb1e63aef87a))
|
||||||
|
- update condition in AnalyticsTrait
|
||||||
|
([fbc0967](https://code.podlibre.org/podlibre/castopod-host/commit/fbc0967caa81630d514ddb1b93b0834ebb4d913b))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
- **cache:** use deleteMatching method to prevent forgetting cached elements in
|
||||||
|
models
|
||||||
|
([76afc0c](https://code.podlibre.org/podlibre/castopod-host/commit/76afc0cfa2feb087697bae4bc138e4956873dd62))
|
||||||
|
|
||||||
|
### Reverts
|
||||||
|
|
||||||
|
- set deprecated config options back in App config
|
||||||
|
([433745f](https://code.podlibre.org/podlibre/castopod-host/commit/433745f194c73407999b207090478563283876a5))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.57](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.56...v1.0.0-alpha.57) (2021-05-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **follow:** add missing helpers to Actor controller
|
||||||
|
([ee53a73](https://code.podlibre.org/podlibre/castopod-host/commit/ee53a732dc12ebbf5706e14969749a12cfd9d559))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.56](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.55...v1.0.0-alpha.56) (2021-05-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **rss:** use originalPath instead of originalMediaPath in Image library
|
||||||
|
([b4012b7](https://code.podlibre.org/podlibre/castopod-host/commit/b4012b7d2ed6b34b69ad767570dd33f0dc7db920))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.55](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.54...v1.0.0-alpha.55) (2021-05-03)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- add remote_url alternative for transcript and chapters files
|
||||||
|
([3143c9a](https://code.podlibre.org/podlibre/castopod-host/commit/3143c9ad36e4cf1364205cf2be39c0c96f80fdd2))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.54](https://code.podlibre.org/podlibre/castopod-host/compare/v1.0.0-alpha.53...v1.0.0-alpha.54) (2021-05-03)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
- set app parameter forceGlobalSecureRequests = true forcing requests to go
|
- set app parameter forceGlobalSecureRequests = true forcing requests to go
|
||||||
through https
|
through https
|
||||||
([d9dff1b](https://code.castopod.org/adaures/castopod/commit/d9dff1b8bf89c8b526ad6cb89f98a1f160d49117))
|
([d9dff1b](https://code.podlibre.org/podlibre/castopod-host/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
|
- **ux:** remove admin dashboard and redirect directly to podcast list
|
||||||
([27c48b8](https://code.castopod.org/adaures/castopod/commit/27c48b8fa930b33e5e15f0c8685e468e857ca9cd))
|
([27c48b8](https://code.podlibre.org/podlibre/castopod-host/commit/27c48b8fa930b33e5e15f0c8685e468e857ca9cd))
|
||||||
- **video-clip:** add video-clip page with video preview + logs
|
- add cache to ActivityPub sql queries + cache activity and note pages
|
||||||
([42538dd](https://code.castopod.org/adaures/castopod/commit/42538dd7577be0ffe59b4fdfadbd76cc89e5ef30))
|
([2d297f4](https://code.podlibre.org/podlibre/castopod-host/commit/2d297f45b3d7ef6e8711875a0b9b908e878115fa))
|
||||||
- **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
|
### Performance Improvements
|
||||||
|
|
||||||
- **cache:** update CI4 to use cache's deleteMatching method
|
- **cache:** update CI4 to use cache's deleteMatching method
|
||||||
([54b84f9](https://code.castopod.org/adaures/castopod/commit/54b84f96843af13f579fea49102c8c2ef81b0a54))
|
([54b84f9](https://code.podlibre.org/podlibre/castopod-host/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
|
- **docker:** add redis caching service for development
|
||||||
([05ace8c](https://code.castopod.org/adaures/castopod/commit/05ace8cff2ef02d19abd40097ac5546dca6a54ca))
|
([05ace8c](https://code.podlibre.org/podlibre/castopod-host/commit/05ace8cff2ef02d19abd40097ac5546dca6a54ca))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.53](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.52...v1.0.0-alpha.53) (2021-04-16)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- check that note has a preview_card_id before displaying it
|
||||||
|
([acb8b3a](https://code.podlibre.org/podlibre/castopod/commit/acb8b3a40172ccb184ffe544760601d756692e6c)),
|
||||||
|
closes [#114](https://code.podlibre.org/podlibre/castopod/issues/114)
|
||||||
|
|
||||||
|
# [1.0.0-alpha.52](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.51...v1.0.0-alpha.52) (2021-04-16)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **avatar:** use default avatar when no avatar url has been set
|
||||||
|
([9d23c7e](https://code.podlibre.org/podlibre/castopod/commit/9d23c7e7e142c6cf1a1418e37e41d711064593c4)),
|
||||||
|
closes [#111](https://code.podlibre.org/podlibre/castopod/issues/111)
|
||||||
|
|
||||||
|
# [1.0.0-alpha.51](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.50...v1.0.0-alpha.51) (2021-04-15)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **interact-as:** set actor_id instead of podcast id upon login event
|
||||||
|
([5dfade7](https://code.podlibre.org/podlibre/castopod/commit/5dfade7cf37f339c56d2e577c679b88a1b1d9336)),
|
||||||
|
closes [#104](https://code.podlibre.org/podlibre/castopod/issues/104)
|
||||||
|
|
||||||
|
# [1.0.0-alpha.50](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.49...v1.0.0-alpha.50) (2021-04-14)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **persons:** prevent overflow of persons list by adding horizontal scroll
|
||||||
|
([9e8995d](https://code.podlibre.org/podlibre/castopod/commit/9e8995dc6e039032cc65f87895cf770f99e8b244))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.49](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.48...v1.0.0-alpha.49) (2021-04-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **multiselect:** add missing class names in choices options for purge to work
|
||||||
|
properly
|
||||||
|
([719538d](https://code.podlibre.org/podlibre/castopod/commit/719538d0ccb28af3c3c5e1a4b6468d4b772fe819))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.48](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.47...v1.0.0-alpha.48) (2021-04-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **import-with-escaped-characters:** remove \CodeIgniter\HTTP\URI in
|
||||||
|
download_file, closes
|
||||||
|
[#103](https://code.podlibre.org/podlibre/castopod/issues/103)
|
||||||
|
([35b5be0](https://code.podlibre.org/podlibre/castopod/commit/35b5be095ff54d27acec1610a846ec0cdbdf1d65))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.47](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.46...v1.0.0-alpha.47) (2021-04-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **episodeCount:** add missing brackets to French language file
|
||||||
|
([c1b4112](https://code.podlibre.org/podlibre/castopod/commit/c1b411265ad9b06e95a8b097ecf73445b88dcb45))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.46](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.45...v1.0.0-alpha.46) (2021-04-09)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **episodes-page:** handle defaultQuery being null when no podcast episodes
|
||||||
|
([15183b7](https://code.podlibre.org/podlibre/castopod/commit/15183b7eab57dac007bcdfa8c3651239de1ae05a)),
|
||||||
|
closes [#100](https://code.podlibre.org/podlibre/castopod/issues/100)
|
||||||
|
|
||||||
|
# [1.0.0-alpha.45](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.44...v1.0.0-alpha.45) (2021-04-08)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- add head request to analytics_hit route
|
||||||
|
([f0a2f0b](https://code.podlibre.org/podlibre/castopod/commit/f0a2f0bea491ca91976b351bb79837e95c9d094b))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.44](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.43...v1.0.0-alpha.44) (2021-04-08)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **rss:** set ❬itunes:author❭ tag to owner_name if publisher not specified
|
||||||
|
([2271c14](https://code.podlibre.org/podlibre/castopod/commit/2271c1445b1ded12bc53b5d23b5e59d12b17c71a)),
|
||||||
|
closes [#96](https://code.podlibre.org/podlibre/castopod/issues/96)
|
||||||
|
|
||||||
|
# [1.0.0-alpha.43](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.42...v1.0.0-alpha.43) (2021-04-08)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **episode-form:** show warning to set `memory_limit`, `upload_max_filesize` &
|
||||||
|
`post_max_size`
|
||||||
|
([3b3c218](https://code.podlibre.org/podlibre/castopod/commit/3b3c218b9c868e9f12c54d7670e69d84c9ee79c0)),
|
||||||
|
closes [#5](https://code.podlibre.org/podlibre/castopod/issues/5)
|
||||||
|
[#86](https://code.podlibre.org/podlibre/castopod/issues/86)
|
||||||
|
|
||||||
|
# [1.0.0-alpha.42](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.41...v1.0.0-alpha.42) (2021-04-02)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **fediverse:** implement activitypub protocols + update user interface
|
||||||
|
([2f525c0](https://code.podlibre.org/podlibre/castopod/commit/2f525c0f6e44d320bff16e22c223481923ba683e)),
|
||||||
|
closes [#69](https://code.podlibre.org/podlibre/castopod/issues/69)
|
||||||
|
[#65](https://code.podlibre.org/podlibre/castopod/issues/65)
|
||||||
|
[#85](https://code.podlibre.org/podlibre/castopod/issues/85)
|
||||||
|
[#51](https://code.podlibre.org/podlibre/castopod/issues/51)
|
||||||
|
[#91](https://code.podlibre.org/podlibre/castopod/issues/91)
|
||||||
|
[#92](https://code.podlibre.org/podlibre/castopod/issues/92)
|
||||||
|
[#88](https://code.podlibre.org/podlibre/castopod/issues/88)
|
||||||
|
|
||||||
|
# [1.0.0-alpha.41](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.40...v1.0.0-alpha.41) (2021-03-30)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **partner:** add link and image in episode description
|
||||||
|
([ad07bb9](https://code.podlibre.org/podlibre/castopod/commit/ad07bb9330dc9493813368e969e1f3a3def44614))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.40](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.39...v1.0.0-alpha.40) (2021-03-19)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **custom-rss:** add custom xml tag injection in rss feed for ❬channel❭ and
|
||||||
|
❬item❭
|
||||||
|
([6ecdaad](https://code.podlibre.org/podlibre/castopod/commit/6ecdaad911d06b7f7a2b7d24710968c7eb9118f6))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.39](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.38...v1.0.0-alpha.39) (2021-03-01)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **embeddable-player:** enable any ancestor when X-Frame-Options is set on
|
||||||
|
server
|
||||||
|
([44a4962](https://code.podlibre.org/podlibre/castopod/commit/44a4962e0b7e3ed87e9914b4e7792a0d52330ff8))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.38](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.37...v1.0.0-alpha.38) (2021-02-27)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **embeddable-player:** add embeddable player widget
|
||||||
|
([141788f](https://code.podlibre.org/podlibre/castopod/commit/141788fa089f9dedc8956c64ca515a4a4625f904))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.37](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.36...v1.0.0-alpha.37) (2021-02-17)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **import:** remove query string from files url
|
||||||
|
([109c4aa](https://code.podlibre.org/podlibre/castopod/commit/109c4aa1afb72dd8b99c0302d74a7fef5a38638e))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.36](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.35...v1.0.0-alpha.36) (2021-02-16)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **platforms:** add pod.link
|
||||||
|
([3d7a232](https://code.podlibre.org/podlibre/castopod/commit/3d7a2320ddd116e4a311605421126aff57243219))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.35](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.34...v1.0.0-alpha.35) (2021-02-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **admin:** save block and lock switches
|
||||||
|
([b66c0af](https://code.podlibre.org/podlibre/castopod/commit/b66c0afc8fab2e338402a9a4f8105e5f5459e208))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.34](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.33...v1.0.0-alpha.34) (2021-02-11)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **rss-import:** add Castopod user-agent, handle redirects for downloaded
|
||||||
|
files, add Content namespace
|
||||||
|
([214243b](https://code.podlibre.org/podlibre/castopod/commit/214243b3fec4937e45ef1ceaba1149004cdf3b44))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.33](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.32...v1.0.0-alpha.33) (2021-02-10)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **platforms:** add helloasso
|
||||||
|
([16cb993](https://code.podlibre.org/podlibre/castopod/commit/16cb993ee6e28987a840fc27a9c2c73794c67697))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.32](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.31...v1.0.0-alpha.32) (2021-02-10)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **person:** add podcastindex.org namespace person tag
|
||||||
|
([8acd011](https://code.podlibre.org/podlibre/castopod/commit/8acd011f13e99492ef4b44b327685bb006fe5f8f))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.31](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.30...v1.0.0-alpha.31) (2020-12-23)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **rss:** add podcast:location tag
|
||||||
|
([c0a2282](https://code.podlibre.org/podlibre/castopod/commit/c0a22829bd87d48535a86e60c6cd7280e44683a2))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.30](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.29...v1.0.0-alpha.30) (2020-12-21)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **rss:** update monetization tag so that it meets PodcastIndex requirements
|
||||||
|
([4c7ecbe](https://code.podlibre.org/podlibre/castopod/commit/4c7ecbee83950e5f9f2482cedaab18a1ac9bfc9e))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.29](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.28...v1.0.0-alpha.29) (2020-12-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **episodes:** add publication status + set publication date to null when none
|
||||||
|
has been set
|
||||||
|
([d882981](https://code.podlibre.org/podlibre/castopod/commit/d882981b3a86c81921ce6b07d4cf61fc13983689)),
|
||||||
|
closes [#70](https://code.podlibre.org/podlibre/castopod/issues/70)
|
||||||
|
|
||||||
### Reverts
|
### 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
|
- **soundbites:** remove soundbite table from episode's public page
|
||||||
([5dc0f19](https://code.castopod.org/adaures/castopod/commit/5dc0f19656de0d764f627d6ae78a9e306c901835))
|
([5dc0f19](https://code.podlibre.org/podlibre/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))
|
# [1.0.0-alpha.28](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.27...v1.0.0-alpha.28) (2020-12-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **rss:** add soundbites according to the podcastindex specs
|
||||||
|
([6b34617](https://code.podlibre.org/podlibre/castopod/commit/6b34617d07c70522cb941e96d91d9987493413eb)),
|
||||||
|
closes [#83](https://code.podlibre.org/podlibre/castopod/issues/83)
|
||||||
|
|
||||||
|
# [1.0.0-alpha.27](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.26...v1.0.0-alpha.27) (2020-12-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **platforms:** add AntennaPod
|
||||||
|
([53e9cfd](https://code.podlibre.org/podlibre/castopod/commit/53e9cfd61c794b1539e9d4691d3c4e73c4b7aaa7))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.26](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.25...v1.0.0-alpha.26) (2020-11-30)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **analytics:** update service management so that it works with new OPAWG slug
|
||||||
|
values
|
||||||
|
([7fe9d42](https://code.podlibre.org/podlibre/castopod/commit/7fe9d42500ade2c6fa3ff4365b4affc475af0e51))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.25](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.24...v1.0.0-alpha.25) (2020-11-30)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **platforms:** add podfriend
|
||||||
|
([9fdc8d3](https://code.podlibre.org/podlibre/castopod/commit/9fdc8d32930234c7ffd2be6892be57febcef1086))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.24](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.23...v1.0.0-alpha.24) (2020-11-26)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **monetization:** add Web Monetization support
|
||||||
|
([96a6026](https://code.podlibre.org/podlibre/castopod/commit/96a6026f1db452085360f5fe248de82a2ec06468))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.23](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.22...v1.0.0-alpha.23) (2020-11-24)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- define podcastNamespaceLink value
|
||||||
|
([0d744d2](https://code.podlibre.org/podlibre/castopod/commit/0d744d212df0d070ceea185068eaf2746e1ccd48))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.22](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.21...v1.0.0-alpha.22) (2020-11-24)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **rss:** add transcript and chapters support
|
||||||
|
([e769d83](https://code.podlibre.org/podlibre/castopod/commit/e769d83a932c169e52a630a17cd4dd8ac5cebaf6)),
|
||||||
|
closes [#72](https://code.podlibre.org/podlibre/castopod/issues/72)
|
||||||
|
[#82](https://code.podlibre.org/podlibre/castopod/issues/82)
|
||||||
|
|
||||||
|
# [1.0.0-alpha.21](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.20...v1.0.0-alpha.21) (2020-11-24)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **platforms:** add Fediverse and some funding platforms, add link on logo
|
||||||
|
([afc3d50](https://code.podlibre.org/podlibre/castopod/commit/afc3d50289bb4173e0697d109ffe72f6814b93d1))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.20](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.19...v1.0.0-alpha.20) (2020-11-24)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **import:** use <image><url> tag when no <itunes:image> is present
|
||||||
|
([20e607a](https://code.podlibre.org/podlibre/castopod/commit/20e607afb755bc75056041738fa7cbf6723d754c))
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **rss:** add podcast-namespace tags for platforms + previousUrl tag
|
||||||
|
([dbba8dc](https://code.podlibre.org/podlibre/castopod/commit/dbba8dc58133967c778514268cbfed8098ed1dbc)),
|
||||||
|
closes [#73](https://code.podlibre.org/podlibre/castopod/issues/73)
|
||||||
|
[#75](https://code.podlibre.org/podlibre/castopod/issues/75)
|
||||||
|
[#76](https://code.podlibre.org/podlibre/castopod/issues/76)
|
||||||
|
[#80](https://code.podlibre.org/podlibre/castopod/issues/80)
|
||||||
|
|
||||||
|
# [1.0.0-alpha.19](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.18...v1.0.0-alpha.19) (2020-11-13)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- handle HEAD requests on podcast_feed route
|
||||||
|
([74b2640](https://code.podlibre.org/podlibre/castopod/commit/74b2640f2a25c4cd6fd8835fc492c2a6893d4950)),
|
||||||
|
closes [#79](https://code.podlibre.org/podlibre/castopod/issues/79)
|
||||||
|
|
||||||
|
# [1.0.0-alpha.18](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.17...v1.0.0-alpha.18) (2020-11-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **platforms:** add Podcast Index
|
||||||
|
([ad52b1c](https://code.podlibre.org/podlibre/castopod/commit/ad52b1cc2b7d0bc844970214d205961a7196b4a9))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.17](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.16...v1.0.0-alpha.17) (2020-11-05)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **open-graph:** replace non existant episode description to podcast
|
||||||
|
description in podcast page
|
||||||
|
([b02584e](https://code.podlibre.org/podlibre/castopod/commit/b02584ee609af1ad1b5680cc28208d113eb0410b))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.16](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.15...v1.0.0-alpha.16) (2020-11-04)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- add Open Graph and Twitter meta tags
|
||||||
|
([af970b8](https://code.podlibre.org/podlibre/castopod/commit/af970b8bac949e4c63047e04aca1b7403a4e8deb)),
|
||||||
|
closes [#41](https://code.podlibre.org/podlibre/castopod/issues/41)
|
||||||
|
|
||||||
|
# [1.0.0-alpha.15](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.14...v1.0.0-alpha.15) (2020-11-03)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **analytics:** add 'other' group to pie charts in order to display more
|
||||||
|
accurate data
|
||||||
|
([73acef9](https://code.podlibre.org/podlibre/castopod/commit/73acef933ff3485987afc5157de022910876fc12))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.14](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.13...v1.0.0-alpha.14) (2020-11-02)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **analytics:** add weekday and hour bar charts
|
||||||
|
([8ab3132](https://code.podlibre.org/podlibre/castopod/commit/8ab313296bb4a254ab05e90b17d896039839b784))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.13](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.12...v1.0.0-alpha.13) (2020-10-29)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **episodes-table:** set descriptions to be not null
|
||||||
|
([6774ec1](https://code.podlibre.org/podlibre/castopod/commit/6774ec10fa78527be6b7548ca1dc34ad0ada090c))
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- add episode_numbering() component helper to display episode and season numbers
|
||||||
|
([3f4a6bd](https://code.podlibre.org/podlibre/castopod/commit/3f4a6bd0b9f870f16107a41b102b6bf734868198))
|
||||||
|
- **episodes:** replace all audio file URL parameters with base64 encoded data
|
||||||
|
([e1f65cd](https://code.podlibre.org/podlibre/castopod/commit/e1f65cd3b53353a30d4ab6eb5312393cf04a1676))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.12](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.11...v1.0.0-alpha.12) (2020-10-26)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- replace getWebEnclosureUrl with getEnclosureWebUrl
|
||||||
|
([8122cea](https://code.podlibre.org/podlibre/castopod/commit/8122ceaf8a70050f14b3078f28b024e7d7cdb9ac))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.11](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.10...v1.0.0-alpha.11) (2020-10-26)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- add CDN url
|
||||||
|
([972bcbf](https://code.podlibre.org/podlibre/castopod/commit/972bcbf65ee119b8641ca3c4e5c0e8cf9ca8dd4f)),
|
||||||
|
closes [#37](https://code.podlibre.org/podlibre/castopod/issues/37)
|
||||||
|
|
||||||
|
# [1.0.0-alpha.10](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.9...v1.0.0-alpha.10) (2020-10-26)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **install:** redirect to host_url install route on instanceConfig validation
|
||||||
|
error
|
||||||
|
([99250b1](https://code.podlibre.org/podlibre/castopod/commit/99250b1868657c249a447399c7ebc69e00d43d1a))
|
||||||
|
|
||||||
|
# [1.0.0-alpha.9](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.8...v1.0.0-alpha.9) (2020-10-26)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- display castopod version in admin footer
|
||||||
|
([9f2574e](https://code.podlibre.org/podlibre/castopod/commit/9f2574e6fbb61dac4e1a4252dff30017685da5f0)),
|
||||||
|
closes [#68](https://code.podlibre.org/podlibre/castopod/issues/68)
|
||||||
|
|
||||||
|
# [1.0.0-alpha.8](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.7...v1.0.0-alpha.8) (2020-10-22)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **episodes:** schedule episode with future publication_date by using cache
|
||||||
|
expiration time
|
||||||
|
([4f1e773](https://code.podlibre.org/podlibre/castopod/commit/4f1e773c0f9e4c2597f6c1b0a4773dfb34b2f203)),
|
||||||
|
closes [#47](https://code.podlibre.org/podlibre/castopod/issues/47)
|
||||||
|
|
||||||
|
# [1.0.0-alpha.7](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.6...v1.0.0-alpha.7) (2020-10-21)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **analytics:** add service name from rss user-agent
|
||||||
|
([7202b98](https://code.podlibre.org/podlibre/castopod/commit/7202b9867bd59aafa8c338a4230fb5e5c55b24c6))
|
||||||
|
|
||||||
### BREAKING CHANGES
|
### BREAKING CHANGES
|
||||||
|
|
||||||
- **analytics:** analytics_podcasts_by_player table and analytics_podcasts
|
- **analytics:** analytics_podcasts_by_player table and analytics_podcasts
|
||||||
procedure were updated
|
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)
|
# [1.0.0-alpha.6](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.5...v1.0.0-alpha.6) (2020-10-20)
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **router:** trim URI slash to match same routes for URIs with and without
|
|
||||||
trailing slash
|
|
||||||
([9e9375f](https://code.castopod.org/adaures/castopod/commit/9e9375f9a2cd6102f827b36ec521f4c86a557c00))
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **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)
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
# [1.0.0-beta.23](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.22...v1.0.0-beta.23) (2022-09-29)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **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))
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 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 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)
|
|
||||||
- **gdpr:** add purpose for granting access to premium content
|
|
||||||
([47d6d81](https://code.castopod.org/adaures/castopod/commit/47d6d81b798ec3ed467e0f4339c98c8a6b80cecd))
|
|
||||||
|
|
||||||
# [1.0.0-beta.22](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.21...v1.0.0-beta.22) (2022-09-23)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **fediverse:** set default castopod avatar url when actor avatar is not
|
|
||||||
present
|
|
||||||
([460f52f](https://code.castopod.org/adaures/castopod/commit/460f52f70e493d619c28632db6c698e88f0ebb5f))
|
|
||||||
- **import:** set default episode type if not set
|
|
||||||
([d7250ab](https://code.castopod.org/adaures/castopod/commit/d7250ab03f9b032830c575ad58b51c8d60b7a49a))
|
|
||||||
- **input-component:** unset required attribute to prevent rendering it when
|
|
||||||
false
|
|
||||||
([db9ac13](https://code.castopod.org/adaures/castopod/commit/db9ac13860bce58235a5da275910bea605a00626))
|
|
||||||
- **notifications:** notify actors after activities insert / update using model
|
|
||||||
callback methods
|
|
||||||
([e08555a](https://code.castopod.org/adaures/castopod/commit/e08555a4e9a6c15eeba18273c63403f82eddae35))
|
|
||||||
- overwrite getActorById to return app's Actor entity
|
|
||||||
([f2bc2f7](https://code.castopod.org/adaures/castopod/commit/f2bc2f7e01aa166faa627df6fe4d5ed4887c16e5))
|
|
||||||
- remove heavy image cover data from audio file metadata
|
|
||||||
([f74403b](https://code.castopod.org/adaures/castopod/commit/f74403bd7a5089b760603abe36264e7615be0e78))
|
|
||||||
- set storage limit as disk_total_space instead of free space
|
|
||||||
([7512e2e](https://code.castopod.org/adaures/castopod/commit/7512e2ed1ff5656cd63a4fc2524296dbb8b4164a))
|
|
||||||
- **ui:** remove empty tooltip when hovering on sponsor button
|
|
||||||
([40aa661](https://code.castopod.org/adaures/castopod/commit/40aa661289e1d1517fffcea5d257183bc9c458e4))
|
|
||||||
- **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:** have podcast dashboard card link to podcast dashboard if only one
|
|
||||||
podcast in instance
|
|
||||||
([7dabee5](https://code.castopod.org/adaures/castopod/commit/7dabee58a187abe92358d962da506a836e29cda3))
|
|
||||||
|
|
||||||
# [1.0.0-beta.21](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.20...v1.0.0-beta.21) (2022-09-06)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **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)
|
|
||||||
- **notifications:** add trigger after activities update + update insert trigger
|
|
||||||
([e5d16e8](https://code.castopod.org/adaures/castopod/commit/e5d16e87119021fa5a43470d67ddfe5128e57f74))
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **i18n:** add support for Simplified Chinese (zh-Hans) and Catalan (ca)
|
|
||||||
locales
|
|
||||||
([48d1443](https://code.castopod.org/adaures/castopod/commit/48d14434727c3310a391160c7af02c56b7e20425))
|
|
||||||
|
|
||||||
# [1.0.0-beta.20](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.19...v1.0.0-beta.20) (2022-08-12)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- add underline and semibold font weight for prose links to have them stand out
|
|
||||||
([d4d8671](https://code.castopod.org/adaures/castopod/commit/d4d867121c50bded4176a53d7154cf1bb347e306))
|
|
||||||
- **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)
|
|
||||||
- **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)
|
|
||||||
- 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)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 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 notifications inbox for actors
|
|
||||||
([999999e](https://code.castopod.org/adaures/castopod/commit/999999e3efab7b1aad7568e4fd114dc7bac04f38)),
|
|
||||||
closes [#215](https://code.castopod.org/adaures/castopod/issues/215)
|
|
||||||
|
|
||||||
# [1.0.0-beta.19](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.18...v1.0.0-beta.19) (2022-07-21)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **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)
|
|
||||||
- **get_browser_language:** return defaultLocale if browser doesn't send user
|
|
||||||
preferred language
|
|
||||||
([9cc2996](https://code.castopod.org/adaures/castopod/commit/9cc299626181048b85b629bbe7f5806a1f5d21ff))
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **episode-unpublish:** remove episode comments upon unpublish
|
|
||||||
([78acd7f](https://code.castopod.org/adaures/castopod/commit/78acd7f5c057c82507d801c424040296dbaba586))
|
|
||||||
|
|
||||||
# [1.0.0-beta.18](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.17...v1.0.0-beta.18) (2022-07-07)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **player-styling:** revert vite to 2.8 to reference the player css
|
|
||||||
([e07d3af](https://code.castopod.org/adaures/castopod/commit/e07d3afea9af85b8361227e000fb64b502781668))
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- add legalNoticeURL to app config for setting an external url to legal notice
|
|
||||||
([711843a](https://code.castopod.org/adaures/castopod/commit/711843a0c81e1e2ec7a015431786df4ef32d5092))
|
|
||||||
|
|
||||||
# [1.0.0-beta.17](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.16...v1.0.0-beta.17) (2022-07-06)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- explicitly cast seconds to int in iso8601_duration helper function
|
|
||||||
([779653f](https://code.castopod.org/adaures/castopod/commit/779653f75b140942f731cbb238bc0667cc461307))
|
|
||||||
- **housekeeping:** use EpisodeModel's builder to reset comments count
|
|
||||||
([65e9c0b](https://code.castopod.org/adaures/castopod/commit/65e9c0b05ea4992884149cb4a4b071bf31a20a1a))
|
|
||||||
- **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)
|
|
||||||
- **xml-editor:** prettify xml even without root node
|
|
||||||
([ca55c24](https://code.castopod.org/adaures/castopod/commit/ca55c248d0562a8529071c1f10be12f40ef50dda))
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 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)
|
|
||||||
- **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)
|
|
||||||
- **datetime-picker:** set material_green theme to flatpickr
|
|
||||||
([3ce6541](https://code.castopod.org/adaures/castopod/commit/3ce6541003260677e722a916ad6bc83ef47c4371))
|
|
||||||
|
|
||||||
# [1.0.0-beta.16](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.15...v1.0.0-beta.16) (2022-06-24)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- change image size requirement hints
|
|
||||||
([ea20206](https://code.castopod.org/adaures/castopod/commit/ea20206ee674eb54dd3ea188d2a2e2d41425df65))
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 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)
|
|
||||||
- **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)
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
# [1.0.0-beta.15](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.14...v1.0.0-beta.15) (2022-06-14)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- replace deletedField with published_at for episodes
|
|
||||||
([14d7d07](https://code.castopod.org/adaures/castopod/commit/14d7d078225cdc8980759273a5dc4163d9f84b06))
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- add default icons to Alert component
|
|
||||||
([0d98001](https://code.castopod.org/adaures/castopod/commit/0d9800123b135e4fa1a2acd14a5e039c12174333))
|
|
||||||
- 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)
|
|
||||||
- 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)
|
|
||||||
- **episodes:** replace soft delete with permanent delete
|
|
||||||
([eb9ff52](https://code.castopod.org/adaures/castopod/commit/eb9ff522c25af8ceb2ed08614b581757ee791d42))
|
|
||||||
|
|
||||||
# [1.0.0-beta.14](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.13...v1.0.0-beta.14) (2022-04-23)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **home:** remove hardcoded prefix in getAllPodcasts query
|
|
||||||
([92d5cc5](https://code.castopod.org/adaures/castopod/commit/92d5cc50a3e533875cd894dccc417918102d4b7f))
|
|
||||||
- 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)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **i18n:** add Spanish to supported locales
|
|
||||||
([e340b54](https://code.castopod.org/adaures/castopod/commit/e340b54a84d7dcdf9ba910fe7ff39c453fac0968))
|
|
||||||
|
|
||||||
# [1.0.0-beta.13](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.12...v1.0.0-beta.13) (2022-04-14)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **rss:** remove escaping for publisher and owner name
|
|
||||||
([e2046e4](https://code.castopod.org/adaures/castopod/commit/e2046e4b116ecddb5e6d68487f666b95fd7f493c))
|
|
||||||
- use UTC_TIMESTAMP() to get current utc date instead of NOW() in sql queries
|
|
||||||
([853a6ba](https://code.castopod.org/adaures/castopod/commit/853a6ba9155b6687604304d59f03d0efb75a9f96))
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **i18n:** add Norwegian Nynorsk to supported locales
|
|
||||||
([744340d](https://code.castopod.org/adaures/castopod/commit/744340df615bee38a54c4abbbb7f03d51b61a39d))
|
|
||||||
|
|
||||||
# [1.0.0-beta.12](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.11...v1.0.0-beta.12) (2022-04-05)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- update form_textarea to prevent escaping value
|
|
||||||
([78548b5](https://code.castopod.org/adaures/castopod/commit/78548b5cd75ea7d6688d1945ff5449ea4f6bec68))
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **i18n:** add support for German and Brazilian Portuguese languages
|
|
||||||
([19da003](https://code.castopod.org/adaures/castopod/commit/19da003fd396bff20b89ad330b787e9cdbe8d919))
|
|
||||||
|
|
||||||
# [1.0.0-beta.11](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.10...v1.0.0-beta.11) (2022-04-01)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- change message upon cancellation of episode publication
|
|
||||||
([9859c74](https://code.castopod.org/adaures/castopod/commit/9859c7434c2a3478ce035f7a4de20f594d63f5b0))
|
|
||||||
- prefill description footer input when creating a new episode
|
|
||||||
([9ea5ca3](https://code.castopod.org/adaures/castopod/commit/9ea5ca31697c70d176294f8aea37bd57d471fcf7))
|
|
||||||
- remove value escaping for form inputs and textareas
|
|
||||||
([bc6dea2](https://code.castopod.org/adaures/castopod/commit/bc6dea2f8ad1cf0aee0eaa93151332fbac7fb771))
|
|
||||||
- restore default podcast icon on public website
|
|
||||||
([342778b](https://code.castopod.org/adaures/castopod/commit/342778bac3c684328d72633961df1a2ebdc1330e))
|
|
||||||
- **socialinteract:** move social interact uri into uri attribute + update
|
|
||||||
social data upon import
|
|
||||||
([12b2200](https://code.castopod.org/adaures/castopod/commit/12b22008a237185cb736fc29352fab22421dad16))
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **analytics-gdpr:** update cached personal data to expire at midnight
|
|
||||||
([0188b67](https://code.castopod.org/adaures/castopod/commit/0188b67354a756f0c926edd7b46623ab5b20c12b))
|
|
||||||
- **analytics:** add current date and secret salt to analytics hash for improved
|
|
||||||
privacy
|
|
||||||
([6f2e7c0](https://code.castopod.org/adaures/castopod/commit/6f2e7c009c24830d4f08633bfbde3b75f40bf215))
|
|
||||||
- **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))
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
# [1.0.0-beta.10](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.9...v1.0.0-beta.10) (2022-03-15)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- add explicit int conversion when formatting episode duration
|
|
||||||
([1253096](https://code.castopod.org/adaures/castopod/commit/1253096197a0d30692bdafa7152f250cd9a71acf))
|
|
||||||
- add href to castopod website on login page
|
|
||||||
([cc54257](https://code.castopod.org/adaures/castopod/commit/cc5425735184ad738aa0f38540f18e8971f8f56e))
|
|
||||||
- move html escaping on credits page
|
|
||||||
([fbffdbd](https://code.castopod.org/adaures/castopod/commit/fbffdbde78544c83138ee6234c62d43056f407b6))
|
|
||||||
- remove cache from remote follow form to display error messages
|
|
||||||
([90e4443](https://code.castopod.org/adaures/castopod/commit/90e44437bdf37d8024ef609b2f7336dbdfc3b974))
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- add autofocus to input field "Email or username" on login page
|
|
||||||
([19caed4](https://code.castopod.org/adaures/castopod/commit/19caed4bce0daab9ccf6ab9645f44b60eb87de88))
|
|
||||||
- add WebSub module for pushing feed updates to open hubs
|
|
||||||
([10d3f73](https://code.castopod.org/adaures/castopod/commit/10d3f73786ba141e27a822b2585c4a244ee92c14))
|
|
||||||
- **GDPR:** add GDPR.yml file to public/.well-known/
|
|
||||||
([86bccc3](https://code.castopod.org/adaures/castopod/commit/86bccc3d5cc9562b89196f1766ac91cdc8ad786d))
|
|
||||||
|
|
||||||
# [1.0.0-beta.9](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.8...v1.0.0-beta.9) (2022-03-04)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **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)
|
|
||||||
- escape characters for `min` in format_duration_symbol
|
|
||||||
([3b6722a](https://code.castopod.org/adaures/castopod/commit/3b6722a42b9e4330e5235d4ceed41c777159f4dc))
|
|
||||||
- **security:** add csrf filter + prevent xss attacks by escaping user input
|
|
||||||
([cd2e1e1](https://code.castopod.org/adaures/castopod/commit/cd2e1e1dc37c53d32d00971c451c4800b8fd6107))
|
|
||||||
- update ivoox podcasting icon
|
|
||||||
([f2b69a4](https://code.castopod.org/adaures/castopod/commit/f2b69a47339c887f57883ec612f3d200e512ac1c))
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **i18n:** add Polish translation
|
|
||||||
([2d83b44](https://code.castopod.org/adaures/castopod/commit/2d83b44add9e4e00766a1f326377ed892f48ad73))
|
|
||||||
- **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)
|
|
||||||
- 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)
|
|
||||||
- **podcasting 2.0:** update podcast:social tag to adhere to latest spec
|
|
||||||
([a597cf4](https://code.castopod.org/adaures/castopod/commit/a597cf4ecfa6807a3413177d99c816056a7e7c45))
|
|
||||||
|
|
||||||
# [1.0.0-beta.8](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.7...v1.0.0-beta.8) (2022-02-10)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **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))
|
|
||||||
|
|
||||||
# [1.0.0-beta.7](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.6...v1.0.0-beta.7) (2022-02-05)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **activitypub:** allow cors on get requests for routes exposing acitivitypub
|
|
||||||
objects
|
|
||||||
([2f24809](https://code.castopod.org/adaures/castopod/commit/2f2480998f9abb34f02ab186c65d462a74b4e640))
|
|
||||||
- **fediverse:** set model instances as non shared to prevent overlapping
|
|
||||||
([91128fa](https://code.castopod.org/adaures/castopod/commit/91128fad7a68e1f4e5acacba90b6899288699e61))
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
# [1.0.0-beta.6](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.5...v1.0.0-beta.6) (2022-02-03)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **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))
|
|
||||||
- **http-signature:** update SIGNATURE_PATTERN allowing signature keys to be
|
|
||||||
sent in any order
|
|
||||||
([b7f285e](https://code.castopod.org/adaures/castopod/commit/b7f285e4e24247fedb94f030356fa6f291f525cc))
|
|
||||||
- **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)
|
|
||||||
- **markdown-editor:** remove unnecessary buttons for podcast and episode
|
|
||||||
editors + add extensions
|
|
||||||
([9c4f60e](https://code.castopod.org/adaures/castopod/commit/9c4f60e00bcbd4f784f12d2a6fed357ad402ee2e))
|
|
||||||
- **podcast-activity:** check if transcript and chapters are set before
|
|
||||||
including them in audio
|
|
||||||
([5855a25](https://code.castopod.org/adaures/castopod/commit/5855a250936f91641efef77650890a18d8e9917f))
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
# [1.0.0-beta.5](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.4...v1.0.0-beta.5) (2022-01-31)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **analytics:** set initial value for duration and bandwidth
|
|
||||||
([ee50539](https://code.castopod.org/adaures/castopod/commit/ee5053959154b1a2e5fbe4b43162968425206a26))
|
|
||||||
|
|
||||||
# [1.0.0-beta.4](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.3...v1.0.0-beta.4) (2022-01-29)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **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))
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **housekeeping:** add clear_cache option to flush redis or files cache
|
|
||||||
([99bfac0](https://code.castopod.org/adaures/castopod/commit/99bfac0b428a4bc6fe8bfd10a355dfd93f42ba5c))
|
|
||||||
|
|
||||||
# [1.0.0-beta.3](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.2...v1.0.0-beta.3) (2022-01-28)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- revert to beta.1's codeigniter4 version
|
|
||||||
([e831411](https://code.castopod.org/adaures/castopod/commit/e83141127080ccde44987195db46ba97fd6cc2ca))
|
|
||||||
|
|
||||||
# [1.0.0-beta.2](https://code.castopod.org/adaures/castopod/compare/v1.0.0-beta.1...v1.0.0-beta.2) (2022-01-28)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **migrations:** ignore invalid utf8 chars for media files metadata + update
|
|
||||||
transcript parser
|
|
||||||
([45e8f99](https://code.castopod.org/adaures/castopod/commit/45e8f99e753cc02ec105e6f4d7fe026a205724f8))
|
|
||||||
- **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))
|
|
||||||
|
|
||||||
# 1.0.0-beta.1 (2022-01-23)
|
|
||||||
|
|
||||||
### 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:** 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 head request to analytics_hit route
|
|
||||||
([f0a2f0b](https://code.castopod.org/adaures/castopod/commit/f0a2f0bea491ca91976b351bb79837e95c9d094b))
|
|
||||||
- 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 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:** 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:** 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))
|
|
||||||
- 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))
|
|
||||||
- **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)
|
|
||||||
- **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 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)
|
|
||||||
- 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))
|
|
||||||
- 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)
|
|
||||||
- **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 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))
|
|
||||||
- **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)
|
|
||||||
- **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))
|
|
||||||
- **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)
|
|
||||||
- minor corrections
|
|
||||||
([13be386](https://code.castopod.org/adaures/castopod/commit/13be386842e94d9def1f7de4720931d8f6935171))
|
|
||||||
- move analytics to helper
|
|
||||||
([d311917](https://code.castopod.org/adaures/castopod/commit/d31191732e41aa106234b5ebe6e54ee02f0ce603))
|
|
||||||
- **multiselect:** add missing class names in choices options for purge to work
|
|
||||||
properly
|
|
||||||
([719538d](https://code.castopod.org/adaures/castopod/commit/719538d0ccb28af3c3c5e1a4b6468d4b772fe819))
|
|
||||||
- **open-graph:** replace non existant episode description to podcast
|
|
||||||
description in podcast page
|
|
||||||
([b02584e](https://code.castopod.org/adaures/castopod/commit/b02584ee609af1ad1b5680cc28208d113eb0410b))
|
|
||||||
- **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)
|
|
||||||
- **podcast-import:** move guid attribute declaration for Episode entity to
|
|
||||||
include slug data
|
|
||||||
([5d02ae3](https://code.castopod.org/adaures/castopod/commit/5d02ae39908a9d743627135b372bf981134c4328))
|
|
||||||
- **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 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 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)
|
|
||||||
- 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 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))
|
|
||||||
- rewrite regenerate image function to use saveSizes method from Image entity
|
|
||||||
([3889912](https://code.castopod.org/adaures/castopod/commit/38899124ec27e94a8c798bc2db528f9f785eec20))
|
|
||||||
- **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:** 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))
|
|
||||||
- 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 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))
|
|
||||||
- **settings:** add .jpg extension to site-icon file input to display all jpeg
|
|
||||||
images
|
|
||||||
([f611a16](https://code.castopod.org/adaures/castopod/commit/f611a16cd0c1a389e1c5a287eaec9d2a927a4bb6))
|
|
||||||
- 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))
|
|
||||||
- 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 iso-369 language table seeder
|
|
||||||
([0c90db4](https://code.castopod.org/adaures/castopod/commit/0c90db44c40de5af5b0b32b54489bda9424d9ef6))
|
|
||||||
- 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))
|
|
||||||
- **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:** redirect user to install page on database error in home page
|
|
||||||
([9017e30](https://code.castopod.org/adaures/castopod/commit/9017e30bf41bed8c2be65091bbc5fb1e63aef87a))
|
|
||||||
- **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:** tweak portrait parameters to have subtitles display without
|
|
||||||
overflowing
|
|
||||||
([2385b1a](https://code.castopod.org/adaures/castopod/commit/2385b1a2926d1344569836e18cb30adb4c604664))
|
|
||||||
- **xml-editor:** escape xml editor's content + restyle form sections to prevent
|
|
||||||
overflowing
|
|
||||||
([588590b](https://code.castopod.org/adaures/castopod/commit/588590bd2c0346e2465ff8f1930580d76a3bf068))
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **activitypub:** add Podcast actor and PodcastEpisode object with comments
|
|
||||||
([9e1e5d2](https://code.castopod.org/adaures/castopod/commit/9e1e5d2e862d6a3345d11ca7f96b955c76bfa013))
|
|
||||||
- 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 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 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 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 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 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 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 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 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)
|
|
||||||
- **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:** 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 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))
|
|
||||||
- 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))
|
|
||||||
- **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))
|
|
||||||
- **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:** 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))
|
|
||||||
- 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))
|
|
||||||
- **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:** 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))
|
|
||||||
- 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 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
|
|
||||||
|
|
||||||
- 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-alpha.80](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.79...v1.0.0-alpha.80) (2021-12-29)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- 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)
|
|
||||||
|
|
||||||
# [1.0.0-alpha.79](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.78...v1.0.0-alpha.79) (2021-12-20)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **import:** set episode and season numbers to null when not present in item
|
|
||||||
tag
|
|
||||||
([3211398](https://code.castopod.org/adaures/castopod/commit/3211398c78b1b28b76a46427ee07874bbf84a85d))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.78](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.77...v1.0.0-alpha.78) (2021-12-15)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **import:** add extension when downloading file without + truncate slug if too
|
|
||||||
long
|
|
||||||
([c5f18bb](https://code.castopod.org/adaures/castopod/commit/c5f18bb6dc08a758ff735454bbe9cfa45a68c09b))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.77](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.76...v1.0.0-alpha.77) (2021-11-23)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **cors:** add preflight option routes for episode, podcast and status objects
|
|
||||||
([a281abf](https://code.castopod.org/adaures/castopod/commit/a281abfda475388a07943c169dab460cc2d4f944))
|
|
||||||
- **podcast-import:** move guid attribute declaration for Episode entity to
|
|
||||||
include slug data
|
|
||||||
([5d02ae3](https://code.castopod.org/adaures/castopod/commit/5d02ae39908a9d743627135b372bf981134c4328))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.76](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.75...v1.0.0-alpha.76) (2021-10-26)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- 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)
|
|
||||||
|
|
||||||
# [1.0.0-alpha.75](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.74...v1.0.0-alpha.75) (2021-10-05)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
# [1.0.0-alpha.74](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.73...v1.0.0-alpha.74) (2021-09-28)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **platforms:** add missing newpodcastapps.com's platforms
|
|
||||||
([92dd370](https://code.castopod.org/adaures/castopod/commit/92dd370e2f9a464edd26cddcde96d0e16f91548d))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.73](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.72...v1.0.0-alpha.73) (2021-09-22)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **map:** update episode markers query to discard unpublished episodes
|
|
||||||
([b3caac4](https://code.castopod.org/adaures/castopod/commit/b3caac45b12a23e4289d00133d2ad7915d084c44))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.72](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.71...v1.0.0-alpha.72) (2021-09-20)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- rename field status to task_status to get scheduled activities
|
|
||||||
([4ff82a5](https://code.castopod.org/adaures/castopod/commit/4ff82a5f0a38dbbc9e272fca7df70ea5a190e334))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.71](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.70...v1.0.0-alpha.71) (2021-09-17)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **map:** display geolocated episodes on a map page
|
|
||||||
([4357cc2](https://code.castopod.org/adaures/castopod/commit/4357cc25ccc585ce398035c1c25d566b6a9df775))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.70](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.69...v1.0.0-alpha.70) (2021-08-31)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **partner:** set correct image URL
|
|
||||||
([61554be](https://code.castopod.org/adaures/castopod/commit/61554be12a64d59ab99fab810b1b05632b408f3a))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.69](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.68...v1.0.0-alpha.69) (2021-08-23)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **import:** cast description's SimpleXMLElement to string
|
|
||||||
([02d17be](https://code.castopod.org/adaures/castopod/commit/02d17be4ffe229fc6657207d31eba0543b5f1a4c))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.68](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.67...v1.0.0-alpha.68) (2021-08-19)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **analytics:** redirect to mp3 file even when referer was not set
|
|
||||||
([9fc388d](https://code.castopod.org/adaures/castopod/commit/9fc388d154f29c335dedcd624abe8c1751762c07))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.67](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.66...v1.0.0-alpha.67) (2021-07-24)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- allow cross origin requests on episode comments
|
|
||||||
([e12f95a](https://code.castopod.org/adaures/castopod/commit/e12f95aca13c6d54489a9cfd99d4cd2490fe83ab))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.66](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.65...v1.0.0-alpha.66) (2021-07-24)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **rss:** add podcast:comments tag to link to episode comments
|
|
||||||
([32e8c7c](https://code.castopod.org/adaures/castopod/commit/32e8c7c16a61ffe08e2f3bfbdeda556811a0358c))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.65](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.64...v1.0.0-alpha.65) (2021-07-22)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- update conditions when checking for empty max_episodes and season_number
|
|
||||||
([fbad0b5](https://code.castopod.org/adaures/castopod/commit/fbad0b59f68c65eba2fdcd5a8d3b312b622e9a45))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.64](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.63...v1.0.0-alpha.64) (2021-07-12)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **activitypub:** add Podcast actor and PodcastEpisode object with comments
|
|
||||||
([9e1e5d2](https://code.castopod.org/adaures/castopod/commit/9e1e5d2e862d6a3345d11ca7f96b955c76bfa013))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.63](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.62...v1.0.0-alpha.63) (2021-07-12)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 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)
|
|
||||||
|
|
||||||
# [1.0.0-alpha.62](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.61...v1.0.0-alpha.62) (2021-07-02)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **episode:** replace guid's empty string value to null
|
|
||||||
([441052a](https://code.castopod.org/adaures/castopod/commit/441052af8d99e6e317edefd1e58ad71799357088))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.61](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.60...v1.0.0-alpha.61) (2021-06-23)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **release:** add missing version number to castopod-host package
|
|
||||||
([8f3e9d9](https://code.castopod.org/adaures/castopod/commit/8f3e9d90c14545d3f84d4469b26a53db4554b4dc))
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
# [1.0.0-alpha.60](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.59...v1.0.0-alpha.60) (2021-06-21)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **rss:** add ˂podcast:guid˃ tag for channel
|
|
||||||
([1fab10e](https://code.castopod.org/adaures/castopod/commit/1fab10eb0d63bb7c3edf34ffe691e2aec2c2e43c))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.59](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.58...v1.0.0-alpha.59) (2021-06-15)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- check that additional files are valid when creating episode
|
|
||||||
([eac5bc8](https://code.castopod.org/adaures/castopod/commit/eac5bc876de125e1fe08d1b89f767a04fc0fbfb6))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.58](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.57...v1.0.0-alpha.58) (2021-06-11)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- cast actor_id to pass as int to set_interact_as_actor() function
|
|
||||||
([56a8e5d](https://code.castopod.org/adaures/castopod/commit/56a8e5d7dd615322aeb007e730801c65d0b02e5c))
|
|
||||||
- **analytics:** set duration field to precise decimal as episode's audio file
|
|
||||||
duration
|
|
||||||
([d772685](https://code.castopod.org/adaures/castopod/commit/d77268540569b2be9d91d5e09aefb3ff5ac2b071))
|
|
||||||
- **analytics:** update migrations to set decimal precision for latitude and
|
|
||||||
longitude
|
|
||||||
([714d6b5](https://code.castopod.org/adaures/castopod/commit/714d6b5d4950e52cf1c3170bb59954f98ffd48bd))
|
|
||||||
- check for database connection and podcasts table existence before redirecting
|
|
||||||
to install
|
|
||||||
([eb74e81](https://code.castopod.org/adaures/castopod/commit/eb74e81c3d93581e310b391cd029e62a0d690a8a))
|
|
||||||
- save transcript and chapters files to podcasts folder
|
|
||||||
([63f49c7](https://code.castopod.org/adaures/castopod/commit/63f49c719f672b615c5a8893d3868dffcd332e47))
|
|
||||||
- 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 location to null when getting empty string
|
|
||||||
([71b1b5f](https://code.castopod.org/adaures/castopod/commit/71b1b5f775af475b1dc78328330e277f565e41b6))
|
|
||||||
- update condition in home controller to redirect to install page
|
|
||||||
([33f1b91](https://code.castopod.org/adaures/castopod/commit/33f1b91d55dd0652c979d50fc85879dbf88a4a42))
|
|
||||||
- **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))
|
|
||||||
- **category:** remove uncategorized option to enforce users in choosing a
|
|
||||||
category
|
|
||||||
([8c64f25](https://code.castopod.org/adaures/castopod/commit/8c64f25a0e72fec03d25544797d32623b2276fce))
|
|
||||||
- **install:** redirect manually to install wizard on first visit
|
|
||||||
([2ceaaca](https://code.castopod.org/adaures/castopod/commit/2ceaaca44f1b82fc64d961e2fb4f4aaeade7e736))
|
|
||||||
- **types:** update fake seeders types + fix bugs
|
|
||||||
([76a4bf3](https://code.castopod.org/adaures/castopod/commit/76a4bf344160df679db29e236e7df7822970fb60))
|
|
||||||
- update broken contributor dropdown fields
|
|
||||||
([e5b7515](https://code.castopod.org/adaures/castopod/commit/e5b75150234bd7f19e01def93425d3bda7379dd3))
|
|
||||||
- **ux:** redirect user to install page on database error in home page
|
|
||||||
([9017e30](https://code.castopod.org/adaures/castopod/commit/9017e30bf41bed8c2be65091bbc5fb1e63aef87a))
|
|
||||||
- update condition in AnalyticsTrait
|
|
||||||
([fbc0967](https://code.castopod.org/adaures/castopod/commit/fbc0967caa81630d514ddb1b93b0834ebb4d913b))
|
|
||||||
|
|
||||||
### Performance Improvements
|
|
||||||
|
|
||||||
- **cache:** use deleteMatching method to prevent forgetting cached elements in
|
|
||||||
models
|
|
||||||
([76afc0c](https://code.castopod.org/adaures/castopod/commit/76afc0cfa2feb087697bae4bc138e4956873dd62))
|
|
||||||
|
|
||||||
### Reverts
|
|
||||||
|
|
||||||
- set deprecated config options back in App config
|
|
||||||
([433745f](https://code.castopod.org/adaures/castopod/commit/433745f194c73407999b207090478563283876a5))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.57](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.56...v1.0.0-alpha.57) (2021-05-12)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **follow:** add missing helpers to Actor controller
|
|
||||||
([ee53a73](https://code.castopod.org/adaures/castopod/commit/ee53a732dc12ebbf5706e14969749a12cfd9d559))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.56](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.55...v1.0.0-alpha.56) (2021-05-12)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **rss:** use originalPath instead of originalMediaPath in Image library
|
|
||||||
([b4012b7](https://code.castopod.org/adaures/castopod/commit/b4012b7d2ed6b34b69ad767570dd33f0dc7db920))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.55](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.54...v1.0.0-alpha.55) (2021-05-03)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- add remote_url alternative for transcript and chapters files
|
|
||||||
([3143c9a](https://code.castopod.org/adaures/castopod/commit/3143c9ad36e4cf1364205cf2be39c0c96f80fdd2))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.54](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.53...v1.0.0-alpha.54) (2021-05-03)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- set app parameter forceGlobalSecureRequests = true forcing requests to go
|
|
||||||
through https
|
|
||||||
([d9dff1b](https://code.castopod.org/adaures/castopod/commit/d9dff1b8bf89c8b526ad6cb89f98a1f160d49117))
|
|
||||||
- **ux:** remove admin dashboard and redirect directly to podcast list
|
|
||||||
([27c48b8](https://code.castopod.org/adaures/castopod/commit/27c48b8fa930b33e5e15f0c8685e468e857ca9cd))
|
|
||||||
- add cache to ActivityPub sql queries + cache activity and note pages
|
|
||||||
([2d297f4](https://code.castopod.org/adaures/castopod/commit/2d297f45b3d7ef6e8711875a0b9b908e878115fa))
|
|
||||||
|
|
||||||
### Performance Improvements
|
|
||||||
|
|
||||||
- **cache:** update CI4 to use cache's deleteMatching method
|
|
||||||
([54b84f9](https://code.castopod.org/adaures/castopod/commit/54b84f96843af13f579fea49102c8c2ef81b0a54))
|
|
||||||
- **docker:** add redis caching service for development
|
|
||||||
([05ace8c](https://code.castopod.org/adaures/castopod/commit/05ace8cff2ef02d19abd40097ac5546dca6a54ca))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.53](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.52...v1.0.0-alpha.53) (2021-04-16)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- 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)
|
|
||||||
|
|
||||||
# [1.0.0-alpha.52](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.51...v1.0.0-alpha.52) (2021-04-16)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
# [1.0.0-alpha.51](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.50...v1.0.0-alpha.51) (2021-04-15)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
# [1.0.0-alpha.50](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.49...v1.0.0-alpha.50) (2021-04-14)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **persons:** prevent overflow of persons list by adding horizontal scroll
|
|
||||||
([9e8995d](https://code.castopod.org/adaures/castopod/commit/9e8995dc6e039032cc65f87895cf770f99e8b244))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.49](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.48...v1.0.0-alpha.49) (2021-04-12)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **multiselect:** add missing class names in choices options for purge to work
|
|
||||||
properly
|
|
||||||
([719538d](https://code.castopod.org/adaures/castopod/commit/719538d0ccb28af3c3c5e1a4b6468d4b772fe819))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.48](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.47...v1.0.0-alpha.48) (2021-04-10)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **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))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.47](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.46...v1.0.0-alpha.47) (2021-04-10)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **episodeCount:** add missing brackets to French language file
|
|
||||||
([c1b4112](https://code.castopod.org/adaures/castopod/commit/c1b411265ad9b06e95a8b097ecf73445b88dcb45))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.46](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.45...v1.0.0-alpha.46) (2021-04-09)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
# [1.0.0-alpha.45](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.44...v1.0.0-alpha.45) (2021-04-08)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- add head request to analytics_hit route
|
|
||||||
([f0a2f0b](https://code.castopod.org/adaures/castopod/commit/f0a2f0bea491ca91976b351bb79837e95c9d094b))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.44](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.43...v1.0.0-alpha.44) (2021-04-08)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
# [1.0.0-alpha.43](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.42...v1.0.0-alpha.43) (2021-04-08)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
# [1.0.0-alpha.42](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.41...v1.0.0-alpha.42) (2021-04-02)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
# [1.0.0-alpha.41](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.40...v1.0.0-alpha.41) (2021-03-30)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **partner:** add link and image in episode description
|
|
||||||
([ad07bb9](https://code.castopod.org/adaures/castopod/commit/ad07bb9330dc9493813368e969e1f3a3def44614))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.40](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.39...v1.0.0-alpha.40) (2021-03-19)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **custom-rss:** add custom xml tag injection in rss feed for ❬channel❭ and
|
|
||||||
❬item❭
|
|
||||||
([6ecdaad](https://code.castopod.org/adaures/castopod/commit/6ecdaad911d06b7f7a2b7d24710968c7eb9118f6))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.39](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.38...v1.0.0-alpha.39) (2021-03-01)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **embeddable-player:** enable any ancestor when X-Frame-Options is set on
|
|
||||||
server
|
|
||||||
([44a4962](https://code.castopod.org/adaures/castopod/commit/44a4962e0b7e3ed87e9914b4e7792a0d52330ff8))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.38](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.37...v1.0.0-alpha.38) (2021-02-27)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **embeddable-player:** add embeddable player widget
|
|
||||||
([141788f](https://code.castopod.org/adaures/castopod/commit/141788fa089f9dedc8956c64ca515a4a4625f904))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.37](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.36...v1.0.0-alpha.37) (2021-02-17)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **import:** remove query string from files url
|
|
||||||
([109c4aa](https://code.castopod.org/adaures/castopod/commit/109c4aa1afb72dd8b99c0302d74a7fef5a38638e))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.36](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.35...v1.0.0-alpha.36) (2021-02-16)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **platforms:** add pod.link
|
|
||||||
([3d7a232](https://code.castopod.org/adaures/castopod/commit/3d7a2320ddd116e4a311605421126aff57243219))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.35](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.34...v1.0.0-alpha.35) (2021-02-12)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **admin:** save block and lock switches
|
|
||||||
([b66c0af](https://code.castopod.org/adaures/castopod/commit/b66c0afc8fab2e338402a9a4f8105e5f5459e208))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.34](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.33...v1.0.0-alpha.34) (2021-02-11)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **rss-import:** add Castopod user-agent, handle redirects for downloaded
|
|
||||||
files, add Content namespace
|
|
||||||
([214243b](https://code.castopod.org/adaures/castopod/commit/214243b3fec4937e45ef1ceaba1149004cdf3b44))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.33](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.32...v1.0.0-alpha.33) (2021-02-10)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **platforms:** add helloasso
|
|
||||||
([16cb993](https://code.castopod.org/adaures/castopod/commit/16cb993ee6e28987a840fc27a9c2c73794c67697))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.32](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.31...v1.0.0-alpha.32) (2021-02-10)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **person:** add podcastindex.org namespace person tag
|
|
||||||
([8acd011](https://code.castopod.org/adaures/castopod/commit/8acd011f13e99492ef4b44b327685bb006fe5f8f))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.31](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.30...v1.0.0-alpha.31) (2020-12-23)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **rss:** add podcast:location tag
|
|
||||||
([c0a2282](https://code.castopod.org/adaures/castopod/commit/c0a22829bd87d48535a86e60c6cd7280e44683a2))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.30](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.29...v1.0.0-alpha.30) (2020-12-21)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **rss:** update monetization tag so that it meets PodcastIndex requirements
|
|
||||||
([4c7ecbe](https://code.castopod.org/adaures/castopod/commit/4c7ecbee83950e5f9f2482cedaab18a1ac9bfc9e))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.29](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.28...v1.0.0-alpha.29) (2020-12-10)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
### Reverts
|
|
||||||
|
|
||||||
- **soundbites:** remove soundbite table from episode's public page
|
|
||||||
([5dc0f19](https://code.castopod.org/adaures/castopod/commit/5dc0f19656de0d764f627d6ae78a9e306c901835))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.28](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.27...v1.0.0-alpha.28) (2020-12-07)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
# [1.0.0-alpha.27](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.26...v1.0.0-alpha.27) (2020-12-07)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **platforms:** add AntennaPod
|
|
||||||
([53e9cfd](https://code.castopod.org/adaures/castopod/commit/53e9cfd61c794b1539e9d4691d3c4e73c4b7aaa7))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.26](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.25...v1.0.0-alpha.26) (2020-11-30)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **analytics:** update service management so that it works with new OPAWG slug
|
|
||||||
values
|
|
||||||
([7fe9d42](https://code.castopod.org/adaures/castopod/commit/7fe9d42500ade2c6fa3ff4365b4affc475af0e51))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.25](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.24...v1.0.0-alpha.25) (2020-11-30)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **platforms:** add podfriend
|
|
||||||
([9fdc8d3](https://code.castopod.org/adaures/castopod/commit/9fdc8d32930234c7ffd2be6892be57febcef1086))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.24](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.23...v1.0.0-alpha.24) (2020-11-26)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **monetization:** add Web Monetization support
|
|
||||||
([96a6026](https://code.castopod.org/adaures/castopod/commit/96a6026f1db452085360f5fe248de82a2ec06468))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.23](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.22...v1.0.0-alpha.23) (2020-11-24)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- define podcastNamespaceLink value
|
|
||||||
([0d744d2](https://code.castopod.org/adaures/castopod/commit/0d744d212df0d070ceea185068eaf2746e1ccd48))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.22](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.21...v1.0.0-alpha.22) (2020-11-24)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
# [1.0.0-alpha.21](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.20...v1.0.0-alpha.21) (2020-11-24)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **platforms:** add Fediverse and some funding platforms, add link on logo
|
|
||||||
([afc3d50](https://code.castopod.org/adaures/castopod/commit/afc3d50289bb4173e0697d109ffe72f6814b93d1))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.20](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.19...v1.0.0-alpha.20) (2020-11-24)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **import:** use <image><url> tag when no <itunes:image> is present
|
|
||||||
([20e607a](https://code.castopod.org/adaures/castopod/commit/20e607afb755bc75056041738fa7cbf6723d754c))
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
# [1.0.0-alpha.19](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.18...v1.0.0-alpha.19) (2020-11-13)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- 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)
|
|
||||||
|
|
||||||
# [1.0.0-alpha.18](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.17...v1.0.0-alpha.18) (2020-11-09)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **platforms:** add Podcast Index
|
|
||||||
([ad52b1c](https://code.castopod.org/adaures/castopod/commit/ad52b1cc2b7d0bc844970214d205961a7196b4a9))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.17](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.16...v1.0.0-alpha.17) (2020-11-05)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **open-graph:** replace non existant episode description to podcast
|
|
||||||
description in podcast page
|
|
||||||
([b02584e](https://code.castopod.org/adaures/castopod/commit/b02584ee609af1ad1b5680cc28208d113eb0410b))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.16](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.15...v1.0.0-alpha.16) (2020-11-04)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 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)
|
|
||||||
|
|
||||||
# [1.0.0-alpha.15](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.14...v1.0.0-alpha.15) (2020-11-03)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **analytics:** add 'other' group to pie charts in order to display more
|
|
||||||
accurate data
|
|
||||||
([73acef9](https://code.castopod.org/adaures/castopod/commit/73acef933ff3485987afc5157de022910876fc12))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.14](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.13...v1.0.0-alpha.14) (2020-11-02)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **analytics:** add weekday and hour bar charts
|
|
||||||
([8ab3132](https://code.castopod.org/adaures/castopod/commit/8ab313296bb4a254ab05e90b17d896039839b784))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.13](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.12...v1.0.0-alpha.13) (2020-10-29)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **episodes-table:** set descriptions to be not null
|
|
||||||
([6774ec1](https://code.castopod.org/adaures/castopod/commit/6774ec10fa78527be6b7548ca1dc34ad0ada090c))
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- add episode_numbering() component helper to display episode and season numbers
|
|
||||||
([3f4a6bd](https://code.castopod.org/adaures/castopod/commit/3f4a6bd0b9f870f16107a41b102b6bf734868198))
|
|
||||||
- **episodes:** replace all audio file URL parameters with base64 encoded data
|
|
||||||
([e1f65cd](https://code.castopod.org/adaures/castopod/commit/e1f65cd3b53353a30d4ab6eb5312393cf04a1676))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.12](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.11...v1.0.0-alpha.12) (2020-10-26)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- replace getWebEnclosureUrl with getEnclosureWebUrl
|
|
||||||
([8122cea](https://code.castopod.org/adaures/castopod/commit/8122ceaf8a70050f14b3078f28b024e7d7cdb9ac))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.11](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.10...v1.0.0-alpha.11) (2020-10-26)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- add CDN url
|
|
||||||
([972bcbf](https://code.castopod.org/adaures/castopod/commit/972bcbf65ee119b8641ca3c4e5c0e8cf9ca8dd4f)),
|
|
||||||
closes [#37](https://code.castopod.org/adaures/castopod/issues/37)
|
|
||||||
|
|
||||||
# [1.0.0-alpha.10](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.9...v1.0.0-alpha.10) (2020-10-26)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **install:** redirect to host_url install route on instanceConfig validation
|
|
||||||
error
|
|
||||||
([99250b1](https://code.castopod.org/adaures/castopod/commit/99250b1868657c249a447399c7ebc69e00d43d1a))
|
|
||||||
|
|
||||||
# [1.0.0-alpha.9](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.8...v1.0.0-alpha.9) (2020-10-26)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 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)
|
|
||||||
|
|
||||||
# [1.0.0-alpha.8](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.7...v1.0.0-alpha.8) (2020-10-22)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
# [1.0.0-alpha.7](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.6...v1.0.0-alpha.7) (2020-10-21)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **analytics:** add service name from rss user-agent
|
|
||||||
([7202b98](https://code.castopod.org/adaures/castopod/commit/7202b9867bd59aafa8c338a4230fb5e5c55b24c6))
|
|
||||||
|
|
||||||
### BREAKING CHANGES
|
|
||||||
|
|
||||||
- **analytics:** analytics_podcasts_by_player table and analytics_podcasts
|
|
||||||
procedure were updated
|
|
||||||
|
|
||||||
# [1.0.0-alpha.6](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.5...v1.0.0-alpha.6) (2020-10-20)
|
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
- **cache:** add locale for podcast and episode pages + clear some persisting
|
- **cache:** add locale for podcast and episode pages + clear some persisting
|
||||||
cache in models
|
cache in models
|
||||||
([9cec8a8](https://code.castopod.org/adaures/castopod/commit/9cec8a81ccbb7239402fe6633dbc31979272302a)),
|
([9cec8a8](https://code.podlibre.org/podlibre/castopod/commit/9cec8a81ccbb7239402fe6633dbc31979272302a)),
|
||||||
closes [#42](https://code.castopod.org/adaures/castopod/issues/42)
|
closes [#42](https://code.podlibre.org/podlibre/castopod/issues/42)
|
||||||
[#61](https://code.castopod.org/adaures/castopod/issues/61)
|
[#61](https://code.podlibre.org/podlibre/castopod/issues/61)
|
||||||
|
|
||||||
# [1.0.0-alpha.5](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.4...v1.0.0-alpha.5) (2020-10-20)
|
# [1.0.0-alpha.5](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.4...v1.0.0-alpha.5) (2020-10-20)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- add lock podcast according to the Podcastindex podcast-namespace to prevent
|
- add lock podcast according to the Podcastindex podcast-namespace to prevent
|
||||||
unauthozized import
|
unauthozized import
|
||||||
([72b3012](https://code.castopod.org/adaures/castopod/commit/72b301272e0b70ded3e2b237391909e3f152ad0b))
|
([72b3012](https://code.podlibre.org/podlibre/castopod/commit/72b301272e0b70ded3e2b237391909e3f152ad0b))
|
||||||
|
|
||||||
# [1.0.0-alpha.4](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.3...v1.0.0-alpha.4) (2020-10-20)
|
# [1.0.0-alpha.4](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.3...v1.0.0-alpha.4) (2020-10-20)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- **analytics:** add charts and data export
|
- **analytics:** add charts and data export
|
||||||
([78625c4](https://code.castopod.org/adaures/castopod/commit/78625c471b4f03a09bd42f72b82217e1f2d01cef))
|
([78625c4](https://code.podlibre.org/podlibre/castopod/commit/78625c471b4f03a09bd42f72b82217e1f2d01cef))
|
||||||
|
|
||||||
# [1.0.0-alpha.3](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.2...v1.0.0-alpha.3) (2020-10-19)
|
# [1.0.0-alpha.3](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.2...v1.0.0-alpha.3) (2020-10-19)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
- **analytics:** remove charts empty values + remove useless language cache
|
- **analytics:** remove charts empty values + remove useless language cache
|
||||||
([1678794](https://code.castopod.org/adaures/castopod/commit/16787941539ba4014281a366789ea896a9cd2afc))
|
([1678794](https://code.podlibre.org/podlibre/castopod/commit/16787941539ba4014281a366789ea896a9cd2afc))
|
||||||
|
|
||||||
# [1.0.0-alpha.2](https://code.castopod.org/adaures/castopod/compare/v1.0.0-alpha.1...v1.0.0-alpha.2) (2020-10-19)
|
# [1.0.0-alpha.2](https://code.podlibre.org/podlibre/castopod/compare/v1.0.0-alpha.1...v1.0.0-alpha.2) (2020-10-19)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- add cumulative listening time charts
|
- add cumulative listening time charts
|
||||||
([588b4d2](https://code.castopod.org/adaures/castopod/commit/588b4d28da00bc12d02126e23181690f54d81716))
|
([588b4d2](https://code.podlibre.org/podlibre/castopod/commit/588b4d28da00bc12d02126e23181690f54d81716))
|
||||||
|
|
||||||
# 1.0.0-alpha.1 (2020-10-16)
|
# 1.0.0-alpha.1 (2020-10-16)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
- add public/media folder to castopod bundle
|
- add public/media folder to castopod bundle
|
||||||
([8053d35](https://code.castopod.org/adaures/castopod/commit/8053d3521b481872711dabaaf265d08b9bfbaa87)),
|
([8053d35](https://code.podlibre.org/podlibre/castopod/commit/8053d3521b481872711dabaaf265d08b9bfbaa87)),
|
||||||
closes [#52](https://code.castopod.org/adaures/castopod/issues/52)
|
closes [#52](https://code.podlibre.org/podlibre/castopod/issues/52)
|
||||||
- add where condition to get episode count without deleted episodes
|
- add where condition to get episode count without deleted episodes
|
||||||
([7661734](https://code.castopod.org/adaures/castopod/commit/7661734ed296654630f3668132671117519145dd)),
|
([7661734](https://code.podlibre.org/podlibre/castopod/commit/7661734ed296654630f3668132671117519145dd)),
|
||||||
closes [#67](https://code.castopod.org/adaures/castopod/issues/67)
|
closes [#67](https://code.podlibre.org/podlibre/castopod/issues/67)
|
||||||
- comment all cache clean after page update to prevent analytics cache deletion
|
- comment all cache clean after page update to prevent analytics cache deletion
|
||||||
([e6197a4](https://code.castopod.org/adaures/castopod/commit/e6197a4972a3cce3d67dd7972bb54f8720b8e5b7))
|
([e6197a4](https://code.podlibre.org/podlibre/castopod/commit/e6197a4972a3cce3d67dd7972bb54f8720b8e5b7))
|
||||||
- correct chart data
|
- correct chart data
|
||||||
([4d3e9c8](https://code.castopod.org/adaures/castopod/commit/4d3e9c8c02cdc882e9fe1c29625695b6f83c820a))
|
([4d3e9c8](https://code.podlibre.org/podlibre/castopod/commit/4d3e9c8c02cdc882e9fe1c29625695b6f83c820a))
|
||||||
- correct percona compatibility issue
|
- correct percona compatibility issue
|
||||||
([e53f819](https://code.castopod.org/adaures/castopod/commit/e53f819264b2d6902996f11ffcbb7c99295a90ef))
|
([e53f819](https://code.podlibre.org/podlibre/castopod/commit/e53f819264b2d6902996f11ffcbb7c99295a90ef))
|
||||||
- correct php-fpm issues
|
- correct php-fpm issues
|
||||||
([1ef55d7](https://code.castopod.org/adaures/castopod/commit/1ef55d7315bb44abe05f02ec8a84b6b6a557a9a0))
|
([1ef55d7](https://code.podlibre.org/podlibre/castopod/commit/1ef55d7315bb44abe05f02ec8a84b6b6a557a9a0))
|
||||||
- correct referrer bug
|
- correct referrer bug
|
||||||
([ed69b2f](https://code.castopod.org/adaures/castopod/commit/ed69b2f5004ed1cd18bac824c08a0df01f5d2637))
|
([ed69b2f](https://code.podlibre.org/podlibre/castopod/commit/ed69b2f5004ed1cd18bac824c08a0df01f5d2637))
|
||||||
- correction for servers with low int precision
|
- correction for servers with low int precision
|
||||||
([31b7828](https://code.castopod.org/adaures/castopod/commit/31b7828e77519ef43e9bcfcbdf6c21712f97a571))
|
([31b7828](https://code.podlibre.org/podlibre/castopod/commit/31b7828e77519ef43e9bcfcbdf6c21712f97a571))
|
||||||
- declare typed properties in PHPDoc for php<7.4
|
- declare typed properties in PHPDoc for php<7.4
|
||||||
([14dd44d](https://code.castopod.org/adaures/castopod/commit/14dd44d03d6db0d9ae4198db8e65c92a0e45cb31)),
|
([14dd44d](https://code.podlibre.org/podlibre/castopod/commit/14dd44d03d6db0d9ae4198db8e65c92a0e45cb31)),
|
||||||
closes [#23](https://code.castopod.org/adaures/castopod/issues/23)
|
closes [#23](https://code.podlibre.org/podlibre/castopod/issues/23)
|
||||||
- escape generated feed tag values and remove new lines from public pages meta
|
- escape generated feed tag values and remove new lines from public pages meta
|
||||||
description
|
description
|
||||||
([6238a43](https://code.castopod.org/adaures/castopod/commit/6238a43863210afe8988ad7cf251e6bfc6c8557c)),
|
([6238a43](https://code.podlibre.org/podlibre/castopod/commit/6238a43863210afe8988ad7cf251e6bfc6c8557c)),
|
||||||
closes [#57](https://code.castopod.org/adaures/castopod/issues/57)
|
closes [#57](https://code.podlibre.org/podlibre/castopod/issues/57)
|
||||||
[#46](https://code.castopod.org/adaures/castopod/issues/46)
|
[#46](https://code.podlibre.org/podlibre/castopod/issues/46)
|
||||||
- fix layout bugs in admin and update translation files
|
- fix layout bugs in admin and update translation files
|
||||||
([a834171](https://code.castopod.org/adaures/castopod/commit/a83417180cf61cdfadc5509b0aaa2fdb66592be3)),
|
([a834171](https://code.podlibre.org/podlibre/castopod/commit/a83417180cf61cdfadc5509b0aaa2fdb66592be3)),
|
||||||
closes [#40](https://code.castopod.org/adaures/castopod/issues/40)
|
closes [#40](https://code.podlibre.org/podlibre/castopod/issues/40)
|
||||||
- minor corrections
|
- minor corrections
|
||||||
([13be386](https://code.castopod.org/adaures/castopod/commit/13be386842e94d9def1f7de4720931d8f6935171))
|
([13be386](https://code.podlibre.org/podlibre/castopod/commit/13be386842e94d9def1f7de4720931d8f6935171))
|
||||||
- move analytics to helper
|
- move analytics to helper
|
||||||
([d311917](https://code.castopod.org/adaures/castopod/commit/d31191732e41aa106234b5ebe6e54ee02f0ce603))
|
([d311917](https://code.podlibre.org/podlibre/castopod/commit/d31191732e41aa106234b5ebe6e54ee02f0ce603))
|
||||||
- re-order graph values
|
- re-order graph values
|
||||||
([35f633b](https://code.castopod.org/adaures/castopod/commit/35f633b4c71c087d1ddc9bba9e9bbe18de09204f))
|
([35f633b](https://code.podlibre.org/podlibre/castopod/commit/35f633b4c71c087d1ddc9bba9e9bbe18de09204f))
|
||||||
- remove required for other_categories field and add podcast_id to latest
|
- remove required for other_categories field and add podcast_id to latest
|
||||||
podcasts query
|
podcasts query
|
||||||
([5417be0](https://code.castopod.org/adaures/castopod/commit/5417be0049288489a19c7b575aa77bd1e2bc0243))
|
([5417be0](https://code.podlibre.org/podlibre/castopod/commit/5417be0049288489a19c7b575aa77bd1e2bc0243))
|
||||||
- rename issue_templates labels
|
- rename issue_templates labels
|
||||||
([9f00305](https://code.castopod.org/adaures/castopod/commit/9f00305844e5a168e89d727fe29892b4ad5e48d6))
|
([9f00305](https://code.podlibre.org/podlibre/castopod/commit/9f00305844e5a168e89d727fe29892b4ad5e48d6))
|
||||||
- rename MyAccount controller file
|
- rename MyAccount controller file
|
||||||
([e109df3](https://code.castopod.org/adaures/castopod/commit/e109df3004a3a98d72de39532e062fff9917f50f)),
|
([e109df3](https://code.podlibre.org/podlibre/castopod/commit/e109df3004a3a98d72de39532e062fff9917f50f)),
|
||||||
closes [#60](https://code.castopod.org/adaures/castopod/issues/60)
|
closes [#60](https://code.podlibre.org/podlibre/castopod/issues/60)
|
||||||
- reorder fields as composite primary keys for analytics tables
|
- reorder fields as composite primary keys for analytics tables
|
||||||
([9660aa9](https://code.castopod.org/adaures/castopod/commit/9660aa97c8ffd4fe61f3a388d52b9ac5dd8e1d63))
|
([9660aa9](https://code.podlibre.org/podlibre/castopod/commit/9660aa97c8ffd4fe61f3a388d52b9ac5dd8e1d63))
|
||||||
- replace website key for webpages in breadcrumb translate file
|
- replace website key for webpages in breadcrumb translate file
|
||||||
([50e32ff](https://code.castopod.org/adaures/castopod/commit/50e32ff75636c1d4c5d945a267e884cb26ad7191))
|
([50e32ff](https://code.podlibre.org/podlibre/castopod/commit/50e32ff75636c1d4c5d945a267e884cb26ad7191))
|
||||||
- set episode duration translation to hardcoded english
|
- set episode duration translation to hardcoded english
|
||||||
([c39efc9](https://code.castopod.org/adaures/castopod/commit/c39efc9489180662edcebd142d4476c0617ea97f)),
|
([c39efc9](https://code.podlibre.org/podlibre/castopod/commit/c39efc9489180662edcebd142d4476c0617ea97f)),
|
||||||
closes [#64](https://code.castopod.org/adaures/castopod/issues/64)
|
closes [#64](https://code.podlibre.org/podlibre/castopod/issues/64)
|
||||||
- set episode guid upon episode creation
|
- set episode guid upon episode creation
|
||||||
([ad8b153](https://code.castopod.org/adaures/castopod/commit/ad8b153f2a3b1a3b1751bf63785c4950e1516e6b)),
|
([ad8b153](https://code.podlibre.org/podlibre/castopod/commit/ad8b153f2a3b1a3b1751bf63785c4950e1516e6b)),
|
||||||
closes [#48](https://code.castopod.org/adaures/castopod/issues/48)
|
closes [#48](https://code.podlibre.org/podlibre/castopod/issues/48)
|
||||||
- update purgecss content path for php helper files
|
- update purgecss content path for php helper files
|
||||||
([eb70bb4](https://code.castopod.org/adaures/castopod/commit/eb70bb4f7078ff347aeb8f5dcc7896311d289466)),
|
([eb70bb4](https://code.podlibre.org/podlibre/castopod/commit/eb70bb4f7078ff347aeb8f5dcc7896311d289466)),
|
||||||
closes [#59](https://code.castopod.org/adaures/castopod/issues/59)
|
closes [#59](https://code.podlibre.org/podlibre/castopod/issues/59)
|
||||||
- **install:** redirect to input baseUrl after instance config
|
- **install:** redirect to input baseUrl after instance config
|
||||||
([2426af7](https://code.castopod.org/adaures/castopod/commit/2426af7de8c9d426aaf534ff17b67f71c2e9f374)),
|
([2426af7](https://code.podlibre.org/podlibre/castopod/commit/2426af7de8c9d426aaf534ff17b67f71c2e9f374)),
|
||||||
closes [#53](https://code.castopod.org/adaures/castopod/issues/53)
|
closes [#53](https://code.podlibre.org/podlibre/castopod/issues/53)
|
||||||
- **platforms:** display platform link only when visible is toggled on
|
- **platforms:** display platform link only when visible is toggled on
|
||||||
([6e503c8](https://code.castopod.org/adaures/castopod/commit/6e503c8d6182987e48892370623183f871bbd1c1)),
|
([6e503c8](https://code.podlibre.org/podlibre/castopod/commit/6e503c8d6182987e48892370623183f871bbd1c1)),
|
||||||
closes [#39](https://code.castopod.org/adaures/castopod/issues/39)
|
closes [#39](https://code.podlibre.org/podlibre/castopod/issues/39)
|
||||||
- sort episodic podcasts by season
|
- sort episodic podcasts by season
|
||||||
([d7b6794](https://code.castopod.org/adaures/castopod/commit/d7b6794f68f9a01fd606a407c6eb4c12d15dee74))
|
([d7b6794](https://code.podlibre.org/podlibre/castopod/commit/d7b6794f68f9a01fd606a407c6eb4c12d15dee74))
|
||||||
- update .htaccess for shared hosting config
|
- update .htaccess for shared hosting config
|
||||||
([2379826](https://code.castopod.org/adaures/castopod/commit/2379826352e2f4b5060910bf9f29268610102f2e))
|
([2379826](https://code.podlibre.org/podlibre/castopod/commit/2379826352e2f4b5060910bf9f29268610102f2e))
|
||||||
- update iso-369 language table seeder
|
- update iso-369 language table seeder
|
||||||
([0c90db4](https://code.castopod.org/adaures/castopod/commit/0c90db44c40de5af5b0b32b54489bda9424d9ef6))
|
([0c90db4](https://code.podlibre.org/podlibre/castopod/commit/0c90db44c40de5af5b0b32b54489bda9424d9ef6))
|
||||||
- **package.json:** update destination of postcss generation scripts
|
- **package.json:** update destination of postcss generation scripts
|
||||||
([21413f8](https://code.castopod.org/adaures/castopod/commit/21413f8af3b8a0ac01d8c6f15bcd7a63e524e964))
|
([21413f8](https://code.podlibre.org/podlibre/castopod/commit/21413f8af3b8a0ac01d8c6f15bcd7a63e524e964))
|
||||||
- use slash instead of backslash to call layout
|
- use slash instead of backslash to call layout
|
||||||
([a80adb2](https://code.castopod.org/adaures/castopod/commit/a80adb22958fc0a38374cbce2d950a0042e699eb))
|
([a80adb2](https://code.podlibre.org/podlibre/castopod/commit/a80adb22958fc0a38374cbce2d950a0042e699eb))
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- add alternate rss feed link tag to podcast page head
|
- add alternate rss feed link tag to podcast page head
|
||||||
([a973c09](https://code.castopod.org/adaures/castopod/commit/a973c097d54a3d0186c4079b9d4d3e81aae38505)),
|
([a973c09](https://code.podlibre.org/podlibre/castopod/commit/a973c097d54a3d0186c4079b9d4d3e81aae38505)),
|
||||||
closes [#35](https://code.castopod.org/adaures/castopod/issues/35)
|
closes [#35](https://code.podlibre.org/podlibre/castopod/issues/35)
|
||||||
- add analytics and unknown useragents
|
- add analytics and unknown useragents
|
||||||
([ec92e65](https://code.castopod.org/adaures/castopod/commit/ec92e65aa42e09b1df04600b52a0c679dfc494bb))
|
([ec92e65](https://code.podlibre.org/podlibre/castopod/commit/ec92e65aa42e09b1df04600b52a0c679dfc494bb))
|
||||||
- add breadcrumb in admin area
|
- add breadcrumb in admin area
|
||||||
([7fb1de2](https://code.castopod.org/adaures/castopod/commit/7fb1de2cf3c97c4cd7afe3bd71bbe66041786ecd)),
|
([7fb1de2](https://code.podlibre.org/podlibre/castopod/commit/7fb1de2cf3c97c4cd7afe3bd71bbe66041786ecd)),
|
||||||
closes [#17](https://code.castopod.org/adaures/castopod/issues/17)
|
closes [#17](https://code.podlibre.org/podlibre/castopod/issues/17)
|
||||||
- add french translation
|
- add french translation
|
||||||
([196920d](https://code.castopod.org/adaures/castopod/commit/196920d62f1810b4c35f800d17d7f93627319091))
|
([196920d](https://code.podlibre.org/podlibre/castopod/commit/196920d62f1810b4c35f800d17d7f93627319091))
|
||||||
- add install wizard form to bootstrap database and create the first superadmin
|
- add install wizard form to bootstrap database and create the first superadmin
|
||||||
user
|
user
|
||||||
([cba871c](https://code.castopod.org/adaures/castopod/commit/cba871c5df9f7120c44d9952456ebbd0d220669e)),
|
([cba871c](https://code.podlibre.org/podlibre/castopod/commit/cba871c5df9f7120c44d9952456ebbd0d220669e)),
|
||||||
closes [#2](https://code.castopod.org/adaures/castopod/issues/2)
|
closes [#2](https://code.podlibre.org/podlibre/castopod/issues/2)
|
||||||
- add ISO 3166 country codes
|
- add ISO 3166 country codes
|
||||||
([97cd94b](https://code.castopod.org/adaures/castopod/commit/97cd94b47494b66faf43fbbe0748872da80020a4))
|
([97cd94b](https://code.podlibre.org/podlibre/castopod/commit/97cd94b47494b66faf43fbbe0748872da80020a4))
|
||||||
- add map analytics, add episodes analytics, clean analytics page layout,
|
- add map analytics, add episodes analytics, clean analytics page layout,
|
||||||
translate countries
|
translate countries
|
||||||
([07eae83](https://code.castopod.org/adaures/castopod/commit/07eae83a00d860e149359fae67d549488403d88b))
|
([07eae83](https://code.podlibre.org/podlibre/castopod/commit/07eae83a00d860e149359fae67d549488403d88b))
|
||||||
- add npm for js dependencies + move src/ files to root folder
|
- add npm for js dependencies + move src/ files to root folder
|
||||||
([cbb83a6](https://code.castopod.org/adaures/castopod/commit/cbb83a6f308ac9357e9fb0cca5edae9d3fee5b48))
|
([cbb83a6](https://code.podlibre.org/podlibre/castopod/commit/cbb83a6f308ac9357e9fb0cca5edae9d3fee5b48))
|
||||||
- add pages table to store custom instance pages (eg. legal-notice, cookie
|
- add pages table to store custom instance pages (eg. legal-notice, cookie
|
||||||
policy, etc.)
|
policy, etc.)
|
||||||
([9c224a8](https://code.castopod.org/adaures/castopod/commit/9c224a8ac6dd95f3c6c087a300fc8bac48e8090f)),
|
([9c224a8](https://code.podlibre.org/podlibre/castopod/commit/9c224a8ac6dd95f3c6c087a300fc8bac48e8090f)),
|
||||||
closes [#24](https://code.castopod.org/adaures/castopod/issues/24)
|
closes [#24](https://code.podlibre.org/podlibre/castopod/issues/24)
|
||||||
- add platform models
|
- add platform models
|
||||||
([a333d29](https://code.castopod.org/adaures/castopod/commit/a333d291966229a909c0851fd8b890ed97c48ceb))
|
([a333d29](https://code.podlibre.org/podlibre/castopod/commit/a333d291966229a909c0851fd8b890ed97c48ceb))
|
||||||
- add platforms form in podcast settings
|
- add platforms form in podcast settings
|
||||||
([043f49c](https://code.castopod.org/adaures/castopod/commit/043f49c784bc007ca0fa756ca4ed2d3b08843ad9))
|
([043f49c](https://code.podlibre.org/podlibre/castopod/commit/043f49c784bc007ca0fa756ca4ed2d3b08843ad9))
|
||||||
- add platforms tables
|
- add platforms tables
|
||||||
([ce59344](https://code.castopod.org/adaures/castopod/commit/ce5934419a516c9926dd3fd0ace3c11a95b60722))
|
([ce59344](https://code.podlibre.org/podlibre/castopod/commit/ce5934419a516c9926dd3fd0ace3c11a95b60722))
|
||||||
- add unique listeners analytics
|
- add unique listeners analytics
|
||||||
([3a49258](https://code.castopod.org/adaures/castopod/commit/3a4925816f3268230640525ad7af507aab8eecb9))
|
([3a49258](https://code.podlibre.org/podlibre/castopod/commit/3a4925816f3268230640525ad7af507aab8eecb9))
|
||||||
- add user permissions and basic groups to handle authorizations
|
- add user permissions and basic groups to handle authorizations
|
||||||
([d58e518](https://code.castopod.org/adaures/castopod/commit/d58e51874a4722921b75b0049117015c2380406e)),
|
([d58e518](https://code.podlibre.org/podlibre/castopod/commit/d58e51874a4722921b75b0049117015c2380406e)),
|
||||||
closes [#3](https://code.castopod.org/adaures/castopod/issues/3)
|
closes [#3](https://code.podlibre.org/podlibre/castopod/issues/3)
|
||||||
[#18](https://code.castopod.org/adaures/castopod/issues/18)
|
[#18](https://code.podlibre.org/podlibre/castopod/issues/18)
|
||||||
- create optimized & resized images upon upload
|
- create optimized & resized images upon upload
|
||||||
([02e4441](https://code.castopod.org/adaures/castopod/commit/02e4441f98f27e9534e5b9b63279153d14632ccd)),
|
([02e4441](https://code.podlibre.org/podlibre/castopod/commit/02e4441f98f27e9534e5b9b63279153d14632ccd)),
|
||||||
closes [#6](https://code.castopod.org/adaures/castopod/issues/6)
|
closes [#6](https://code.podlibre.org/podlibre/castopod/issues/6)
|
||||||
- display legal disclaimer and warning on podcast import page
|
- display legal disclaimer and warning on podcast import page
|
||||||
([2f07992](https://code.castopod.org/adaures/castopod/commit/2f07992e5508b34b91f194eebfac80c51e80e90a)),
|
([2f07992](https://code.podlibre.org/podlibre/castopod/commit/2f07992e5508b34b91f194eebfac80c51e80e90a)),
|
||||||
closes [#34](https://code.castopod.org/adaures/castopod/issues/34)
|
closes [#34](https://code.podlibre.org/podlibre/castopod/issues/34)
|
||||||
- edit + delete podcast and episode
|
- edit + delete podcast and episode
|
||||||
([ac5f0c7](https://code.castopod.org/adaures/castopod/commit/ac5f0c732806e955c01e05b7867801bc938c6bd5))
|
([ac5f0c7](https://code.podlibre.org/podlibre/castopod/commit/ac5f0c732806e955c01e05b7867801bc938c6bd5))
|
||||||
- enhance admin ui with responsive design and ux improvements
|
- enhance admin ui with responsive design and ux improvements
|
||||||
([2d44b45](https://code.castopod.org/adaures/castopod/commit/2d44b457a02205d2e7da258d7029b8bc5da39533)),
|
([2d44b45](https://code.podlibre.org/podlibre/castopod/commit/2d44b457a02205d2e7da258d7029b8bc5da39533)),
|
||||||
closes [#31](https://code.castopod.org/adaures/castopod/issues/31)
|
closes [#31](https://code.podlibre.org/podlibre/castopod/issues/31)
|
||||||
[#9](https://code.castopod.org/adaures/castopod/issues/9)
|
[#9](https://code.podlibre.org/podlibre/castopod/issues/9)
|
||||||
- enhance ui using javascript in admin area
|
- enhance ui using javascript in admin area
|
||||||
([c0e66d5](https://code.castopod.org/adaures/castopod/commit/c0e66d5f7012026e145d106f4d6bd3ba792a1b77))
|
([c0e66d5](https://code.podlibre.org/podlibre/castopod/commit/c0e66d5f7012026e145d106f4d6bd3ba792a1b77))
|
||||||
- import podcast from an rss feed url
|
- import podcast from an rss feed url
|
||||||
([9a5d5a1](https://code.castopod.org/adaures/castopod/commit/9a5d5a15b4945eb319da9e999c4ca60a0a4f6d2d)),
|
([9a5d5a1](https://code.podlibre.org/podlibre/castopod/commit/9a5d5a15b4945eb319da9e999c4ca60a0a4f6d2d)),
|
||||||
closes [#21](https://code.castopod.org/adaures/castopod/issues/21)
|
closes [#21](https://code.podlibre.org/podlibre/castopod/issues/21)
|
||||||
- set podcast / episode description in the pages description meta tag
|
- set podcast / episode description in the pages description meta tag
|
||||||
([1c4a504](https://code.castopod.org/adaures/castopod/commit/1c4a50442bea2d3449efce9c5ff1c80743152f55)),
|
([1c4a504](https://code.podlibre.org/podlibre/castopod/commit/1c4a50442bea2d3449efce9c5ff1c80743152f55)),
|
||||||
closes [#44](https://code.castopod.org/adaures/castopod/issues/44)
|
closes [#44](https://code.podlibre.org/podlibre/castopod/issues/44)
|
||||||
- update analytics so to meet IABv2 requirements
|
- update analytics so to meet IABv2 requirements
|
||||||
([03e23a2](https://code.castopod.org/adaures/castopod/commit/03e23a28bf9b1b73fba55352c36a8cd6cc8ae729)),
|
([03e23a2](https://code.podlibre.org/podlibre/castopod/commit/03e23a28bf9b1b73fba55352c36a8cd6cc8ae729)),
|
||||||
closes [#10](https://code.castopod.org/adaures/castopod/issues/10)
|
closes [#10](https://code.podlibre.org/podlibre/castopod/issues/10)
|
||||||
- **cache:** add podcast and episode pages to cache + clear them after insert or
|
- **cache:** add podcast and episode pages to cache + clear them after insert or
|
||||||
update
|
update
|
||||||
([da0f047](https://code.castopod.org/adaures/castopod/commit/da0f0472819007e02e5da37399f2377772c618b9))
|
([da0f047](https://code.podlibre.org/podlibre/castopod/commit/da0f0472819007e02e5da37399f2377772c618b9))
|
||||||
- **categories:** create model, entity, migrations and seeds
|
- **categories:** create model, entity, migrations and seeds
|
||||||
([f73b042](https://code.castopod.org/adaures/castopod/commit/f73b042cc091be82abdbbca8992080875d526972))
|
([f73b042](https://code.podlibre.org/podlibre/castopod/commit/f73b042cc091be82abdbbca8992080875d526972))
|
||||||
- **devcontainer:** add devcontainer settings for dev environment
|
- **devcontainer:** add devcontainer settings for dev environment
|
||||||
([69e7266](https://code.castopod.org/adaures/castopod/commit/69e72667365247b63430dee88194e8f0d7c28edc))
|
([69e7266](https://code.podlibre.org/podlibre/castopod/commit/69e72667365247b63430dee88194e8f0d7c28edc))
|
||||||
- **episodes:** add create form and view pages for episode
|
- **episodes:** add create form and view pages for episode
|
||||||
([f3b2c8b](https://code.castopod.org/adaures/castopod/commit/f3b2c8b84f3d93bef734e34dbe8ed729535e45e9)),
|
([f3b2c8b](https://code.podlibre.org/podlibre/castopod/commit/f3b2c8b84f3d93bef734e34dbe8ed729535e45e9)),
|
||||||
closes [#1](https://code.castopod.org/adaures/castopod/issues/1)
|
closes [#1](https://code.podlibre.org/podlibre/castopod/issues/1)
|
||||||
- **episodes:** add migrations, model and entity for episodes table
|
- **episodes:** add migrations, model and entity for episodes table
|
||||||
([0444821](https://code.castopod.org/adaures/castopod/commit/044482174ede555ce19a2d8c6f48771cc8e7d27b))
|
([0444821](https://code.podlibre.org/podlibre/castopod/commit/044482174ede555ce19a2d8c6f48771cc8e7d27b))
|
||||||
- **podcast:** create a podcast using form
|
- **podcast:** create a podcast using form
|
||||||
([1202ba3](https://code.castopod.org/adaures/castopod/commit/1202ba3545f521097c60a6a2af95e70527cd1d34))
|
([1202ba3](https://code.podlibre.org/podlibre/castopod/commit/1202ba3545f521097c60a6a2af95e70527cd1d34))
|
||||||
- **podcast-form:** update routes and redirect to podcast page
|
- **podcast-form:** update routes and redirect to podcast page
|
||||||
([12ce905](https://code.castopod.org/adaures/castopod/commit/12ce905799002dc9c07e6de092342d30ba9fd7d8))
|
([12ce905](https://code.podlibre.org/podlibre/castopod/commit/12ce905799002dc9c07e6de092342d30ba9fd7d8))
|
||||||
- **public-ui:** adapt public podcast and episode pages to wireframes
|
- **public-ui:** adapt public podcast and episode pages to wireframes
|
||||||
([40a0535](https://code.castopod.org/adaures/castopod/commit/40a0535fc1bc12a24994b651f5e00b35995cbdda)),
|
([40a0535](https://code.podlibre.org/podlibre/castopod/commit/40a0535fc1bc12a24994b651f5e00b35995cbdda)),
|
||||||
closes [#30](https://code.castopod.org/adaures/castopod/issues/30)
|
closes [#30](https://code.podlibre.org/podlibre/castopod/issues/30)
|
||||||
[#13](https://code.castopod.org/adaures/castopod/issues/13)
|
[#13](https://code.podlibre.org/podlibre/castopod/issues/13)
|
||||||
- **rss:** generate rss feed from podcast entity
|
- **rss:** generate rss feed from podcast entity
|
||||||
([c815ecd](https://code.castopod.org/adaures/castopod/commit/c815ecd6640931fee0895f80908a3ddfac482666))
|
([c815ecd](https://code.podlibre.org/podlibre/castopod/commit/c815ecd6640931fee0895f80908a3ddfac482666))
|
||||||
- **users:** add myth-auth to handle users crud + add admin gateway only
|
- **users:** add myth-auth to handle users crud + add admin gateway only
|
||||||
accessible by login
|
accessible by login
|
||||||
([c63a077](https://code.castopod.org/adaures/castopod/commit/c63a077618c61b4cde7f25ffc650a4b0e1495f44)),
|
([c63a077](https://code.podlibre.org/podlibre/castopod/commit/c63a077618c61b4cde7f25ffc650a4b0e1495f44)),
|
||||||
closes [#11](https://code.castopod.org/adaures/castopod/issues/11)
|
closes [#11](https://code.podlibre.org/podlibre/castopod/issues/11)
|
||||||
- minor corrections to some tables
|
- minor corrections to some tables
|
||||||
([3bf9420](https://code.castopod.org/adaures/castopod/commit/3bf9420b5956a501b3b24405d243a71a928d6086))
|
([3bf9420](https://code.podlibre.org/podlibre/castopod/commit/3bf9420b5956a501b3b24405d243a71a928d6086))
|
||||||
- write id3v2 tags to episode's audio file
|
- write id3v2 tags to episode's audio file
|
||||||
([4651d01](https://code.castopod.org/adaures/castopod/commit/4651d01a84ff3ea8433a8ae26cfd750a1ec9e88d))
|
([4651d01](https://code.podlibre.org/podlibre/castopod/commit/4651d01a84ff3ea8433a8ae26cfd750a1ec9e88d))
|
||||||
|
|
||||||
### Reverts
|
### Reverts
|
||||||
|
|
||||||
- use basic input file for episodes audio files instead of button for better UX
|
- use basic input file for episodes audio files instead of button for better UX
|
||||||
([d5f22fb](https://code.castopod.org/adaures/castopod/commit/d5f22fbb38c43d9b37df401eff655958a57cb40a))
|
([d5f22fb](https://code.podlibre.org/podlibre/castopod/commit/d5f22fbb38c43d9b37df401eff655958a57cb40a))
|
||||||
|
|
|
||||||
|
|
@ -1,162 +1,128 @@
|
||||||
# Contributor Covenant 3.0 Code of Conduct
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
## Our Pledge
|
## Our Pledge
|
||||||
|
|
||||||
We pledge to make our community welcoming, safe, and equitable for all.
|
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 are committed to fostering an environment that respects and promotes the
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
dignity, rights, and contributions of all individuals, regardless of
|
diverse, inclusive, and healthy community.
|
||||||
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.
|
|
||||||
|
|
||||||
## Encouraged Behaviors
|
## Our Standards
|
||||||
|
|
||||||
While acknowledging differences in social norms, we all strive to meet our
|
Examples of behavior that contributes to a positive environment for our
|
||||||
community's expectations for positive behavior. We also understand that our
|
community include:
|
||||||
words and actions may be interpreted differently than we intend based on
|
|
||||||
culture, background, or native language.
|
|
||||||
|
|
||||||
With these considerations in mind, we agree to behave mindfully toward each
|
- Demonstrating empathy and kindness toward other people
|
||||||
other and act in ways that center our shared values, including:
|
- 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
|
||||||
|
|
||||||
1. Respecting the **purpose of our community**, our activities, and our ways of
|
Examples of unacceptable behavior include:
|
||||||
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**.
|
|
||||||
|
|
||||||
## Restricted Behaviors
|
- 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
|
||||||
|
|
||||||
We agree to restrict the following behaviors in our community. Instances,
|
## Enforcement Responsibilities
|
||||||
threats, and promotion of these behaviors are violations of this Code of
|
|
||||||
Conduct.
|
|
||||||
|
|
||||||
1. **Harassment.** Violating explicitly expressed boundaries or engaging in
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
unnecessary personal attention after any clear request to stop.
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
2. **Character attacks.** Making insulting, demeaning, or pejorative comments
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
directed at a community member or group of people.
|
or harmful.
|
||||||
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.
|
|
||||||
|
|
||||||
### Other Restrictions
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
1. **Misleading identity.** Impersonating someone else for any reason, or
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
pretending to be someone else to evade enforcement actions.
|
decisions when appropriate.
|
||||||
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
|
## Scope
|
||||||
|
|
||||||
This Code of Conduct applies within all community spaces, and also applies when
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
an individual is officially representing the community in public or other
|
an individual is officially representing the community in public spaces.
|
||||||
spaces. Examples of representing our community include using an official email
|
Examples of representing our community include using an official e-mail address,
|
||||||
address, posting via an official social media account, or acting as an appointed
|
posting via an official social media account, or acting as an appointed
|
||||||
representative at an online or offline event.
|
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@podlibre.org](mailto:abuse@podlibre.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
|
## Attribution
|
||||||
|
|
||||||
This Code of Conduct is adapted from the Contributor Covenant, version 3.0,
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
permanently available at
|
version 2.0, available at
|
||||||
[https://www.contributor-covenant.org/version/3/0/](https://www.contributor-covenant.org/version/3/0/).
|
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||||
|
|
||||||
Contributor Covenant is stewarded by the Organization for Ethical Source and
|
Community Impact Guidelines were inspired by
|
||||||
licensed under CC BY-SA 4.0. To view a copy of this license, visit
|
[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
||||||
[https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/)
|
|
||||||
|
|
||||||
For answers to common questions about Contributor Covenant, see the FAQ at
|
[homepage]: https://www.contributor-covenant.org
|
||||||
[https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq).
|
|
||||||
Translations are provided at
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
[https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations).
|
https://www.contributor-covenant.org/faq. Translations are available at
|
||||||
Additional enforcement and community guideline resources can be found at
|
https://www.contributor-covenant.org/translations.
|
||||||
[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,437 +0,0 @@
|
||||||
# Setup your development environment
|
|
||||||
|
|
||||||
## Introduction
|
|
||||||
|
|
||||||
Castopod is a web app based on the `php` framework
|
|
||||||
[CodeIgniter 4](https://codeigniter.com).
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
> You don't need any prior knowledge of Docker to follow the next steps.
|
|
||||||
> However, if you wish to use your own environment, feel free to do so!
|
|
||||||
|
|
||||||
## Setup instructions
|
|
||||||
|
|
||||||
### 1. Pre-requisites
|
|
||||||
|
|
||||||
0. Install [Docker](https://docs.docker.com/get-docker).
|
|
||||||
|
|
||||||
1. Clone the Castopod repository by running:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://code.castopod.org/adaures/castopod.git
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Create a `.env` file with the minimum required config to connect the app to
|
|
||||||
the database and use redis as a cache handler:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
CI_ENVIRONMENT="development"
|
|
||||||
# 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.
|
|
||||||
# For development, this must be set to false as it is
|
|
||||||
# on a local environment
|
|
||||||
app.forceGlobalSecureRequests=false
|
|
||||||
|
|
||||||
app.baseURL="http://localhost:8080/"
|
|
||||||
|
|
||||||
admin.gateway="cp-admin"
|
|
||||||
auth.gateway="cp-auth"
|
|
||||||
|
|
||||||
database.default.hostname="mariadb"
|
|
||||||
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"
|
|
||||||
|
|
||||||
# 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
|
|
||||||
```
|
|
||||||
|
|
||||||
> [!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
|
|
||||||
`Settings` > `Resources` > `File Sharing`
|
|
||||||
|
|
||||||
### 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
|
|
||||||
requirements and VSCode extensions so you don't have to worry about them. All
|
|
||||||
required services will be loaded automagically! 🪄
|
|
||||||
|
|
||||||
1. Install the VSCode extension
|
|
||||||
[Remote - Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
|
|
||||||
2. `Ctrl/Cmd + Shift + P` > `Open in container`
|
|
||||||
|
|
||||||
> 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.
|
|
||||||
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
|
|
||||||
pnpm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
If there is any issue with the PHP server not running, you can restart them
|
|
||||||
using the following commands:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# run Castopod server
|
|
||||||
php spark serve - 0.0.0.0
|
|
||||||
```
|
|
||||||
|
|
||||||
3. You're all set! 🎉
|
|
||||||
|
|
||||||
You're now **inside the dev container**, you may use the VSCode console
|
|
||||||
(`Terminal` > `New Terminal`) to run any command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# PHP is installed
|
|
||||||
php -v
|
|
||||||
|
|
||||||
# Composer is installed
|
|
||||||
composer -V
|
|
||||||
|
|
||||||
# pnpm is installed
|
|
||||||
pnpm -v
|
|
||||||
|
|
||||||
# git is installed
|
|
||||||
git version
|
|
||||||
```
|
|
||||||
|
|
||||||
For more info, see
|
|
||||||
[Developing inside a Container](https://code.visualstudio.com/docs/devcontainers/containers)
|
|
||||||
|
|
||||||
### 3. Start hacking
|
|
||||||
|
|
||||||
You're all set! Start working your magic by updating the project's files! Help
|
|
||||||
yourself to the
|
|
||||||
[CodeIgniter4 User Guide](https://codeigniter.com/user_guide/index.html) for
|
|
||||||
more insights.
|
|
||||||
|
|
||||||
To see your changes, go to:
|
|
||||||
|
|
||||||
- `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**
|
|
||||||
|
|
||||||
### 2-alt. Develop outside the app container
|
|
||||||
|
|
||||||
You do not wish to use the VSCode devcontainer? No problem!
|
|
||||||
|
|
||||||
1. Start the Docker containers manually:
|
|
||||||
|
|
||||||
Go to the project's root folder and run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# starts all services declared in docker-compose.yml file
|
|
||||||
# -d option starts the containers in the background
|
|
||||||
docker-compose up -d
|
|
||||||
|
|
||||||
# See all running processes (you should see 3 processes running)
|
|
||||||
docker-compose ps
|
|
||||||
|
|
||||||
# Alternatively, you can check all docker processes
|
|
||||||
docker ps -a
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
> The `docker-compose up -d` command will boot 5 containers in the
|
|
||||||
> background:
|
|
||||||
>
|
|
||||||
> - `castopod_app`: a php based container with Castopod requirements
|
|
||||||
> installed
|
|
||||||
> - `castopod_redis`: a [redis](https://redis.io/) database to handle queries
|
|
||||||
> and pages caching
|
|
||||||
> - `castopod_mariadb`: a [mariadb](https://mariadb.org/) server for
|
|
||||||
> 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`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# use PHP
|
|
||||||
docker-compose run --rm app php -v
|
|
||||||
|
|
||||||
# use Composer
|
|
||||||
docker-compose run --rm app composer -V
|
|
||||||
|
|
||||||
# use pnpm
|
|
||||||
docker-compose run --rm app pnpm -v
|
|
||||||
|
|
||||||
# use git
|
|
||||||
docker-compose run --rm app git version
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Going Further
|
|
||||||
|
|
||||||
### Install Castopod's dependencies
|
|
||||||
|
|
||||||
1. Install php dependencies with [Composer](https://getcomposer.org/)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
composer install
|
|
||||||
```
|
|
||||||
|
|
||||||
> [!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/).
|
|
||||||
|
|
||||||
2. Install JavaScript dependencies with [pnpm](https://pnpm.io/)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm install
|
|
||||||
```
|
|
||||||
|
|
||||||
> [!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
|
|
||||||
pnpm run build:static
|
|
||||||
|
|
||||||
# build specific assets
|
|
||||||
pnpm run build:icons
|
|
||||||
pnpm run build:svg
|
|
||||||
```
|
|
||||||
|
|
||||||
> [!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]
|
|
||||||
> You may skip this section if you go through the install wizard (go to
|
|
||||||
> `/cp-install`).
|
|
||||||
|
|
||||||
1. Build the database with the migrate command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# loads the database schema during first migration
|
|
||||||
php spark migrate -all
|
|
||||||
```
|
|
||||||
|
|
||||||
You may need to undo the migration (rollback):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# rolls back database schema (deletes all tables and their content)
|
|
||||||
php spark migrate:rollback
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Populate the database with the required data:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Populates all required data
|
|
||||||
php spark db:seed DevSeeder
|
|
||||||
```
|
|
||||||
|
|
||||||
You may choose to add data separately:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Populates all categories
|
|
||||||
php spark db:seed CategorySeeder
|
|
||||||
|
|
||||||
# Populates all Languages
|
|
||||||
php spark db:seed LanguageSeeder
|
|
||||||
|
|
||||||
# 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
|
|
||||||
php spark db:seed FakePodcastsAnalyticsSeeder
|
|
||||||
```
|
|
||||||
|
|
||||||
- Populate with fake website analytics:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
php spark db:seed FakeWebsiteAnalyticsSeeder
|
|
||||||
```
|
|
||||||
|
|
||||||
### Useful docker / docker-compose commands
|
|
||||||
|
|
||||||
- Monitor the app container:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose logs --tail 50 --follow --timestamps app
|
|
||||||
```
|
|
||||||
|
|
||||||
- Interact with the Redis server using included redis-cli command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker exec -it castopod_redis redis-cli
|
|
||||||
```
|
|
||||||
|
|
||||||
- Monitor the Redis container:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose logs --tail 50 --follow --timestamps redis
|
|
||||||
```
|
|
||||||
|
|
||||||
- Monitor the mariadb container:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose logs --tail 50 --follow --timestamps mariadb
|
|
||||||
```
|
|
||||||
|
|
||||||
- Monitor the phpmyadmin container:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose logs --tail 50 --follow --timestamps phpmyadmin
|
|
||||||
```
|
|
||||||
|
|
||||||
- Restart docker containers:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose restart
|
|
||||||
```
|
|
||||||
|
|
||||||
- Destroy all containers, opposite of `up` command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose down
|
|
||||||
```
|
|
||||||
|
|
||||||
- Rebuild app container:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose build app
|
|
||||||
```
|
|
||||||
|
|
||||||
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 `pnpm install`.
|
|
||||||
|
|
||||||
👉 By default, docker might not have access to enough RAM. Allocate more memory
|
|
||||||
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:
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> Replace "username" with your local username
|
|
||||||
|
|
||||||
1. Go to `/etc/docker/daemon.json` and add:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"userns-remap": "username"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Configure the subordinate uid/guid:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# in /etc/subuid
|
|
||||||
username:1000:1
|
|
||||||
username:100000:65536
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# in /etc/subgid
|
|
||||||
username:1000:1
|
|
||||||
username:100000:65536
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Restart Docker:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo systemctl restart docker
|
|
||||||
```
|
|
||||||
|
|
||||||
4. That's it! Now, the root user in the container will be mapped to the user on
|
|
||||||
your local machine, no more permission issues! 🎉
|
|
||||||
|
|
||||||
You can check
|
|
||||||
[this great article](https://www.jujens.eu/posts/en/2017/Jul/02/docker-userns-remap/)
|
|
||||||
to know more about how it works.
|
|
||||||
|
|
@ -1,16 +1,8 @@
|
||||||
# Contributing to Castopod
|
# Contributing to Castopod Host
|
||||||
|
|
||||||
Love Castopod and want to help? Thanks so much, there's something to do for
|
Love Castopod Host and want to help? Thanks so much, there's something to do for
|
||||||
everybody!
|
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
|
Please take a moment to review this document in order to make the contribution
|
||||||
process easy and effective for everyone involved.
|
process easy and effective for everyone involved.
|
||||||
|
|
||||||
|
|
@ -19,34 +11,17 @@ developers managing and developing this open source project. In return, they
|
||||||
should reciprocate that respect in addressing your issue or assessing patches
|
should reciprocate that respect in addressing your issue or assessing patches
|
||||||
and features.
|
and features.
|
||||||
|
|
||||||
## Translating Castopod
|
⚠️ Note that **any** contribution made on a repository other than
|
||||||
|
[the original repository](https://code.podlibre.org/podlibre/castopod-host) will
|
||||||
We use [Crowdin](https://translate.castopod.org/) to manage translation files
|
not be accepted.
|
||||||
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
|
## Using the issue tracker
|
||||||
|
|
||||||
The [issue tracker](https://code.castopod.org/adaures/castopod/-/issues) is the
|
The [issue tracker](https://code.podlibre.org/podlibre/castopod-host/-/issues)
|
||||||
preferred channel for [bug reports](#bug-reports),
|
is the preferred channel for [bug reports](#bug-reports),
|
||||||
[features requests](#feature-requests) and
|
[features requests](#feature-requests) and
|
||||||
[submitting pull requests](#pull-requests).
|
[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
|
## Bug reports
|
||||||
|
|
||||||
A bug is a _demonstrable problem_ that is caused by the code in the repository.
|
A bug is a _demonstrable problem_ that is caused by the code in the repository.
|
||||||
|
|
@ -70,9 +45,8 @@ 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
|
experience the problem? What would you expect to be the outcome? All these
|
||||||
details will help people to fix any potential bugs.
|
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)
|
||||||
> [Issue templates](https://docs.gitlab.com/ee/user/project/description_templates.html#using-the-templates) have
|
> have been created for this project. You may use them to help you follow those
|
||||||
> been created for this project. You may use them to help you follow those
|
|
||||||
> guidelines.
|
> guidelines.
|
||||||
|
|
||||||
## Feature requests
|
## Feature requests
|
||||||
|
|
@ -98,33 +72,33 @@ 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
|
Adhering to the following process is the best way to get your work included in
|
||||||
the project:
|
the project:
|
||||||
|
|
||||||
1. [Fork](https://docs.gitlab.com/ee/user/project/repository/forking_workflow.html)
|
1. [Fork](https://docs.gitlab.com/ee/gitlab-basics/fork-project.html) the
|
||||||
the project, clone your fork, and configure the remotes:
|
project, clone your fork, and configure the remotes:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clone your fork of the repo into the current directory
|
# Clone your fork of the repo into the current directory
|
||||||
git clone https://code.castopod.org/<your-username>/castopod.git
|
git clone https://code.podlibre.org/<your-username>/castopod-host.git
|
||||||
|
|
||||||
# Navigate to the newly cloned directory
|
# Navigate to the newly cloned directory
|
||||||
cd castopod
|
cd castopod-host
|
||||||
|
|
||||||
# Assign the original repo to a remote called "upstream"
|
# Assign the original repo to a remote called "upstream"
|
||||||
git remote add upstream https://code.castopod.org/adaures/castopod.git
|
git remote add upstream https://code.podlibre.org/podlibre/castopod-host.git
|
||||||
```
|
```
|
||||||
|
|
||||||
2. If you cloned a while ago, get the latest changes from upstream:
|
2. If you cloned a while ago, get the latest changes from upstream:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git checkout main
|
git checkout main
|
||||||
git pull upstream main
|
git pull upstream main
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Create a new topic branch (off the `main` branch) to contain your feature,
|
3. Create a new topic branch (off the `main` branch) to contain your feature,
|
||||||
change, or fix:
|
change, or fix:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git checkout -b <topic-branch-name>
|
git checkout -b <topic-branch-name>
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Commit your changes in logical chunks. Please adhere to these
|
4. Commit your changes in logical chunks. Please adhere to these
|
||||||
[git commit message guidelines](https://conventionalcommits.org/) or your
|
[git commit message guidelines](https://conventionalcommits.org/) or your
|
||||||
|
|
@ -134,23 +108,22 @@ the project:
|
||||||
|
|
||||||
5. Locally merge (or rebase) the upstream dev branch into your topic branch:
|
5. Locally merge (or rebase) the upstream dev branch into your topic branch:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git pull [--rebase] upstream main
|
git pull [--rebase] upstream main
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Push your topic branch up to your fork:
|
6. Push your topic branch up to your fork:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git push origin <topic-branch-name>
|
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)
|
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.
|
with a clear title and description.
|
||||||
|
|
||||||
> [!IMPORTANT]
|
**IMPORTANT**: By submitting a patch, you agree to allow the project owners to
|
||||||
> By submitting a patch, you agree to allow the project owners to license your
|
license your work under the terms of the
|
||||||
> work under the terms of the
|
[GNU AGPLv3](https://code.podlibre.org/podlibre/castopod-host/-/blob/main/LICENSE).
|
||||||
> [GNU AGPLv3](https://code.castopod.org/adaures/castopod/-/blob/develop/LICENSE.md).
|
|
||||||
|
|
||||||
## Collaborating guidelines
|
## Collaborating guidelines
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,66 @@
|
||||||
# Castopod dependencies
|
# Castopod Host dependencies
|
||||||
|
|
||||||
Castopod uses the following components:
|
Castopod Host uses the following components:
|
||||||
|
|
||||||
## PHP Dependencies
|
PHP Dependencies:
|
||||||
|
|
||||||
PHP dependencies can be found in the [composer.json](./composer.json) file.
|
- [CodeIgniter 4](https://codeigniter.com)
|
||||||
|
([MIT License](https://codeigniter.com/user_guide/license.html))
|
||||||
|
- [WhichBrowser/Parser-PHP](https://github.com/WhichBrowser/Parser-PHP)
|
||||||
|
([MIT License](https://github.com/WhichBrowser/Parser-PHP/blob/master/LICENSE))
|
||||||
|
- [GeoIP2 PHP API](https://github.com/maxmind/GeoIP2-php)
|
||||||
|
([Apache License 2.0](https://github.com/maxmind/GeoIP2-php/blob/master/LICENSE))
|
||||||
|
- [getID3](https://github.com/JamesHeinrich/getID3)
|
||||||
|
([GNU General Public License v3](https://github.com/JamesHeinrich/getID3/blob/2.0/licenses/license.gpl-30.txt))
|
||||||
|
- [myth-auth](https://github.com/lonnieezell/myth-auth)
|
||||||
|
([MIT license](https://github.com/lonnieezell/myth-auth/blob/develop/LICENSE.md))
|
||||||
|
- [commonmark](https://commonmark.thephpleague.com/)
|
||||||
|
([BSD 3-Clause "New" or "Revised" License](https://github.com/thephpleague/commonmark/blob/latest/LICENSE))
|
||||||
|
- [phpdotenv](https://github.com/vlucas/phpdotenv)
|
||||||
|
([ BSD-3-Clause License ](https://github.com/vlucas/phpdotenv/blob/master/LICENSE))
|
||||||
|
- [HTML To Markdown for PHP](https://github.com/thephpleague/html-to-markdown)
|
||||||
|
([MIT License](https://github.com/thephpleague/html-to-markdown/blob/master/LICENSE))
|
||||||
|
- [opawg/user-agents-php](https://github.com/opawg/user-agents-php)
|
||||||
|
([MIT License](https://github.com/podlibre/user-agents-php/blob/main/LICENSE))
|
||||||
|
- [podlibre/ipcat](https://github.com/podlibre/ipcat)
|
||||||
|
([GNU General Public License v3.0](https://github.com/podlibre/ipcat/blob/master/LICENSE))
|
||||||
|
- [podlibre/podcast-namespace](https://code.podlibre.org/podlibre/podcastnamespace)
|
||||||
|
([MIT License](https://code.podlibre.org/podlibre/podcastnamespace/-/blob/master/LICENSE))
|
||||||
|
- [phpseclib](https://phpseclib.com/)
|
||||||
|
([MIT License](https://github.com/phpseclib/phpseclib/blob/master/LICENSE))
|
||||||
|
- [codeigniter4-uuid](https://github.com/michalsn/codeigniter4-uuid)
|
||||||
|
([MIT License](https://github.com/michalsn/codeigniter4-uuid/blob/develop/LICENSE))
|
||||||
|
- [essence](https://github.com/essence/essence)
|
||||||
|
([The FreeBSD License](https://github.com/essence/essence/blob/master/LICENSE.txt))
|
||||||
|
|
||||||
## Javascript dependencies
|
Javascript dependencies:
|
||||||
|
|
||||||
Javascript dependencies can be found in the [package.json](./package.json) file.
|
- [rollup](https://rollupjs.org/)
|
||||||
|
([MIT License](https://github.com/rollup/rollup/blob/master/LICENSE.md))
|
||||||
|
- [tailwindcss](https://tailwindcss.com/)
|
||||||
|
([MIT License](https://github.com/tailwindcss/tailwindcss/blob/master/LICENSE))
|
||||||
|
- [ProseMirror](https://prosemirror.net/)
|
||||||
|
([MIT License](https://github.com/ProseMirror/prosemirror/blob/master/LICENSE))
|
||||||
|
- [amCharts 4](https://github.com/amcharts/amcharts4)
|
||||||
|
([Free amCharts license](https://github.com/amcharts/amcharts4/blob/master/dist/script/LICENSE))
|
||||||
|
- [Choices.js](https://joshuajohnson.co.uk/Choices/)
|
||||||
|
([MIT License](https://github.com/jshjohnson/Choices/blob/master/LICENSE))
|
||||||
|
- [flatpickr](https://flatpickr.js.org/)
|
||||||
|
([MIT License](https://github.com/flatpickr/flatpickr/blob/master/LICENSE.md))
|
||||||
|
- [popperjs](https://popper.js.org/)
|
||||||
|
([MIT License](https://github.com/popperjs/popper-core/blob/master/LICENSE.md))
|
||||||
|
|
||||||
## Other dependencies
|
Other:
|
||||||
|
|
||||||
- [Kumbh Sans](https://fonts.google.com/specimen/Kumbh+Sans)
|
- [Kumbh Sans](https://fonts.google.com/specimen/Kumbh+Sans)
|
||||||
([Open Font License](https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL))
|
([Open Font License](https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL))
|
||||||
- [Inter](https://fonts.google.com/specimen/Inter)
|
- [Montserrat](https://fonts.google.com/specimen/Montserrat)
|
||||||
([Open Font License](https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL))
|
([Open Font License](https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL))
|
||||||
- [RemixIcon](https://remixicon.com/)
|
- [RemixIcon](https://remixicon.com/)
|
||||||
([Apache License 2.0](https://github.com/Remix-Design/RemixIcon/blob/master/License))
|
([Apache License 2.0](https://github.com/Remix-Design/RemixIcon/blob/master/License))
|
||||||
- [OPAWG/User agent list](https://github.com/opawg/user-agents-v2)
|
- [OPAWG/User agent list](https://github.com/opawg/user-agents)
|
||||||
([by Open Podcast Analytics Working Group](https://github.com/opawg))
|
([by Open Podcast Analytics Working Group](https://github.com/opawg))
|
||||||
([MIT license](https://github.com/opawg/user-agents-v2/blob/master/LICENSE))
|
([MIT license](https://github.com/opawg/user-agents/blob/master/LICENSE))
|
||||||
- [OPAWG/podcast-rss-useragents](https://github.com/opawg/podcast-rss-useragents)
|
- [OPAWG/podcast-rss-useragents](https://github.com/opawg/podcast-rss-useragents)
|
||||||
([by Open Podcast Analytics Working Group](https://github.com/opawg))
|
([by Open Podcast Analytics Working Group](https://github.com/opawg))
|
||||||
([MIT license](https://github.com/opawg/podcast-rss-useragents/blob/master/LICENSE))
|
([MIT license](https://github.com/opawg/podcast-rss-useragents/blob/master/LICENSE))
|
||||||
|
|
|
||||||
62
Dockerfile
Normal file
62
Dockerfile
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
####################################################
|
||||||
|
# Castopod Host development Docker file
|
||||||
|
####################################################
|
||||||
|
# ⚠️ NOT optimized for production
|
||||||
|
# should be used only for development purposes
|
||||||
|
#---------------------------------------------------
|
||||||
|
FROM php:8.0-fpm
|
||||||
|
|
||||||
|
LABEL maintainer="Yassine Doghri <yassine@doghri.fr>"
|
||||||
|
|
||||||
|
COPY . /castopod-host
|
||||||
|
WORKDIR /castopod-host
|
||||||
|
|
||||||
|
# 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_14.x | bash - \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install --yes --no-install-recommends nodejs \
|
||||||
|
# update npm
|
||||||
|
&& npm install --global npm@7 \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install --yes --no-install-recommends \
|
||||||
|
git \
|
||||||
|
openssh-client \
|
||||||
|
vim \
|
||||||
|
# cron for scheduled tasks
|
||||||
|
cron \
|
||||||
|
# unzip used by composer
|
||||||
|
unzip \
|
||||||
|
# required libraries to install php extensions using
|
||||||
|
# https://github.com/mlocati/docker-php-extension-installer (included in php's docker image)
|
||||||
|
libicu-dev \
|
||||||
|
libpng-dev \
|
||||||
|
libjpeg-dev \
|
||||||
|
zlib1g-dev \
|
||||||
|
libzip-dev \
|
||||||
|
# intl for Internationalization
|
||||||
|
&& docker-php-ext-install intl \
|
||||||
|
&& docker-php-ext-install zip \
|
||||||
|
# gd for image processing
|
||||||
|
&& docker-php-ext-configure gd --with-jpeg \
|
||||||
|
&& docker-php-ext-install gd \
|
||||||
|
# redis extension for cache
|
||||||
|
&& pecl install -o -f redis \
|
||||||
|
&& rm -rf /tmp/pear \
|
||||||
|
&& 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 \
|
||||||
83
GDPR.txt
83
GDPR.txt
|
|
@ -1,83 +0,0 @@
|
||||||
# This file lists processing purposes and the personal data gathered by
|
|
||||||
# Castopod.
|
|
||||||
# It is intended for hosting providers who want to provide a service
|
|
||||||
# based on Castopod, helping them to comply with GDPR requirements. Note
|
|
||||||
# that the services powered by Castopod may collect more data, HTTP logs
|
|
||||||
# in particular. As a hosting provider, you must inform your users of their
|
|
||||||
# rights and how their data are used and protected.
|
|
||||||
|
|
||||||
purpose:
|
|
||||||
Deduplicate number of audio file downloads made by the same listener
|
|
||||||
for analytics purposes
|
|
||||||
lawfulness: legitimate interest
|
|
||||||
|
|
||||||
data: (User IP address + Browser User Agent)
|
|
||||||
required: yes
|
|
||||||
visibility: none
|
|
||||||
description:
|
|
||||||
In order to produce analytics data comparable to the podcasting
|
|
||||||
ecosystem standards, the User IP address (REMOTE_ADDR) with the
|
|
||||||
browser User Agent (HTTP_USER_AGENT) are stored when an audio file
|
|
||||||
is downloaded.
|
|
||||||
mitigation:
|
|
||||||
The data (User IP address + Browser User Agent) is never stored in plain
|
|
||||||
format.
|
|
||||||
The data is concatenated with a cryptographic salt, the current date,
|
|
||||||
and the podcast or episode IDs.
|
|
||||||
The data is hashed (using sha1) after being concatenated and before
|
|
||||||
being stored.
|
|
||||||
The data is stored in a cache database (eg. Redis).
|
|
||||||
The data expires every day at midnight (server time).
|
|
||||||
|
|
||||||
purpose: Connect users to their accounts
|
|
||||||
lawfulness: legitimate interest
|
|
||||||
|
|
||||||
data: username
|
|
||||||
required: yes
|
|
||||||
visibility: authenticated users
|
|
||||||
description:
|
|
||||||
The username is used to identify users during the login process.
|
|
||||||
The username is only required for users accessing the admin area.
|
|
||||||
mitigation:
|
|
||||||
The username does not have to be a real or known identity.
|
|
||||||
|
|
||||||
data: user e-mail address
|
|
||||||
required: yes
|
|
||||||
visibility: administrators
|
|
||||||
description:
|
|
||||||
The e-mail address is used for administrative purposes, to identify users
|
|
||||||
during the login process and in case of forgotten password.
|
|
||||||
|
|
||||||
data: password
|
|
||||||
required: yes
|
|
||||||
visibility: private
|
|
||||||
description:
|
|
||||||
The password is used to check the identity of users during the login
|
|
||||||
process.
|
|
||||||
mitigation:
|
|
||||||
Only hashes (using the Argon2 key derivation function) of the passwords
|
|
||||||
are stored in the database (but they transit over the network).
|
|
||||||
|
|
||||||
purpose: Claim ownership of a podcast
|
|
||||||
lawfulness: legitimate interest
|
|
||||||
|
|
||||||
data: Podcast e-mail address
|
|
||||||
required: yes
|
|
||||||
visibility: public
|
|
||||||
description:
|
|
||||||
The podcast e-mail address is used to claim podcast ownership on other
|
|
||||||
platforms (such as Apple Podcasts).
|
|
||||||
mitigation:
|
|
||||||
The e-mail can be generic.
|
|
||||||
|
|
||||||
purpose: Grant access to premium content
|
|
||||||
lawfulness: legitimate interest
|
|
||||||
|
|
||||||
data: Subscriber's email address
|
|
||||||
required: yes
|
|
||||||
visibility: administrators
|
|
||||||
description:
|
|
||||||
The subscriber's e-mail address is used to provide credentials for
|
|
||||||
listening to premium content.
|
|
||||||
mitigation:
|
|
||||||
The e-mail can be generic.
|
|
||||||
123
INSTALL.md
Normal file
123
INSTALL.md
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
# How to install Castopod Host <!-- omit in toc -->
|
||||||
|
|
||||||
|
_Castopod Host_ was thought-out to be easy to install. Whether using dedicated
|
||||||
|
or shared hosting, you can install it on most PHP-MySQL compatible web servers.
|
||||||
|
|
||||||
|
## Table of contents <!-- omit in toc -->
|
||||||
|
|
||||||
|
- [Install instructions](#install-instructions)
|
||||||
|
- [0. Pre-requisites](#0-pre-requisites)
|
||||||
|
- [(recommended) Install Wizard](#recommended-install-wizard)
|
||||||
|
- [(alternative) Manual configuration](#alternative-manual-configuration)
|
||||||
|
- [Web Server Requirements](#web-server-requirements)
|
||||||
|
- [PHP v8.0 or higher](#php-v80-or-higher)
|
||||||
|
- [MySQL compatible database](#mysql-compatible-database)
|
||||||
|
- [Privileges](#privileges)
|
||||||
|
- [(Optional) Other recommendations](#optional-other-recommendations)
|
||||||
|
- [Security concerns](#security-concerns)
|
||||||
|
|
||||||
|
## Install instructions
|
||||||
|
|
||||||
|
### 0. Pre-requisites
|
||||||
|
|
||||||
|
0. Get a Web Server with requirements installed
|
||||||
|
1. Create a MySQL database for Castopod Host with a user having access and
|
||||||
|
modification privileges (for more info, see
|
||||||
|
[Web Server Requirements](#web-server-requirements)).
|
||||||
|
2. Activate HTTPS on your domain with an _SSL certificate_.
|
||||||
|
3. Download and unzip the latest
|
||||||
|
[Castopod Host Package](https://code.podlibre.org/podlibre/castopod-host/-/releases)
|
||||||
|
onto the web server if you haven’t already.
|
||||||
|
- ⚠️ Set the web server document root to the `public/` sub-folder.
|
||||||
|
4. Add a cron task on your web server to run every minute (replace the paths
|
||||||
|
accordingly):
|
||||||
|
|
||||||
|
```php
|
||||||
|
* * * * * /path/to/php /path/to/castopod-host/public/index.php scheduled-activities
|
||||||
|
```
|
||||||
|
|
||||||
|
> ⚠️ Social features will not work properly if you do not set the task. It is
|
||||||
|
> used to broadcast social activities to the fediverse.
|
||||||
|
|
||||||
|
### (recommended) Install Wizard
|
||||||
|
|
||||||
|
1. Run the Castopod Host install script by going to the install wizard page
|
||||||
|
(`https://your_domain_name.com/cp-install`) in your favorite web browser.
|
||||||
|
2. Follow the instructions on your screen.
|
||||||
|
3. Start podcasting!
|
||||||
|
|
||||||
|
> **Note:**
|
||||||
|
>
|
||||||
|
> The install script writes a `.env` file in the package root. If you cannot go
|
||||||
|
> through the install wizard, you can
|
||||||
|
> [create and update the `.env` file manually](#alternative-manual-configuration).
|
||||||
|
|
||||||
|
### (alternative) Manual configuration
|
||||||
|
|
||||||
|
1. Rename the `.env.example` file to `.env` and update the default values with
|
||||||
|
your own.
|
||||||
|
2. Upload the `.env` file to the Castopod Host Package root on your server.
|
||||||
|
3. Go to `/cp-install` to finish the install process.
|
||||||
|
4. Start podcasting!
|
||||||
|
|
||||||
|
## Web Server Requirements
|
||||||
|
|
||||||
|
### PHP v8.0 or higher
|
||||||
|
|
||||||
|
PHP version 8.0 or higher is required, with the following extensions installed:
|
||||||
|
|
||||||
|
- [intl](https://php.net/manual/en/intl.requirements.php)
|
||||||
|
- [libcurl](https://php.net/manual/en/curl.requirements.php)
|
||||||
|
- [mbstring](https://php.net/manual/en/mbstring.installation.php)
|
||||||
|
- [gd](https://www.php.net/manual/en/image.installation.php)
|
||||||
|
|
||||||
|
Additionally, make sure that the following extensions are enabled in your PHP:
|
||||||
|
|
||||||
|
- json (enabled by default - don't turn it off)
|
||||||
|
- xml (enabled by default - don't turn it off)
|
||||||
|
- [mysqlnd](https://php.net/manual/en/mysqlnd.install.php)
|
||||||
|
|
||||||
|
### MySQL compatible database
|
||||||
|
|
||||||
|
> We recommend using [MariaDB](https://mariadb.org).
|
||||||
|
|
||||||
|
You will need the server hostname, database name, username and password to
|
||||||
|
complete the installation process. If you do not have these, please contact your
|
||||||
|
server administrator.
|
||||||
|
|
||||||
|
> NB. Castopod Host only works with supported MySQL compatible databases. It
|
||||||
|
> will break with MySQL v5.6 for example as its end of life was on February
|
||||||
|
> 5, 2021.
|
||||||
|
|
||||||
|
#### Privileges
|
||||||
|
|
||||||
|
User must have at least these privileges on the database for Castopod Host to
|
||||||
|
work: `ALTER`, `DELETE`, `EXECUTE`, `INDEX`, `INSERT`, `SELECT`, `UPDATE`.
|
||||||
|
|
||||||
|
### (Optional) Other recommendations
|
||||||
|
|
||||||
|
- Redis for better cache performances.
|
||||||
|
- CDN for static files caching and better performances.
|
||||||
|
- e-mail gateway for lost passwords.
|
||||||
|
|
||||||
|
## Security concerns
|
||||||
|
|
||||||
|
Castopod Host is built on top of Codeigniter, a PHP framework that encourages
|
||||||
|
[good security practices](https://codeigniter.com/user_guide/concepts/security.html).
|
||||||
|
|
||||||
|
To maximize your instance safety and prevent any malicious attack, we recommend
|
||||||
|
you update all your Castopod Host files permissions after installation (to avoid
|
||||||
|
any permission error):
|
||||||
|
|
||||||
|
- `writable/` folder must be **readable** and **writable**.
|
||||||
|
- `public/media/` folder must be **readable** and **writable**.
|
||||||
|
- any other file must be set to **readonly**.
|
||||||
|
|
||||||
|
For instance, if you are using Apache or NGINX with Ubuntu you may do the
|
||||||
|
following:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo chown -R root:root /path/to/castopod-host
|
||||||
|
sudo chown -R www-data:www-data /path/to/castopod-host/writable
|
||||||
|
sudo chown -R www-data:www-data /path/to/castopod-host/public/media
|
||||||
|
```
|
||||||
208
README.md
208
README.md
|
|
@ -1,191 +1,75 @@
|
||||||
<div align="center">
|
<h1 style="text-align: center">
|
||||||
<h1>
|
<img src="https://podlibre.org/static/images/Castopod-Title.svg" alt="Castopod Host" />
|
||||||
<a href="https://castopod.org/">
|
</h1>
|
||||||
<img src="./docs/src/assets/castopod-logo-inline.svg" alt="Castopod" height="64px" />
|
|
||||||
</a>
|
> ⚠️ **Castopod Host is in alpha version**. It is still under heavy development
|
||||||
</h1>
|
> and may not be 100% stable as new features are being worked on.
|
||||||
|
|
||||||
|
_Castopod Host_ is a free and open-source podcast hosting solution made for
|
||||||
|
podcasters who want engage and interact with their audience.
|
||||||
|
|
||||||
|
Create, upload, publish, interact with your followers and get comprehensive
|
||||||
|
audience measurements that respect your listeners privacy.
|
||||||
|
|
||||||
|
Whether you choose to install it on your own server or have it hosted by a
|
||||||
|
professional, all your data and analytics belong to you and you only!
|
||||||
|
|
||||||
|
<div style="text-align: center">
|
||||||
|
<img src="https://podlibre.org/static/images/Castopod-Mascot-Server.svg" alt="Castopod Mascot" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div align="center">
|
You may find Castopod Host's source code on the
|
||||||
|
[original repository](https://code.podlibre.org/podlibre/castopod-host) or,
|
||||||
|
alternatively, on the
|
||||||
|
[github repository (mirror)](https://github.com/podlibre/castopod-host).
|
||||||
|
|
||||||
[![release-badge]][release] [![license-badge]][license] [![crowdin-badge]][crowdin] [![contributions-badge]][contributions] [![semantic-release-badge]][semantic-release] [![discord-badge]][discord] [![stars-badge]][stars]
|
## Install / Update
|
||||||
|
|
||||||
</div>
|
To install or update Castopod Host on your PHP/MySQL server:
|
||||||
|
|
||||||
Castopod is a free and open-source podcast hosting solution made for podcasters
|
- Download
|
||||||
who want engage and interact with their audience.
|
[Castopod Host's latest Package (zip or tar.gz)](https://code.podlibre.org/podlibre/castopod-host/-/releases):
|
||||||
|
|
||||||
## Getting started
|
- Follow one of the procedures on:
|
||||||
|
|
||||||
Castopod comes pre-packaged with all the required static assets and
|
- [“How to **install** Castopod Host”](./INSTALL.md)
|
||||||
dependencies, you may download and install it by checking out the
|
- or [“How to **update** Castopod Host”](./UPDATE.md)
|
||||||
[getting started page](https://castopod.org/getting-started/)!
|
|
||||||
|
|
||||||
## Security issues and vulnerabilities
|
## Documentation
|
||||||
|
|
||||||
If you encounter any security issue or vulnerability in the Castopod source,
|
You can check Castopod Host's documentation for
|
||||||
please contact us directly by email at
|
[setting up a development environment](./docs/setup-development.md).
|
||||||
[security@castopod.org](mailto:security@castopod.org)
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Contributions are always welcome!
|
Love Castopod Host and would like to help? Check out the
|
||||||
|
[contribution guidelines](./CONTRIBUTING.md) for this project, everything should
|
||||||
|
be there!
|
||||||
|
|
||||||
See the [contribution guidelines](./CONTRIBUTING.md) for ways to get started.
|
⚠️ Note that **any** contribution made on a repository other than
|
||||||
|
[the original repository](https://code.podlibre.org/podlibre/castopod-host) will
|
||||||
|
not be accepted.
|
||||||
|
|
||||||
> [!Important]
|
## Support
|
||||||
> **Any** contribution made on a repository other than
|
|
||||||
> [the original repository](https://code.castopod.org/adaures/castopod) will not
|
|
||||||
> be accepted.
|
|
||||||
|
|
||||||
## Contributors ✨
|
|
||||||
|
|
||||||
Thanks goes to these wonderful people
|
|
||||||
([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
||||||
<!-- prettier-ignore-start -->
|
|
||||||
<!-- markdownlint-disable -->
|
|
||||||
<table>
|
|
||||||
<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 -->
|
|
||||||
<!-- prettier-ignore-end -->
|
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
|
||||||
|
|
||||||
This project follows the
|
|
||||||
[all-contributors](https://github.com/all-contributors/all-contributors)
|
|
||||||
specification. Contributions of any kind welcome!
|
|
||||||
|
|
||||||
## Contact
|
|
||||||
|
|
||||||
You may reach us for help or ask any question you have on:
|
You may reach us for help or ask any question you have on:
|
||||||
|
|
||||||
- [Discord](https://castopod.org/discord) (for direct interaction with
|
- [Discord](https://castopod.org/discord) (for direct interaction with
|
||||||
developers and the community)
|
developers and the community)
|
||||||
- [Issue tracker](https://code.castopod.org/adaures/castopod/-/issues) (for
|
|
||||||
feature requests & bug reports)
|
|
||||||
|
|
||||||
Alternatively, you can follow us on social media platforms to get news about
|
Alternatively, you can follow us on social media platforms to get news about
|
||||||
Castopod:
|
Castopod:
|
||||||
|
|
||||||
- [podlibre.social](https://podlibre.social/@Castopod) (Mastodon instance)
|
- [podlibre.social](https://podlibre.social/@Castopod) (Mastodon instance)
|
||||||
- [Bluesky](https://bsky.app/profile/castopod.org)
|
- [Twitter](https://twitter.com/castopod)
|
||||||
- [LinkedIn](https://linkedin.com/company/castopod)
|
|
||||||
- [Facebook](https://www.facebook.com/castopod)
|
- [Facebook](https://www.facebook.com/castopod)
|
||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
The ongoing development of Castopod is made possible with the support of its
|
[Castopod](https://nlnet.nl/project/Castopod/) was funded through the
|
||||||
backers. If you'd like to help, please consider
|
[NGI0 Discovery](https://nlnet.nl/discovery/) Fund under grant agreement
|
||||||
[sponsoring Castopod's development](https://opencollective.com/castopod/contribute).
|
Nº 825322.
|
||||||
|
|
||||||
<table>
|
The fund was established by NLnet with financial support from the European
|
||||||
<tbody>
|
Commission's [Next Generation Internet](https://www.ngi.eu/) programme, under
|
||||||
<tr>
|
the aegis of DG Communications Networks, Content and Technology.
|
||||||
<td align="center">
|
|
||||||
<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="./docs/src/assets/images/sponsors/nlnet.svg" alt="NLnet Logo" height="48" /></a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
[GNU Affero General Public License v3.0](https://choosealicense.com/licenses/agpl-3.0/)
|
|
||||||
|
|
||||||
Copyright © 2020-present, [Ad Aures](https://adaures.com/).
|
|
||||||
|
|
||||||
[release]: https://code.castopod.org/adaures/castopod/-/releases
|
|
||||||
[release-badge]:
|
|
||||||
https://img.shields.io/gitlab/v/release/2?color=brightgreen&gitlab_url=https%3A%2F%2Fcode.castopod.org%2F&include_prereleases&label=release
|
|
||||||
[license]: https://code.castopod.org/adaures/castopod/-/blob/beta/LICENSE.md
|
|
||||||
[license-badge]:
|
|
||||||
https://img.shields.io/github/license/ad-aures/castopod?color=blue
|
|
||||||
[contributions]: https://code.castopod.org/adaures/castopod/-/issues
|
|
||||||
[contributions-badge]:
|
|
||||||
https://img.shields.io/badge/contributions-welcome-brightgreen.svg
|
|
||||||
[semantic-release]: https://github.com/semantic-release/semantic-release
|
|
||||||
[semantic-release-badge]:
|
|
||||||
https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
|
|
||||||
[discord]: https://castopod.org/discord
|
|
||||||
[discord-badge]: https://img.shields.io/badge/chat-on%20discord-7389D8
|
|
||||||
[stars]: https://github.com/ad-aures/castopod/stargazers
|
|
||||||
[stars-badge]:
|
|
||||||
https://img.shields.io/github/stars/ad-aures/castopod?style=social
|
|
||||||
[crowdin]: https://translate.castopod.org/project/castopod
|
|
||||||
[crowdin-badge]: https://badges.crowdin.net/castopod/localized.svg
|
|
||||||
|
|
|
||||||
95
UPDATE.md
Normal file
95
UPDATE.md
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
# How to update Castopod Host <!-- omit in toc -->
|
||||||
|
|
||||||
|
After installing _Castopod Host_, you may want to update your instance to the
|
||||||
|
latest version in order to enjoy the latest features ✨, bug fixes 🐛 and
|
||||||
|
performance improvements ⚡.
|
||||||
|
|
||||||
|
## Table of contents <!-- omit in toc -->
|
||||||
|
|
||||||
|
- [Manual update instructions](#manual-update-instructions)
|
||||||
|
- [Automatic update instructions](#automatic-update-instructions)
|
||||||
|
- [Frequently asked questions (FAQ)](#frequently-asked-questions-faq)
|
||||||
|
- [Where can I find my _Castopod Host_ version?](#where-can-i-find-my-castopod-host-version)
|
||||||
|
- [I haven't updated my instance in a long time… What should I do?](#i-havent-updated-my-instance-in-a-long-time-what-should-i-do)
|
||||||
|
- [Should I make a backup before updating?](#should-i-make-a-backup-before-updating)
|
||||||
|
|
||||||
|
## Manual update instructions
|
||||||
|
|
||||||
|
1. Go to the
|
||||||
|
[releases page](https://code.podlibre.org/podlibre/castopod-host/-/releases)
|
||||||
|
and see if your instance is up to date with the latest _Castopod Host_
|
||||||
|
version
|
||||||
|
|
||||||
|
- cf.
|
||||||
|
[Where can I find my _Castopod Host_ version?](#where-can-i-find-my-castopod-host-version)
|
||||||
|
|
||||||
|
2. Download the latest release package named `Castopod Host Package`, you may
|
||||||
|
choose between the `zip` or `tar.gz` archives
|
||||||
|
|
||||||
|
- ⚠️ Make sure you download the Castopod Host Package and **NOT** the Source
|
||||||
|
Code
|
||||||
|
|
||||||
|
3. On your server:
|
||||||
|
|
||||||
|
- Remove all files except `.env` and `public/media`
|
||||||
|
- Copy the new files from the downloaded package into your server
|
||||||
|
- Note: you may need to reset files permissions as during the install
|
||||||
|
process. Check
|
||||||
|
[Security Concerns section in INSTALL.md](./INSTALL.md#security-concerns).
|
||||||
|
|
||||||
|
4. Alpha releases may come with additional update instructions (see
|
||||||
|
[releases page](https://code.podlibre.org/podlibre/castopod-host/-/releases)).
|
||||||
|
They are usually database migration scripts in `.sql` format to update your
|
||||||
|
database schema.
|
||||||
|
|
||||||
|
- 👉 Make sure you run the scripts on your phpmyadmin panel or using command
|
||||||
|
line to update the database along with the package files!
|
||||||
|
- cf.
|
||||||
|
[I haven't updated my instance in a long time… What should I do?](#i-havent-updated-my-instance-in-a-long-time-what-should-i-do)
|
||||||
|
|
||||||
|
5. If you are using redis, clear your cache.
|
||||||
|
6. ✨ Enjoy your fresh instance, you're all done!
|
||||||
|
|
||||||
|
## Automatic update instructions
|
||||||
|
|
||||||
|
> Coming soon... 👀
|
||||||
|
|
||||||
|
## Frequently asked questions (FAQ)
|
||||||
|
|
||||||
|
### Where can I find my _Castopod Host_ version?
|
||||||
|
|
||||||
|
Go to your _Castopod Host_ admin panel, the version is displayed on the bottom
|
||||||
|
right corner.
|
||||||
|
|
||||||
|
Alternatively, you can find the version in the `app > Config > Constants.php`
|
||||||
|
file.
|
||||||
|
|
||||||
|
### I haven't updated my instance in a long time… What should I do?
|
||||||
|
|
||||||
|
No problem! Just get the latest release as described above. Only, when going
|
||||||
|
through the release instructions (4), perform them sequentially, from the oldest
|
||||||
|
to the newest.
|
||||||
|
|
||||||
|
> You may want to backup your instance depending on how long you haven't updated
|
||||||
|
> _Castopod Host_.
|
||||||
|
|
||||||
|
For example, if you're on `v1.0.0-alpha.42` and would like to upgrade to
|
||||||
|
`v1.0.0-alpha.58`:
|
||||||
|
|
||||||
|
0. (recommended) Make a backup of your files and database.
|
||||||
|
|
||||||
|
1. Download the latest release, overwrite your files whilst keeping `.env` and
|
||||||
|
`public/media`.
|
||||||
|
|
||||||
|
2. Go through each release update instructions sequentially (from oldest to
|
||||||
|
newest) starting with `v1.0.0-alpha.43`, `v1.0.0-alpha.44`,
|
||||||
|
`v1.0.0-alpha.45`, …, `v1.0.0-alpha.58`.
|
||||||
|
|
||||||
|
3. ✨ Enjoy your fresh instance, you're all done!
|
||||||
|
|
||||||
|
### Should I make a backup before updating?
|
||||||
|
|
||||||
|
We advise you do, so you don't lose everything if anything goes wrong!
|
||||||
|
|
||||||
|
More generally, we advise you make regular backups of your Castopod Host files
|
||||||
|
and database to prevent you from losing it all…
|
||||||
|
|
@ -1,2 +1,6 @@
|
||||||
<IfModule authz_core_module> Require all denied </IfModule>
|
<IfModule authz_core_module>
|
||||||
<IfModule !authz_core_module> Deny from all </IfModule>
|
Require all denied
|
||||||
|
</IfModule>
|
||||||
|
<IfModule !authz_core_module>
|
||||||
|
Deny from all
|
||||||
|
</IfModule>
|
||||||
|
|
|
||||||
54
app/Authorization/FlatAuthorization.php
Normal file
54
app/Authorization/FlatAuthorization.php
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Authorization;
|
||||||
|
|
||||||
|
use Myth\Auth\Authorization\FlatAuthorization as MythAuthFlatAuthorization;
|
||||||
|
|
||||||
|
class FlatAuthorization extends MythAuthFlatAuthorization
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The group model to use. Usually the class noted below (or an extension thereof) but can be any compatible
|
||||||
|
* CodeIgniter Model.
|
||||||
|
*
|
||||||
|
* @var PermissionModel
|
||||||
|
*/
|
||||||
|
protected $permissionModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks a group to see if they have the specified permission.
|
||||||
|
*/
|
||||||
|
public function groupHasPermission(int | string $permission, int $groupId): bool
|
||||||
|
{
|
||||||
|
// Get the Permission ID
|
||||||
|
$permissionId = $this->getPermissionID($permission);
|
||||||
|
|
||||||
|
if (! is_numeric($permissionId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->permissionModel->doesGroupHavePermission($groupId, $permissionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes user part of given groups.
|
||||||
|
*
|
||||||
|
* @param array<string, string> $groups Either collection of ID or names
|
||||||
|
*/
|
||||||
|
public function setUserGroups(int $userId, array $groups = []): bool
|
||||||
|
{
|
||||||
|
// remove user from all groups before resetting it in new groups
|
||||||
|
$this->groupModel->removeUserFromAllGroups($userId);
|
||||||
|
|
||||||
|
if ($groups === []) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($groups as $group) {
|
||||||
|
$this->addUserToGroup($userId, $group);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
app/Authorization/GroupModel.php
Normal file
30
app/Authorization/GroupModel.php
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Authorization;
|
||||||
|
|
||||||
|
use Myth\Auth\Authorization\GroupModel as MythAuthGroupModel;
|
||||||
|
|
||||||
|
class GroupModel extends MythAuthGroupModel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return mixed[]
|
||||||
|
*/
|
||||||
|
public function getContributorRoles(): array
|
||||||
|
{
|
||||||
|
return $this->select('auth_groups.*')
|
||||||
|
->like('name', 'podcast_', 'after')
|
||||||
|
->findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed[]
|
||||||
|
*/
|
||||||
|
public function getUserRoles(): array
|
||||||
|
{
|
||||||
|
return $this->select('auth_groups.*')
|
||||||
|
->notLike('name', 'podcast_', 'after')
|
||||||
|
->findAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
53
app/Authorization/PermissionModel.php
Normal file
53
app/Authorization/PermissionModel.php
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Authorization;
|
||||||
|
|
||||||
|
use Myth\Auth\Authorization\PermissionModel as MythAuthPermissionModel;
|
||||||
|
|
||||||
|
class PermissionModel extends MythAuthPermissionModel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Checks to see if a user, or one of their groups, has a specific permission.
|
||||||
|
*/
|
||||||
|
public function doesGroupHavePermission(int $groupId, int $permissionId): bool
|
||||||
|
{
|
||||||
|
// Check group permissions and take advantage of caching
|
||||||
|
$groupPerms = $this->getPermissionsForGroup($groupId);
|
||||||
|
|
||||||
|
return count($groupPerms) &&
|
||||||
|
array_key_exists($permissionId, $groupPerms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all permissions for a group in a way that can be easily used to check against:
|
||||||
|
*
|
||||||
|
* [ id => name, id => name ]
|
||||||
|
*
|
||||||
|
* @return array<int, string>
|
||||||
|
*/
|
||||||
|
public function getPermissionsForGroup(int $groupId): array
|
||||||
|
{
|
||||||
|
$cacheName = "group{$groupId}_permissions";
|
||||||
|
if (! ($found = cache($cacheName))) {
|
||||||
|
$groupPermissions = $this->db
|
||||||
|
->table('auth_groups_permissions')
|
||||||
|
->select('id, auth_permissions.name')
|
||||||
|
->join('auth_permissions', 'auth_permissions.id = permission_id', 'inner')
|
||||||
|
->where('group_id', $groupId)
|
||||||
|
->get()
|
||||||
|
->getResultObject();
|
||||||
|
|
||||||
|
$found = [];
|
||||||
|
foreach ($groupPermissions as $row) {
|
||||||
|
$found[$row->id] = strtolower($row->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
cache()
|
||||||
|
->save($cacheName, $found, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
<?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,49 +2,13 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use ViewThemes\Theme;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The goal of this file is to allow developers a location where they can overwrite core procedural functions and
|
* The goal of this file is to allow developers a location where they can overwrite core procedural functions and
|
||||||
* replace them with their own. This file is loaded during the bootstrap process and is called during the framework's
|
* replace them with their own. This file is loaded during the bootstrap process and is called during the frameworks
|
||||||
* execution.
|
* execution.
|
||||||
*
|
*
|
||||||
* This can be looked at as a `master helper` file that is loaded early on, and may also contain additional functions
|
* 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
|
* that you'd like to use throughout your entire application
|
||||||
*
|
*
|
||||||
* @see: https://codeigniter.com/user_guide/extending/common.html
|
* @link: https://codeigniter4.github.io/CodeIgniter4/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (! function_exists('view')) {
|
|
||||||
/**
|
|
||||||
* Grabs the current RendererInterface-compatible class and tells it to render the specified view. Simply provides a
|
|
||||||
* convenience method that can be used in Controllers, libraries, and routed closures.
|
|
||||||
*
|
|
||||||
* NOTE: Does not provide any escaping of the data, so that must all be handled manually by the developer.
|
|
||||||
*
|
|
||||||
* @param array<string, mixed> $data
|
|
||||||
* @param array<string, mixed> $options Unused - reserved for third-party extensions.
|
|
||||||
*/
|
|
||||||
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')
|
|
||||||
->saveData;
|
|
||||||
|
|
||||||
if (array_key_exists('saveData', $options)) {
|
|
||||||
$saveData = (bool) $options['saveData'];
|
|
||||||
unset($options['saveData']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $renderer->setData($data, 'raw')
|
|
||||||
->render($name, $options, $saveData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
31
app/Config/ActivityPub.php
Normal file
31
app/Config/ActivityPub.php
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Config;
|
||||||
|
|
||||||
|
use ActivityPub\Config\ActivityPub as ActivityPubBase;
|
||||||
|
use App\Libraries\NoteObject;
|
||||||
|
|
||||||
|
class ActivityPub extends ActivityPubBase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* --------------------------------------------------------------------
|
||||||
|
* ActivityPub Objects
|
||||||
|
* --------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
public string $noteObject = NoteObject::class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* --------------------------------------------------------------------
|
||||||
|
* Default avatar and cover images
|
||||||
|
* --------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
public string $defaultAvatarImagePath = 'assets/images/castopod-avatar-default.jpg';
|
||||||
|
|
||||||
|
public string $defaultAvatarImageMimetype = 'image/jpeg';
|
||||||
|
|
||||||
|
public string $defaultCoverImagePath = 'assets/images/castopod-cover-default.jpg';
|
||||||
|
|
||||||
|
public string $defaultCoverImageMimetype = 'image/jpeg';
|
||||||
|
}
|
||||||
43
app/Config/Analytics.php
Normal file
43
app/Config/Analytics.php
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Config;
|
||||||
|
|
||||||
|
use Analytics\Config\Analytics as AnalyticsBase;
|
||||||
|
|
||||||
|
class Analytics extends AnalyticsBase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* --------------------------------------------------------------------
|
||||||
|
* Route filters options
|
||||||
|
* --------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
public array $routeFilters = [
|
||||||
|
'analytics-full-data' => 'permission:podcasts-view,podcast-view',
|
||||||
|
'analytics-data' => 'permission:podcasts-view,podcast-view',
|
||||||
|
'analytics-filtered-data' => 'permission:podcasts-view,podcast-view',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
// set the analytics gateway behind the admin gateway.
|
||||||
|
// Only logged in users should be able to view analytics
|
||||||
|
$this->gateway = config('App')
|
||||||
|
->adminGateway . '/analytics';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the full audio file url
|
||||||
|
*
|
||||||
|
* @param string|string[] $audioFilePath
|
||||||
|
*/
|
||||||
|
public function getAudioFileUrl($audioFilePath): string
|
||||||
|
{
|
||||||
|
helper('media');
|
||||||
|
|
||||||
|
return media_base_url($audioFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||||
namespace Config;
|
namespace Config;
|
||||||
|
|
||||||
use CodeIgniter\Config\BaseConfig;
|
use CodeIgniter\Config\BaseConfig;
|
||||||
use Override;
|
use CodeIgniter\Session\Handlers\FileHandler;
|
||||||
|
|
||||||
class App extends BaseConfig
|
class App extends BaseConfig
|
||||||
{
|
{
|
||||||
|
|
@ -14,34 +14,38 @@ class App extends BaseConfig
|
||||||
* Base Site URL
|
* 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:
|
* WITH a trailing slash:
|
||||||
*
|
*
|
||||||
* E.g., http://example.com/
|
* 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.
|
||||||
*/
|
*/
|
||||||
public string $baseURL = 'http://localhost:8080/';
|
public string $baseURL = 'http://localhost:8080/';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allowed Hostnames in the Site URL other than the hostname in the baseURL.
|
* --------------------------------------------------------------------------
|
||||||
* If you want to accept multiple Hostnames, set this.
|
* Media Base URL
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
*
|
*
|
||||||
* E.g.,
|
* URL to your media root. Typically this will be your base URL,
|
||||||
* When your site URL ($baseURL) is 'http://example.com/', and your site
|
* WITH a trailing slash:
|
||||||
* also accepts 'http://media.example.com/' and 'http://accounts.example.com/':
|
|
||||||
* ['media.example.com', 'accounts.example.com']
|
|
||||||
*
|
*
|
||||||
* @var list<string>
|
* http://cdn.example.com/
|
||||||
*/
|
*/
|
||||||
public array $allowedHostnames = [];
|
public string $mediaBaseURL = 'http://localhost:8080/';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
* Index File
|
* Index File
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
*
|
*
|
||||||
* Typically, this will be your `index.php` file, unless you've renamed it to
|
* 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
|
* something else. If you are using mod_rewrite to remove the page set this
|
||||||
* from your site URIs, set this variable to an empty string.
|
* variable so that it is blank.
|
||||||
*/
|
*/
|
||||||
public string $indexPage = '';
|
public string $indexPage = '';
|
||||||
|
|
||||||
|
|
@ -50,42 +54,18 @@ class App extends BaseConfig
|
||||||
* URI PROTOCOL
|
* URI PROTOCOL
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
*
|
*
|
||||||
* This item determines which server global should be used to retrieve the
|
* This item determines which getServer 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:
|
* If your links do not seem to work, try one of the other delicious flavors:
|
||||||
*
|
*
|
||||||
* 'REQUEST_URI': Uses $_SERVER['REQUEST_URI']
|
* 'REQUEST_URI' Uses $_SERVER['REQUEST_URI']
|
||||||
* 'QUERY_STRING': Uses $_SERVER['QUERY_STRING']
|
* 'QUERY_STRING' Uses $_SERVER['QUERY_STRING']
|
||||||
* 'PATH_INFO': Uses $_SERVER['PATH_INFO']
|
* 'PATH_INFO' Uses $_SERVER['PATH_INFO']
|
||||||
*
|
*
|
||||||
* WARNING: If you set this to 'PATH_INFO', URIs will always be URL-decoded!
|
* WARNING: If you set this to 'PATH_INFO', URIs will always be URL-decoded!
|
||||||
*/
|
*/
|
||||||
public string $uriProtocol = 'REQUEST_URI';
|
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
|
* Default Locale
|
||||||
|
|
@ -119,23 +99,9 @@ class App extends BaseConfig
|
||||||
* by the application in descending order of priority. If no match is
|
* by the application in descending order of priority. If no match is
|
||||||
* found, the first locale will be used.
|
* found, the first locale will be used.
|
||||||
*
|
*
|
||||||
* IncomingRequest::setLocale() also uses this list.
|
* @var string[]
|
||||||
*
|
|
||||||
* @var list<string>
|
|
||||||
*/
|
*/
|
||||||
public array $supportedLocales = [
|
public array $supportedLocales = ['en', 'fr'];
|
||||||
'en',
|
|
||||||
'fr',
|
|
||||||
'pl',
|
|
||||||
'de',
|
|
||||||
'pt-br',
|
|
||||||
'nn-no',
|
|
||||||
'es',
|
|
||||||
'zh-hans',
|
|
||||||
'ca',
|
|
||||||
'br',
|
|
||||||
'sr-latn',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
|
|
@ -144,9 +110,6 @@ class App extends BaseConfig
|
||||||
*
|
*
|
||||||
* The default timezone that will be used in your application to display
|
* The default timezone that will be used in your application to display
|
||||||
* dates with the date helper, and can be retrieved through app_timezone()
|
* 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';
|
public string $appTimezone = 'UTC';
|
||||||
|
|
||||||
|
|
@ -170,10 +133,170 @@ class App extends BaseConfig
|
||||||
* If true, this will force every request made to this application to be
|
* 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
|
* 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
|
* secure, the user will be redirected to a secure version of the page
|
||||||
* and the HTTP Strict Transport Security (HSTS) header will be set.
|
* and the HTTP Strict Transport Security header will be set.
|
||||||
*/
|
*/
|
||||||
public bool $forceGlobalSecureRequests = true;
|
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 use Config\Cookie::$samesite property instead.
|
||||||
|
*/
|
||||||
|
public string $cookieSameSite = 'Lax';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
* Reverse Proxy IPs
|
* Reverse Proxy IPs
|
||||||
|
|
@ -181,21 +304,103 @@ class App extends BaseConfig
|
||||||
*
|
*
|
||||||
* If your server is behind a reverse proxy, you must whitelist the proxy
|
* If your server is behind a reverse proxy, you must whitelist the proxy
|
||||||
* IP addresses from which CodeIgniter should trust headers such as
|
* IP addresses from which CodeIgniter should trust headers such as
|
||||||
* X-Forwarded-For or Client-IP in order to properly identify
|
* HTTP_X_FORWARDED_FOR and HTTP_CLIENT_IP in order to properly identify
|
||||||
* the visitor's IP address.
|
* the visitor's IP address.
|
||||||
*
|
*
|
||||||
* You need to set a proxy IP address or IP address with subnets and
|
* You can use both an array or a comma-separated list of proxy addresses,
|
||||||
* the HTTP header for the client IP address.
|
* as well as specifying whole subnets. Here are a few examples:
|
||||||
*
|
*
|
||||||
* Here are some examples:
|
* Comma-separated: '10.0.1.200,192.168.5.0/24'
|
||||||
* [
|
* Array: ['10.0.1.200', '192.168.5.0/24']
|
||||||
* '10.0.1.200' => 'X-Forwarded-For',
|
|
||||||
* '192.168.5.0/24' => 'X-Real-IP',
|
|
||||||
* ]
|
|
||||||
*
|
*
|
||||||
* @var array<string, string>|string
|
* @var string|string[]
|
||||||
*/
|
*/
|
||||||
public $proxyIPs = [];
|
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';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
|
|
@ -217,70 +422,33 @@ class App extends BaseConfig
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
* Instance / Site Config
|
* Media root folder
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
|
* Defines the root folder for media files storage
|
||||||
*/
|
*/
|
||||||
public string $siteName = 'Castopod';
|
public string $mediaRoot = 'media';
|
||||||
|
|
||||||
public string $siteTitleSeparator = ' | ';
|
|
||||||
|
|
||||||
public string $siteDescription = 'Castopod is an open-source hosting platform made for podcasters who want engage and interact with their audience.';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array<int|string, string>
|
* --------------------------------------------------------------------------
|
||||||
|
* Admin gateway
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
* Defines a base route for all admin pages
|
||||||
*/
|
*/
|
||||||
public array $siteIcon = [
|
public string $adminGateway = 'cp-admin';
|
||||||
'ico' => '/favicon.ico',
|
|
||||||
'64' => '/icon-64.png',
|
|
||||||
'180' => '/icon-180.png',
|
|
||||||
'192' => '/icon-192.png',
|
|
||||||
'512' => '/icon-512.png',
|
|
||||||
];
|
|
||||||
|
|
||||||
public string $theme = 'pine';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Storage limit in Gigabytes
|
* --------------------------------------------------------------------------
|
||||||
|
* Auth gateway
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
* Defines a base route for all authentication related pages
|
||||||
*/
|
*/
|
||||||
public ?int $storageLimit = null;
|
public string $authGateway = 'cp-auth';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bandwidth limit (per month) in Gigabytes
|
* --------------------------------------------------------------------------
|
||||||
|
* Install gateway
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
* Defines a base route for instance installation
|
||||||
*/
|
*/
|
||||||
public ?int $bandwidthLimit = null;
|
public string $installGateway = 'cp-install';
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
58
app/Config/Auth.php
Normal file
58
app/Config/Auth.php
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Config;
|
||||||
|
|
||||||
|
use Myth\Auth\Config\Auth as MythAuthConfig;
|
||||||
|
|
||||||
|
class Auth extends MythAuthConfig
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
* Views used by Auth Controllers
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* @var array<string, string>
|
||||||
|
*/
|
||||||
|
public $views = [
|
||||||
|
'login' => 'auth/login',
|
||||||
|
'register' => 'auth/register',
|
||||||
|
'forgot' => 'auth/forgot',
|
||||||
|
'reset' => 'auth/reset',
|
||||||
|
'emailForgot' => 'auth/emails/forgot',
|
||||||
|
'emailActivation' => 'auth/emails/activation',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
* Layout for the views to extend
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $viewLayout = 'auth/_layout';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
* Allow User Registration
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
* When enabled (default) any unregistered user may apply for a new
|
||||||
|
* account. If you disable registration you may need to ensure your
|
||||||
|
* controllers and views know not to offer registration.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $allowRegistration = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
* Require confirmation registration via email
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
* When enabled, every registered user will receive an email message
|
||||||
|
* with a special link he have to confirm to activate his account.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $requireActivation = false;
|
||||||
|
}
|
||||||
|
|
@ -27,39 +27,30 @@ class Autoload extends AutoloadConfig
|
||||||
* their location on the file system. These are used by the autoloader
|
* their location on the file system. These are used by the autoloader
|
||||||
* to locate files the first time they have been instantiated.
|
* to locate files the first time they have been instantiated.
|
||||||
*
|
*
|
||||||
* The 'Config' (APPPATH . 'Config') and 'CodeIgniter' (SYSTEMPATH) are
|
* The '/app' and '/system' directories are already mapped for you.
|
||||||
* already mapped for you.
|
* you may change the name of the 'App' namespace if you wish,
|
||||||
*
|
|
||||||
* You may change the name of the 'App' namespace if you wish,
|
|
||||||
* but this should be done prior to creating any namespaced classes,
|
* 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.
|
* else you will need to modify all of those classes for this to work.
|
||||||
*
|
*
|
||||||
* @var array<string, list<string>|string>
|
* Prototype:
|
||||||
|
*
|
||||||
|
* $psr4 = [
|
||||||
|
* 'CodeIgniter' => SYSTEMPATH,
|
||||||
|
* 'App' => APPPATH
|
||||||
|
* ];
|
||||||
|
*
|
||||||
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
public $psr4 = [
|
public $psr4 = [
|
||||||
APP_NAMESPACE => APPPATH,
|
APP_NAMESPACE => APPPATH,
|
||||||
'Modules' => ROOTPATH . 'modules/',
|
'Config' => APPPATH . 'Config',
|
||||||
'Modules\Admin' => ROOTPATH . 'modules/Admin/',
|
'ActivityPub' => APPPATH . 'Libraries/ActivityPub',
|
||||||
'Modules\Analytics' => ROOTPATH . 'modules/Analytics/',
|
'Analytics' => APPPATH . 'Libraries/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/',
|
|
||||||
'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
|
* The class map provides a map of class names and their exact
|
||||||
* location on the drive. Classes loaded in this manner will have
|
* location on the drive. Classes loaded in this manner will have
|
||||||
|
|
@ -86,25 +77,12 @@ class Autoload extends AutoloadConfig
|
||||||
* or for loading functions.
|
* or for loading functions.
|
||||||
*
|
*
|
||||||
* Prototype:
|
* Prototype:
|
||||||
*
|
* ```
|
||||||
* $files = [
|
* $files = [
|
||||||
* '/path/to/my/file.php',
|
* '/path/to/my/file.php',
|
||||||
* ];
|
* ];
|
||||||
*
|
* ```
|
||||||
* @var list<string>
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
public $files = [];
|
public $files = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* -------------------------------------------------------------------
|
|
||||||
* Helpers
|
|
||||||
* -------------------------------------------------------------------
|
|
||||||
* Prototype:
|
|
||||||
* $helpers = [
|
|
||||||
* 'form',
|
|
||||||
* ];
|
|
||||||
*
|
|
||||||
* @var list<string>
|
|
||||||
*/
|
|
||||||
public $helpers = ['auth', 'setting', 'plugins'];
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,8 @@ declare(strict_types=1);
|
||||||
* In development, we want to show as many errors as possible to help
|
* 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
|
* make sure they don't make it to production. And save us hours of
|
||||||
* painful debugging.
|
* painful debugging.
|
||||||
*
|
|
||||||
* If you set 'display_errors' to '1', CI4's detailed error report will show.
|
|
||||||
*/
|
*/
|
||||||
error_reporting(E_ALL);
|
error_reporting(-1);
|
||||||
ini_set('display_errors', '1');
|
ini_set('display_errors', '1');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,9 @@ declare(strict_types=1);
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
* Don't show ANY in production environments. Instead, let the system catch
|
* Don't show ANY in production environments. Instead, let the system catch
|
||||||
* it and display a generic error message.
|
* 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');
|
ini_set('display_errors', '0');
|
||||||
|
error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT & ~E_USER_NOTICE & ~E_USER_DEPRECATED);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,6 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
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
|
* ERROR DISPLAY
|
||||||
|
|
@ -16,7 +10,7 @@ declare(strict_types=1);
|
||||||
* make sure they don't make it to production. And save us hours of
|
* make sure they don't make it to production. And save us hours of
|
||||||
* painful debugging.
|
* painful debugging.
|
||||||
*/
|
*/
|
||||||
error_reporting(E_ALL);
|
error_reporting(-1);
|
||||||
ini_set('display_errors', '1');
|
ini_set('display_errors', '1');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Config;
|
|
||||||
|
|
||||||
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
|
|
||||||
* --------------------------------------------------------------------------
|
|
||||||
*
|
|
||||||
* Whether share options between requests or not.
|
|
||||||
*
|
|
||||||
* If true, all the options won't be reset between requests.
|
|
||||||
* It may cause an error request with unnecessary headers.
|
|
||||||
*/
|
|
||||||
public bool $shareOptions = false;
|
|
||||||
}
|
|
||||||
|
|
@ -4,8 +4,6 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Config;
|
namespace Config;
|
||||||
|
|
||||||
use CodeIgniter\Cache\CacheInterface;
|
|
||||||
use CodeIgniter\Cache\Handlers\ApcuHandler;
|
|
||||||
use CodeIgniter\Cache\Handlers\DummyHandler;
|
use CodeIgniter\Cache\Handlers\DummyHandler;
|
||||||
use CodeIgniter\Cache\Handlers\FileHandler;
|
use CodeIgniter\Cache\Handlers\FileHandler;
|
||||||
use CodeIgniter\Cache\Handlers\MemcachedHandler;
|
use CodeIgniter\Cache\Handlers\MemcachedHandler;
|
||||||
|
|
@ -37,6 +35,37 @@ class Cache extends BaseConfig
|
||||||
*/
|
*/
|
||||||
public string $backupHandler = 'dummy';
|
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
|
* Key Prefix
|
||||||
|
|
@ -60,51 +89,36 @@ class Cache extends BaseConfig
|
||||||
*/
|
*/
|
||||||
public int $ttl = 60;
|
public int $ttl = 60;
|
||||||
|
|
||||||
/**
|
|
||||||
* --------------------------------------------------------------------------
|
|
||||||
* Reserved Characters
|
|
||||||
* --------------------------------------------------------------------------
|
|
||||||
*
|
|
||||||
* 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 = '{}()/\@:';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
* File settings
|
* File settings
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
*
|
|
||||||
* Your file storage preferences can be specified below, if you are using
|
* Your file storage preferences can be specified below, if you are using
|
||||||
* the File driver.
|
* the File driver.
|
||||||
*
|
*
|
||||||
* @var array{storePath?: string, mode?: int}
|
* @var array<string, string|int|null>
|
||||||
*/
|
*/
|
||||||
public array $file = [
|
public array $file = [
|
||||||
'storePath' => WRITEPATH . 'cache/',
|
'storePath' => WRITEPATH . 'cache/',
|
||||||
'mode' => 0640,
|
'mode' => 0640,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* -------------------------------------------------------------------------
|
* -------------------------------------------------------------------------
|
||||||
* Memcached settings
|
* Memcached settings
|
||||||
* -------------------------------------------------------------------------
|
* -------------------------------------------------------------------------
|
||||||
*
|
|
||||||
* Your Memcached servers can be specified below, if you are using
|
* Your Memcached servers can be specified below, if you are using
|
||||||
* the Memcached drivers.
|
* the Memcached drivers.
|
||||||
*
|
*
|
||||||
* @see https://codeigniter.com/user_guide/libraries/caching.html#memcached
|
* @see https://codeigniter.com/user_guide/libraries/caching.html#memcached
|
||||||
*
|
*
|
||||||
* @var array{host?: string, port?: int, weight?: int, raw?: bool}
|
* @var array<string, string|int|boolean>
|
||||||
*/
|
*/
|
||||||
public array $memcached = [
|
public array $memcached = [
|
||||||
'host' => '127.0.0.1',
|
'host' => '127.0.0.1',
|
||||||
'port' => 11211,
|
'port' => 11211,
|
||||||
'weight' => 1,
|
'weight' => 1,
|
||||||
'raw' => false,
|
'raw' => false,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -114,24 +128,14 @@ class Cache extends BaseConfig
|
||||||
* Your Redis server can be specified below, if you are using
|
* Your Redis server can be specified below, if you are using
|
||||||
* the Redis or Predis drivers.
|
* the Redis or Predis drivers.
|
||||||
*
|
*
|
||||||
* @var array{
|
* @var array<string, string|int|null>
|
||||||
* host?: string,
|
|
||||||
* password?: string|null,
|
|
||||||
* port?: int,
|
|
||||||
* timeout?: int,
|
|
||||||
* async?: bool,
|
|
||||||
* persistent?: bool,
|
|
||||||
* database?: int
|
|
||||||
* }
|
|
||||||
*/
|
*/
|
||||||
public array $redis = [
|
public array $redis = [
|
||||||
'host' => '127.0.0.1',
|
'host' => '127.0.0.1',
|
||||||
'password' => null,
|
'password' => null,
|
||||||
'port' => 6379,
|
'port' => 6379,
|
||||||
'timeout' => 0,
|
'timeout' => 0,
|
||||||
'async' => false, // specific to Predis and ignored by the native Redis extension
|
'database' => 0,
|
||||||
'persistent' => false,
|
|
||||||
'database' => 0,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -142,58 +146,14 @@ class Cache extends BaseConfig
|
||||||
* This is an array of cache engine alias' and class names. Only engines
|
* This is an array of cache engine alias' and class names. Only engines
|
||||||
* that are listed here are allowed to be used.
|
* that are listed here are allowed to be used.
|
||||||
*
|
*
|
||||||
* @var array<string, class-string<CacheInterface>>
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
public array $validHandlers = [
|
public array $validHandlers = [
|
||||||
'apcu' => ApcuHandler::class,
|
'dummy' => DummyHandler::class,
|
||||||
'dummy' => DummyHandler::class,
|
'file' => FileHandler::class,
|
||||||
'file' => FileHandler::class,
|
|
||||||
'memcached' => MemcachedHandler::class,
|
'memcached' => MemcachedHandler::class,
|
||||||
'predis' => PredisHandler::class,
|
'predis' => PredisHandler::class,
|
||||||
'redis' => RedisHandler::class,
|
'redis' => RedisHandler::class,
|
||||||
'wincache' => WincacheHandler::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 = [];
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,150 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Config;
|
|
||||||
|
|
||||||
use CodeIgniter\Config\BaseConfig;
|
|
||||||
|
|
||||||
class Colors extends BaseConfig
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var array<string, array<string, mixed>>
|
|
||||||
*/
|
|
||||||
public array $themes = [
|
|
||||||
/* Castopod's brand color */
|
|
||||||
'pine' => [
|
|
||||||
'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-navigation' => [172, 100, 17],
|
|
||||||
'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-navigation' => [131, 100, 12],
|
|
||||||
|
|
||||||
'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-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-highlight' => [344, 79, 96],
|
|
||||||
'background-backdrop' => [0, 0, 50],
|
|
||||||
|
|
||||||
'border-subtle' => [348, 42, 86],
|
|
||||||
'border-contrast' => [0, 0, 0],
|
|
||||||
|
|
||||||
'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-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-highlight' => [195, 100, 92],
|
|
||||||
'background-backdrop' => [0, 0, 50],
|
|
||||||
|
|
||||||
'border-subtle' => [195, 42, 86],
|
|
||||||
'border-contrast' => [0, 0, 0],
|
|
||||||
|
|
||||||
'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-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-highlight' => [17, 100, 89],
|
|
||||||
'background-backdrop' => [0, 0, 50],
|
|
||||||
|
|
||||||
'border-subtle' => [17, 42, 86],
|
|
||||||
'border-contrast' => [0, 0, 0],
|
|
||||||
|
|
||||||
'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-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-highlight' => [254, 88, 91],
|
|
||||||
'background-backdrop' => [0, 0, 50],
|
|
||||||
|
|
||||||
'border-subtle' => [254, 42, 86],
|
|
||||||
'border-contrast' => [0, 0, 0],
|
|
||||||
|
|
||||||
'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-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-highlight' => [240, 17, 94],
|
|
||||||
'background-backdrop' => [0, 0, 50],
|
|
||||||
|
|
||||||
'border-subtle' => [240, 17, 86],
|
|
||||||
'border-contrast' => [0, 0, 0],
|
|
||||||
|
|
||||||
'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.
|
| NOTE: this constant is updated upon release with Continuous Integration.
|
||||||
*/
|
*/
|
||||||
defined('CP_VERSION') || define('CP_VERSION', '2.0.0-next.3');
|
defined('CP_VERSION') || define('CP_VERSION', '1.0.0-alpha.80');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
| --------------------------------------------------------------------
|
| --------------------------------------------------------------------
|
||||||
|
|
@ -24,23 +24,10 @@ defined('CP_VERSION') || define('CP_VERSION', '2.0.0-next.3');
|
||||||
| classes should use.
|
| classes should use.
|
||||||
|
|
|
|
||||||
| NOTE: changing this will require manually modifying the
|
| 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');
|
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
|
| Composer Path
|
||||||
|
|
@ -65,9 +52,9 @@ defined('MINUTE') || define('MINUTE', 60);
|
||||||
defined('HOUR') || define('HOUR', 3600);
|
defined('HOUR') || define('HOUR', 3600);
|
||||||
defined('DAY') || define('DAY', 86400);
|
defined('DAY') || define('DAY', 86400);
|
||||||
defined('WEEK') || define('WEEK', 604800);
|
defined('WEEK') || define('WEEK', 604800);
|
||||||
defined('MONTH') || define('MONTH', 2_592_000);
|
defined('MONTH') || define('MONTH', 2592000);
|
||||||
defined('YEAR') || define('YEAR', 31_536_000);
|
defined('YEAR') || define('YEAR', 31536000);
|
||||||
defined('DECADE') || define('DECADE', 315_360_000);
|
defined('DECADE') || define('DECADE', 315360000);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
| --------------------------------------------------------------------------
|
| --------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -26,77 +26,37 @@ class ContentSecurityPolicy extends BaseConfig
|
||||||
*/
|
*/
|
||||||
public ?string $reportURI = null;
|
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
|
* 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.
|
* numbers of old URLs that need to be rewritten.
|
||||||
*/
|
*/
|
||||||
public bool $upgradeInsecureRequests = false;
|
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 list<string>|string|null
|
* @var string|string[]|null
|
||||||
*/
|
*/
|
||||||
public string | array | null $defaultSrc = null;
|
public string | array | null $defaultSrc = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists allowed scripts' URLs.
|
* Lists allowed scripts' URLs.
|
||||||
*
|
*
|
||||||
* @var list<string>|string
|
* @var string|string[]
|
||||||
*/
|
*/
|
||||||
public string | array $scriptSrc = 'self';
|
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.
|
* Lists allowed stylesheets' URLs.
|
||||||
*
|
*
|
||||||
* @var list<string>|string
|
* @var string|string[]
|
||||||
*/
|
*/
|
||||||
public string | array $styleSrc = 'self';
|
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.
|
* Defines the origins from which images can be loaded.
|
||||||
*
|
*
|
||||||
* @var list<string>|string
|
* @var string|string[]
|
||||||
*/
|
*/
|
||||||
public string | array $imageSrc = 'self';
|
public string | array $imageSrc = 'self';
|
||||||
|
|
||||||
|
|
@ -105,35 +65,35 @@ class ContentSecurityPolicy extends BaseConfig
|
||||||
*
|
*
|
||||||
* Will default to self if not overridden
|
* Will default to self if not overridden
|
||||||
*
|
*
|
||||||
* @var list<string>|string|null
|
* @var string|string[]|null
|
||||||
*/
|
*/
|
||||||
public string | array | null $baseURI = null;
|
public string | array | null $baseURI = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists the URLs for workers and embedded frame contents
|
* Lists the URLs for workers and embedded frame contents
|
||||||
*
|
*
|
||||||
* @var list<string>|string
|
* @var string|string[]
|
||||||
*/
|
*/
|
||||||
public string | array $childSrc = 'self';
|
public string | array $childSrc = 'self';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Limits the origins that you can connect to (via XHR, WebSockets, and EventSource).
|
* Limits the origins that you can connect to (via XHR, WebSockets, and EventSource).
|
||||||
*
|
*
|
||||||
* @var list<string>|string
|
* @var string|string[]
|
||||||
*/
|
*/
|
||||||
public string | array $connectSrc = 'self';
|
public string | array $connectSrc = 'self';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies the origins that can serve web fonts.
|
* Specifies the origins that can serve web fonts.
|
||||||
*
|
*
|
||||||
* @var list<string>|string
|
* @var string|string[]
|
||||||
*/
|
*/
|
||||||
public string | array $fontSrc;
|
public string | array $fontSrc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists valid endpoints for submission from `<form>` tags.
|
* Lists valid endpoints for submission from `<form>` tags.
|
||||||
*
|
*
|
||||||
* @var list<string>|string
|
* @var string|string[]
|
||||||
*/
|
*/
|
||||||
public string | array $formAction = 'self';
|
public string | array $formAction = 'self';
|
||||||
|
|
||||||
|
|
@ -142,67 +102,47 @@ class ContentSecurityPolicy extends BaseConfig
|
||||||
* `<embed>`, and `<applet>` tags. This directive can't be used in `<meta>` tags and applies only to non-HTML
|
* `<embed>`, and `<applet>` tags. This directive can't be used in `<meta>` tags and applies only to non-HTML
|
||||||
* resources.
|
* resources.
|
||||||
*
|
*
|
||||||
* @var list<string>|string|null
|
* @var string|string[]|null
|
||||||
*/
|
*/
|
||||||
public string | array | null $frameAncestors = null;
|
public string | array | null $frameAncestors = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The frame-src directive restricts the URLs which may be loaded into nested browsing contexts.
|
* The frame-src directive restricts the URLs which may be loaded into nested browsing contexts.
|
||||||
*
|
*
|
||||||
* @var list<string>|string|null
|
* @var string[]|string|null
|
||||||
*/
|
*/
|
||||||
public string | array | null $frameSrc = null;
|
public string | array | null $frameSrc = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restricts the origins allowed to deliver video and audio.
|
* Restricts the origins allowed to deliver video and audio.
|
||||||
*
|
*
|
||||||
* @var list<string>|string|null
|
* @var string|string[]|null
|
||||||
*/
|
*/
|
||||||
public string | array | null $mediaSrc = null;
|
public string | array | null $mediaSrc = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows control over Flash and other plugins.
|
* Allows control over Flash and other plugins.
|
||||||
*
|
*
|
||||||
* @var list<string>|string
|
* @var string|string[]
|
||||||
*/
|
*/
|
||||||
public string | array $objectSrc = 'self';
|
public string | array $objectSrc = 'self';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var list<string>|string|null
|
* @var string|string[]|null
|
||||||
*/
|
*/
|
||||||
public string | array | null $manifestSrc = null;
|
public string | array | null $manifestSrc = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var list<string>|string
|
|
||||||
*/
|
|
||||||
public array|string $workerSrc = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Limits the kinds of plugins a page may invoke.
|
* Limits the kinds of plugins a page may invoke.
|
||||||
*
|
*
|
||||||
* @var list<string>|string|null
|
* @var string|string[]|null
|
||||||
*/
|
*/
|
||||||
public string | array | null $pluginTypes = null;
|
public string | array | null $pluginTypes = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of actions allowed.
|
* List of actions allowed.
|
||||||
*
|
*
|
||||||
* @var list<string>|string|null
|
* @var string|string[]|null
|
||||||
*/
|
*/
|
||||||
public string | array | null $sandbox = null;
|
public string | array | null $sandbox = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* Nonce placeholder for style tags.
|
|
||||||
*/
|
|
||||||
public string $styleNonceTag = '{csp-style-nonce}';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Nonce placeholder for script tags.
|
|
||||||
*/
|
|
||||||
public string $scriptNonceTag = '{csp-script-nonce}';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace nonce tag automatically?
|
|
||||||
*/
|
|
||||||
public bool $autoNonce = true;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,8 +84,6 @@ class Cookie extends BaseConfig
|
||||||
* Defaults to `Lax` for compatibility with modern browsers. Setting `''`
|
* Defaults to `Lax` for compatibility with modern browsers. Setting `''`
|
||||||
* (empty string) means default SameSite attribute set by browsers (`Lax`)
|
* (empty string) means default SameSite attribute set by browsers (`Lax`)
|
||||||
* will be set on cookies. If set to `None`, `$secure` must also be set.
|
* will be set on cookies. If set to `None`, `$secure` must also be set.
|
||||||
*
|
|
||||||
* @var ''|'Lax'|'None'|'Strict'
|
|
||||||
*/
|
*/
|
||||||
public string $samesite = 'Lax';
|
public string $samesite = 'Lax';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
<?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,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
@ -24,67 +24,53 @@ class Database extends Config
|
||||||
/**
|
/**
|
||||||
* The default database connection.
|
* The default database connection.
|
||||||
*
|
*
|
||||||
* @var array<string, mixed>
|
* @var array<string, string|bool|int|array>
|
||||||
*/
|
*/
|
||||||
public array $default = [
|
public array $default = [
|
||||||
'DSN' => '',
|
'DSN' => '',
|
||||||
'hostname' => 'localhost',
|
'hostname' => 'localhost',
|
||||||
'username' => '',
|
'username' => '',
|
||||||
'password' => '',
|
'password' => '',
|
||||||
'database' => '',
|
'database' => '',
|
||||||
'DBDriver' => 'MySQLi',
|
'DBDriver' => 'MySQLi',
|
||||||
'DBPrefix' => 'cp_',
|
'DBPrefix' => 'cp_',
|
||||||
'pConnect' => false,
|
'pConnect' => false,
|
||||||
'DBDebug' => true,
|
'DBDebug' => ENVIRONMENT !== 'production',
|
||||||
'charset' => 'utf8mb4',
|
'charset' => 'utf8mb4',
|
||||||
'DBCollat' => 'utf8mb4_unicode_ci',
|
'DBCollat' => 'utf8mb4_unicode_ci',
|
||||||
'swapPre' => '',
|
'swapPre' => '',
|
||||||
'encrypt' => false,
|
'encrypt' => false,
|
||||||
'compress' => false,
|
'compress' => false,
|
||||||
'strictOn' => false,
|
'strictOn' => false,
|
||||||
'failover' => [],
|
'failover' => [],
|
||||||
'port' => 3306,
|
'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.
|
* This database connection is used when running PHPUnit database tests.
|
||||||
*
|
*
|
||||||
* @var array<string, mixed>
|
* @noRector StringClassNameToClassConstantRector
|
||||||
|
*
|
||||||
|
* @var array<string, string|bool|int|array>
|
||||||
*/
|
*/
|
||||||
public array $tests = [
|
public array $tests = [
|
||||||
'DSN' => '',
|
'DSN' => '',
|
||||||
'hostname' => '127.0.0.1',
|
'hostname' => '127.0.0.1',
|
||||||
'username' => '',
|
'username' => '',
|
||||||
'password' => '',
|
'password' => '',
|
||||||
'database' => ':memory:',
|
'database' => ':memory:',
|
||||||
'DBDriver' => 'SQLite3',
|
'DBDriver' => 'SQLite3',
|
||||||
'DBPrefix' => 'db_',
|
'DBPrefix' => 'db_',
|
||||||
// Needed to ensure we're working correctly with prefixes live. DO NOT REMOVE FOR CI DEVS
|
'pConnect' => false,
|
||||||
'pConnect' => false,
|
'DBDebug' => ENVIRONMENT !== 'production',
|
||||||
'DBDebug' => true,
|
'charset' => 'utf8',
|
||||||
'charset' => 'utf8',
|
'DBCollat' => 'utf8_general_ci',
|
||||||
'DBCollat' => '',
|
'swapPre' => '',
|
||||||
'swapPre' => '',
|
'encrypt' => false,
|
||||||
'encrypt' => false,
|
'compress' => false,
|
||||||
'compress' => false,
|
'strictOn' => false,
|
||||||
'strictOn' => false,
|
'failover' => [],
|
||||||
'failover' => [],
|
'port' => 3306,
|
||||||
'port' => 3306,
|
|
||||||
'foreignKeys' => true,
|
|
||||||
'busyTimeout' => 1000,
|
|
||||||
'synchronous' => null,
|
|
||||||
'dateFormat' => [
|
|
||||||
'date' => 'Y-m-d',
|
|
||||||
'datetime' => 'Y-m-d H:i:s',
|
|
||||||
'time' => 'H:i:s',
|
|
||||||
],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
//--------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -12,34 +12,42 @@ class DocTypes
|
||||||
* @var array<string, string>
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
public array $list = [
|
public array $list = [
|
||||||
'xhtml11' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
|
'xhtml11' =>
|
||||||
'xhtml1-strict' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
|
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
|
||||||
'xhtml1-trans' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
|
'xhtml1-strict' =>
|
||||||
'xhtml1-frame' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
|
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
|
||||||
'xhtml-basic11' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
|
'xhtml1-trans' =>
|
||||||
'html5' => '<!DOCTYPE html>',
|
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
|
||||||
'html4-strict' => '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">',
|
'xhtml1-frame' =>
|
||||||
'html4-trans' => '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
|
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
|
||||||
'html4-frame' => '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">',
|
'xhtml-basic11' =>
|
||||||
'mathml1' => '<!DOCTYPE math SYSTEM "http://www.w3.org/Math/DTD/mathml1/mathml.dtd">',
|
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
|
||||||
'mathml2' => '<!DOCTYPE math PUBLIC "-//W3C//DTD MathML 2.0//EN" "http://www.w3.org/Math/DTD/mathml2/mathml2.dtd">',
|
'html5' => '<!DOCTYPE html>',
|
||||||
'svg10' => '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">',
|
'html4-strict' =>
|
||||||
'svg11' => '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">',
|
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.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">',
|
'html4-trans' =>
|
||||||
'svg11-tiny' => '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">',
|
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.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">',
|
'html4-frame' =>
|
||||||
'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">',
|
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">',
|
||||||
'xhtml-rdfa-1' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">',
|
'mathml1' =>
|
||||||
'xhtml-rdfa-2' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.1//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-2.dtd">',
|
'<!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;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,21 +8,21 @@ use CodeIgniter\Config\BaseConfig;
|
||||||
|
|
||||||
class Email extends BaseConfig
|
class Email extends BaseConfig
|
||||||
{
|
{
|
||||||
public string $fromEmail = '';
|
public string $fromEmail;
|
||||||
|
|
||||||
public string $fromName = 'Castopod';
|
public string $fromName;
|
||||||
|
|
||||||
public string $recipients = '';
|
public string $recipients;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The "user agent"
|
* The "user agent"
|
||||||
*/
|
*/
|
||||||
public string $userAgent = 'Castopod/' . CP_VERSION;
|
public string $userAgent = 'CodeIgniter';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The mail sending protocol: mail, sendmail, smtp
|
* The mail sending protocol: mail, sendmail, smtp
|
||||||
*/
|
*/
|
||||||
public string $protocol = 'smtp';
|
public string $protocol = 'mail';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The server path to Sendmail.
|
* The server path to Sendmail.
|
||||||
|
|
@ -30,24 +30,19 @@ class Email extends BaseConfig
|
||||||
public string $mailPath = '/usr/sbin/sendmail';
|
public string $mailPath = '/usr/sbin/sendmail';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SMTP Server Hostname
|
* SMTP Server Address
|
||||||
*/
|
*/
|
||||||
public string $SMTPHost = '';
|
public string $SMTPHost;
|
||||||
|
|
||||||
/**
|
|
||||||
* Which SMTP authentication method to use: login, plain
|
|
||||||
*/
|
|
||||||
public string $SMTPAuthMethod = 'login';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SMTP Username
|
* SMTP Username
|
||||||
*/
|
*/
|
||||||
public string $SMTPUser = '';
|
public string $SMTPUser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SMTP Password
|
* SMTP Password
|
||||||
*/
|
*/
|
||||||
public string $SMTPPass = '';
|
public string $SMTPPass;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SMTP Port
|
* SMTP Port
|
||||||
|
|
@ -65,11 +60,7 @@ class Email extends BaseConfig
|
||||||
public bool $SMTPKeepAlive = false;
|
public bool $SMTPKeepAlive = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SMTP Encryption.
|
* SMTP Encryption. Either tls or ssl
|
||||||
*
|
|
||||||
* @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';
|
public string $SMTPCrypto = 'tls';
|
||||||
|
|
||||||
|
|
@ -86,7 +77,7 @@ class Email extends BaseConfig
|
||||||
/**
|
/**
|
||||||
* Type of mail, either 'text' or 'html'
|
* Type of mail, either 'text' or 'html'
|
||||||
*/
|
*/
|
||||||
public string $mailType = 'html';
|
public string $mailType = 'text';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Character set (utf-8, iso-8859-1, etc.)
|
* Character set (utf-8, iso-8859-1, etc.)
|
||||||
|
|
@ -127,11 +118,4 @@ class Email extends BaseConfig
|
||||||
* Enable notify message from server
|
* Enable notify message from server
|
||||||
*/
|
*/
|
||||||
public bool $DSN = false;
|
public bool $DSN = false;
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
|
|
||||||
$this->userAgent = 'Castopod/' . CP_VERSION . '; +' . base_url('', 'https');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Config;
|
|
||||||
|
|
||||||
use CodeIgniter\Config\BaseConfig;
|
|
||||||
|
|
||||||
class Embed extends BaseConfig
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* --------------------------------------------------------------------------
|
|
||||||
* Embeddable player config
|
|
||||||
* --------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
public int $width = 485;
|
|
||||||
|
|
||||||
public int $height = 112;
|
|
||||||
}
|
|
||||||
|
|
@ -25,23 +25,6 @@ class Encryption extends BaseConfig
|
||||||
*/
|
*/
|
||||||
public string $key = '';
|
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
|
* Encryption Driver to Use
|
||||||
|
|
@ -75,37 +58,4 @@ class Encryption extends BaseConfig
|
||||||
* HMAC digest to use, e.g. 'SHA512' or 'SHA256'. Default value is 'SHA512'.
|
* HMAC digest to use, e.g. 'SHA512' or 'SHA256'. Default value is 'SHA512'.
|
||||||
*/
|
*/
|
||||||
public string $digest = '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';
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,10 @@ declare(strict_types=1);
|
||||||
namespace Config;
|
namespace Config;
|
||||||
|
|
||||||
use App\Entities\Actor;
|
use App\Entities\Actor;
|
||||||
use App\Entities\Post;
|
use App\Entities\Status;
|
||||||
use App\Models\EpisodeModel;
|
use App\Entities\User;
|
||||||
use CodeIgniter\Debug\Toolbar\Collectors\Database;
|
|
||||||
use CodeIgniter\Events\Events;
|
use CodeIgniter\Events\Events;
|
||||||
use CodeIgniter\Exceptions\FrameworkException;
|
use CodeIgniter\Exceptions\FrameworkException;
|
||||||
use CodeIgniter\HotReloader\HotReloader;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* --------------------------------------------------------------------
|
* --------------------------------------------------------------------
|
||||||
|
|
@ -29,7 +27,8 @@ use CodeIgniter\HotReloader\HotReloader;
|
||||||
* Events::on('create', [$myInstance, 'myMethod']);
|
* Events::on('create', [$myInstance, 'myMethod']);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Events::on('pre_system', static function (): void {
|
Events::on('pre_system', function () {
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
if (ENVIRONMENT !== 'testing') {
|
if (ENVIRONMENT !== 'testing') {
|
||||||
if (ini_get('zlib.output_compression')) {
|
if (ini_get('zlib.output_compression')) {
|
||||||
throw FrameworkException::forEnabledZlibOutputCompression();
|
throw FrameworkException::forEnabledZlibOutputCompression();
|
||||||
|
|
@ -39,7 +38,9 @@ Events::on('pre_system', static function (): void {
|
||||||
ob_end_flush();
|
ob_end_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
ob_start(static fn ($buffer) => $buffer);
|
ob_start(function ($buffer) {
|
||||||
|
return $buffer;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -47,32 +48,42 @@ Events::on('pre_system', static function (): void {
|
||||||
* Debug Toolbar Listeners.
|
* Debug Toolbar Listeners.
|
||||||
* --------------------------------------------------------------------
|
* --------------------------------------------------------------------
|
||||||
* If you delete, they will no longer be collected.
|
* If you delete, they will no longer be collected.
|
||||||
|
*
|
||||||
|
* @phpstan-ignore-next-line
|
||||||
*/
|
*/
|
||||||
if (CI_DEBUG && ! is_cli()) {
|
if (CI_DEBUG && ! is_cli()) {
|
||||||
Events::on('DBQuery', Database::class . '::collect');
|
Events::on('DBQuery', 'CodeIgniter\Debug\Toolbar\Collectors\Database::collect');
|
||||||
service('toolbar')
|
Services::toolbar()->respond();
|
||||||
->respond();
|
|
||||||
|
|
||||||
// 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('login', 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Events::on('logout', function (User $user): void {
|
||||||
|
helper('auth');
|
||||||
|
|
||||||
|
// remove user's interact_as_actor session
|
||||||
|
remove_interact_as_actor();
|
||||||
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* --------------------------------------------------------------------
|
* --------------------------------------------------------------------
|
||||||
* Fediverse events
|
* ActivityPub events
|
||||||
* --------------------------------------------------------------------
|
* --------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* @param Actor $actor
|
* @param Actor $actor
|
||||||
* @param Actor $targetActor
|
* @param Actor $targetActor
|
||||||
*/
|
*/
|
||||||
Events::on('on_follow', static function ($actor, $targetActor): void {
|
Events::on('on_follow', function ($actor, $targetActor): void {
|
||||||
if ($actor->is_podcast) {
|
if ($actor->is_podcast) {
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("podcast#{$actor->podcast->id}*");
|
->deleteMatching("podcast#{$actor->podcast->id}*");
|
||||||
|
|
@ -92,7 +103,7 @@ Events::on('on_follow', static function ($actor, $targetActor): void {
|
||||||
* @param Actor $actor
|
* @param Actor $actor
|
||||||
* @param Actor $targetActor
|
* @param Actor $targetActor
|
||||||
*/
|
*/
|
||||||
Events::on('on_undo_follow', static function ($actor, $targetActor): void {
|
Events::on('on_undo_follow', function ($actor, $targetActor): void {
|
||||||
if ($actor->is_podcast) {
|
if ($actor->is_podcast) {
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("podcast#{$actor->podcast->id}*");
|
->deleteMatching("podcast#{$actor->podcast->id}*");
|
||||||
|
|
@ -109,53 +120,82 @@ Events::on('on_undo_follow', static function ($actor, $targetActor): void {
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Post $post
|
* @param Status $status
|
||||||
*/
|
*/
|
||||||
Events::on('on_post_add', static function ($post): void {
|
Events::on('on_status_add', function ($status): void {
|
||||||
model(EpisodeModel::class, false)->builder()
|
if ($status->in_reply_to_id !== null) {
|
||||||
->where('id', $post->episode_id)
|
$status = $status->reply_to_status;
|
||||||
->increment('posts_count');
|
}
|
||||||
if ($post->actor->is_podcast) {
|
|
||||||
|
if ($status->episode_id) {
|
||||||
|
model('EpisodeModel')
|
||||||
|
->where('id', $status->episode_id)
|
||||||
|
->increment('statuses_total');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($status->actor->is_podcast) {
|
||||||
// Removing all of the podcast pages is a bit overkill, but works to avoid caching bugs
|
// Removing all of the podcast pages is a bit overkill, but works to avoid caching bugs
|
||||||
// same for other events below
|
// same for other events below
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("podcast#{$post->actor->podcast->id}*");
|
->deleteMatching("podcast#{$status->actor->podcast->id}*");
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("page_podcast#{$post->actor->podcast->id}*");
|
->deleteMatching("page_podcast#{$status->actor->podcast->id}*");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Post $post
|
* @param Status $status
|
||||||
*/
|
*/
|
||||||
Events::on('on_post_remove', static function ($post): void {
|
Events::on('on_status_remove', function ($status): void {
|
||||||
if ($episodeId = $post->episode_id) {
|
if ($status->in_reply_to_id !== null) {
|
||||||
model(EpisodeModel::class, false)->builder()
|
Events::trigger('on_status_remove', $status->reply_to_status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($episodeId = $status->episode_id) {
|
||||||
|
model('EpisodeModel')
|
||||||
->where('id', $episodeId)
|
->where('id', $episodeId)
|
||||||
->decrement('posts_count');
|
->decrement('statuses_total', 1 + $status->reblogs_count);
|
||||||
|
|
||||||
|
model('EpisodeModel')
|
||||||
|
->where('id', $episodeId)
|
||||||
|
->decrement('reblogs_total', $status->reblogs_count);
|
||||||
|
|
||||||
|
model('EpisodeModel')
|
||||||
|
->where('id', $episodeId)
|
||||||
|
->decrement('favourites_total', $status->favourites_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($post->actor->is_podcast) {
|
if ($status->actor->is_podcast) {
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("podcast#{$post->actor->podcast->id}*");
|
->deleteMatching("podcast#{$status->actor->podcast->id}*");
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("page_podcast#{$post->actor->podcast->id}*");
|
->deleteMatching("page_podcast#{$status->actor->podcast->id}*");
|
||||||
}
|
}
|
||||||
|
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("page_post#{$post->id}*");
|
->deleteMatching("page_status#{$status->id}*");
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Actor $actor
|
* @param Actor $actor
|
||||||
* @param Post $post
|
* @param Status $status
|
||||||
*/
|
*/
|
||||||
Events::on('on_post_reblog', static function ($actor, $post): void {
|
Events::on('on_status_reblog', function ($actor, $status): void {
|
||||||
if ($post->actor->is_podcast) {
|
if ($episodeId = $status->episode_id) {
|
||||||
|
model('EpisodeModel')
|
||||||
|
->where('id', $episodeId)
|
||||||
|
->increment('reblogs_total');
|
||||||
|
|
||||||
|
model('EpisodeModel')
|
||||||
|
->where('id', $episodeId)
|
||||||
|
->increment('statuses_total');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($status->actor->is_podcast) {
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("podcast#{$post->actor->podcast->id}*");
|
->deleteMatching("podcast#{$status->actor->podcast->id}*");
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("page_podcast#{$post->actor->podcast->id}*");
|
->deleteMatching("page_podcast#{$status->actor->podcast->id}*");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($actor->is_podcast) {
|
if ($actor->is_podcast) {
|
||||||
|
|
@ -165,106 +205,111 @@ Events::on('on_post_reblog', static function ($actor, $post): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("page_post#{$post->id}*");
|
->deleteMatching("page_status#{$status->id}*");
|
||||||
if ($post->in_reply_to_id !== null) {
|
|
||||||
cache()->deleteMatching("page_post#{$post->in_reply_to_id}");
|
if ($status->in_reply_to_id !== null) {
|
||||||
|
cache()->deleteMatching("page_status#{$status->in_reply_to_id}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Post $reblogPost
|
* @param Status $reblogStatus
|
||||||
*/
|
*/
|
||||||
Events::on('on_post_undo_reblog', static function ($reblogPost): void {
|
Events::on('on_status_undo_reblog', function ($reblogStatus): void {
|
||||||
$post = $reblogPost->reblog_of_post;
|
$status = $reblogStatus->reblog_of_status;
|
||||||
if ($post->actor->is_podcast) {
|
if ($episodeId = $status->episode_id) {
|
||||||
|
model('EpisodeModel')
|
||||||
|
->where('id', $episodeId)
|
||||||
|
->decrement('reblogs_total');
|
||||||
|
|
||||||
|
model('EpisodeModel')
|
||||||
|
->where('id', $episodeId)
|
||||||
|
->decrement('statuses_total');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($status->actor->is_podcast) {
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("podcast#{$post->actor->podcast->id}*");
|
->deleteMatching("podcast#{$status->actor->podcast->id}*");
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("page_podcast#{$post->actor->podcast->id}*");
|
->deleteMatching("page_podcast#{$status->actor->podcast->id}*");
|
||||||
}
|
}
|
||||||
|
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("page_post#{$post->id}*");
|
->deleteMatching("page_status#{$status->id}*");
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("page_post#{$reblogPost->id}*");
|
->deleteMatching("page_status#{$reblogStatus->id}*");
|
||||||
if ($post->in_reply_to_id !== null) {
|
|
||||||
cache()->deleteMatching("page_post#{$post->in_reply_to_id}");
|
if ($status->in_reply_to_id !== null) {
|
||||||
|
cache()->deleteMatching("page_status#{$status->in_reply_to_id}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($reblogPost->actor->is_podcast) {
|
if ($reblogStatus->actor->is_podcast) {
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("podcast#{$reblogPost->actor->podcast->id}*");
|
->deleteMatching("podcast#{$reblogStatus->actor->podcast->id}*");
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("page_podcast#{$reblogPost->actor->podcast->id}*");
|
->deleteMatching("page_podcast#{$reblogStatus->actor->podcast->id}*");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Post $reply
|
* @param Status $reply
|
||||||
*/
|
*/
|
||||||
Events::on('on_post_reply', static function ($reply): void {
|
Events::on('on_status_reply', function ($reply): void {
|
||||||
$post = $reply->reply_to_post;
|
$status = $reply->reply_to_status;
|
||||||
if ($post->in_reply_to_id === null) {
|
|
||||||
model(EpisodeModel::class, false)->builder()
|
|
||||||
->where('id', $post->episode_id)
|
|
||||||
->increment('comments_count');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post->actor->is_podcast) {
|
if ($status->actor->is_podcast) {
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("podcast-{$post->actor->podcast->handle}*");
|
->deleteMatching("podcast#{$status->actor->podcast->id}*");
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("podcast#{$post->actor->podcast->id}*");
|
->deleteMatching("page_podcast#{$status->actor->podcast->id}*");
|
||||||
cache()
|
|
||||||
->deleteMatching("page_podcast#{$post->actor->podcast->id}*");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("page_post#{$post->id}*");
|
->deleteMatching("page_status#{$status->id}*");
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Post $reply
|
* @param Status $reply
|
||||||
*/
|
*/
|
||||||
Events::on('on_reply_remove', static function ($reply): void {
|
Events::on('on_reply_remove', function ($reply): void {
|
||||||
$post = $reply->reply_to_post;
|
$status = $reply->reply_to_status;
|
||||||
if ($post->in_reply_to_id === null) {
|
|
||||||
model(EpisodeModel::class, false)->builder()
|
|
||||||
->where('id', $post->episode_id)
|
|
||||||
->decrement('comments_count');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post->actor->is_podcast) {
|
if ($status->actor->is_podcast) {
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("podcast-{$post->actor->podcast->handle}*");
|
->deleteMatching("page_podcast#{$status->actor->podcast->id}*");
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("page_podcast#{$post->actor->podcast->id}*");
|
->deleteMatching("podcast#{$status->actor->podcast->id}*");
|
||||||
cache()
|
|
||||||
->deleteMatching("podcast#{$post->actor->podcast->id}*");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("page_post#{$post->id}*");
|
->deleteMatching("page_status#{$status->id}*");
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("page_post#{$reply->id}*");
|
->deleteMatching("page_status#{$reply->id}*");
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Actor $actor
|
* @param Actor $actor
|
||||||
* @param Post $post
|
* @param Status $status
|
||||||
*/
|
*/
|
||||||
Events::on('on_post_favourite', static function ($actor, $post): void {
|
Events::on('on_status_favourite', function ($actor, $status): void {
|
||||||
if ($post->actor->is_podcast) {
|
if ($status->episode_id) {
|
||||||
|
model('EpisodeModel')
|
||||||
|
->where('id', $status->episode_id)
|
||||||
|
->increment('favourites_total');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($status->actor->is_podcast) {
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("podcast#{$post->actor->podcast->id}*");
|
->deleteMatching("podcast#{$status->actor->podcast->id}*");
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("page_podcast#{$post->actor->podcast->id}*");
|
->deleteMatching("page_podcast#{$status->actor->podcast->id}*");
|
||||||
}
|
}
|
||||||
|
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("page_post#{$post->id}*");
|
->deleteMatching("page_status#{$status->id}*");
|
||||||
if ($post->in_reply_to_id !== null) {
|
|
||||||
cache()->deleteMatching("page_post#{$post->in_reply_to_id}*");
|
if ($status->in_reply_to_id !== null) {
|
||||||
|
cache()->deleteMatching("page_status#{$status->in_reply_to_id}*");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($actor->is_podcast) {
|
if ($actor->is_podcast) {
|
||||||
|
|
@ -276,20 +321,27 @@ Events::on('on_post_favourite', static function ($actor, $post): void {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Actor $actor
|
* @param Actor $actor
|
||||||
* @param Post $post
|
* @param Status $status
|
||||||
*/
|
*/
|
||||||
Events::on('on_post_undo_favourite', static function ($actor, $post): void {
|
Events::on('on_status_undo_favourite', function ($actor, $status): void {
|
||||||
if ($post->actor->is_podcast) {
|
if ($status->episode_id) {
|
||||||
|
model('EpisodeModel')
|
||||||
|
->where('id', $status->episode_id)
|
||||||
|
->decrement('favourites_total');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($status->actor->is_podcast) {
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("podcast#{$post->actor->podcast->id}*");
|
->deleteMatching("podcast#{$status->actor->podcast->id}*");
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("page_podcast#{$post->actor->podcast->id}*");
|
->deleteMatching("page_podcast#{$status->actor->podcast->id}*");
|
||||||
}
|
}
|
||||||
|
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching("page_post#{$post->id}*");
|
->deleteMatching("page_status#{$status->id}*");
|
||||||
if ($post->in_reply_to_id !== null) {
|
|
||||||
cache()->deleteMatching("page_post#{$post->in_reply_to_id}*");
|
if ($status->in_reply_to_id !== null) {
|
||||||
|
cache()->deleteMatching("page_status#{$status->in_reply_to_id}*");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($actor->is_podcast) {
|
if ($actor->is_podcast) {
|
||||||
|
|
@ -299,34 +351,34 @@ Events::on('on_post_undo_favourite', static function ($actor, $post): void {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Events::on('on_block_actor', static function (int $actorId): void {
|
Events::on('on_block_actor', function (int $actorId): void {
|
||||||
cache()->deleteMatching('page_podcast*');
|
cache()->deleteMatching('page_podcast*');
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching('podcast*');
|
->deleteMatching('podcast*');
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching('page_post*');
|
->deleteMatching('page_status*');
|
||||||
});
|
});
|
||||||
|
|
||||||
Events::on('on_unblock_actor', static function (int $actorId): void {
|
Events::on('on_unblock_actor', function (int $actorId): void {
|
||||||
cache()->deleteMatching('page_podcast*');
|
cache()->deleteMatching('page_podcast*');
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching('podcast*');
|
->deleteMatching('podcast*');
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching('page_post*');
|
->deleteMatching('page_status*');
|
||||||
});
|
});
|
||||||
|
|
||||||
Events::on('on_block_domain', static function (string $domainName): void {
|
Events::on('on_block_domain', function (string $domainName): void {
|
||||||
cache()->deleteMatching('page_podcast*');
|
cache()->deleteMatching('page_podcast*');
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching('podcast*');
|
->deleteMatching('podcast*');
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching('page_post*');
|
->deleteMatching('page_status*');
|
||||||
});
|
});
|
||||||
|
|
||||||
Events::on('on_unblock_domain', static function (string $domainName): void {
|
Events::on('on_unblock_domain', function (string $domainName): void {
|
||||||
cache()->deleteMatching('page_podcast*');
|
cache()->deleteMatching('page_podcast*');
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching('podcast*');
|
->deleteMatching('podcast*');
|
||||||
cache()
|
cache()
|
||||||
->deleteMatching('page_post*');
|
->deleteMatching('page_status*');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,6 @@ declare(strict_types=1);
|
||||||
namespace Config;
|
namespace Config;
|
||||||
|
|
||||||
use CodeIgniter\Config\BaseConfig;
|
use CodeIgniter\Config\BaseConfig;
|
||||||
use CodeIgniter\Debug\ExceptionHandler;
|
|
||||||
use CodeIgniter\Debug\ExceptionHandlerInterface;
|
|
||||||
use Psr\Log\LogLevel;
|
|
||||||
use Throwable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup how the exception handler works.
|
* Setup how the exception handler works.
|
||||||
|
|
@ -33,7 +29,7 @@ class Exceptions extends BaseConfig
|
||||||
* Any status codes here will NOT be logged if logging is turned on.
|
* Any status codes here will NOT be logged if logging is turned on.
|
||||||
* By default, only 404 (Page Not Found) exceptions are ignored.
|
* By default, only 404 (Page Not Found) exceptions are ignored.
|
||||||
*
|
*
|
||||||
* @var list<int>
|
* @var int[]
|
||||||
*/
|
*/
|
||||||
public array $ignoreCodes = [404];
|
public array $ignoreCodes = [404];
|
||||||
|
|
||||||
|
|
@ -56,53 +52,7 @@ class Exceptions extends BaseConfig
|
||||||
* In order to specify 2 levels, use "/" to separate.
|
* In order to specify 2 levels, use "/" to separate.
|
||||||
* ex. ['server', 'setup/password', 'secret_token']
|
* ex. ['server', 'setup/password', 'secret_token']
|
||||||
*
|
*
|
||||||
* @var list<string>
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
public array $sensitiveDataInTrace = [];
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Config;
|
|
||||||
|
|
||||||
use CodeIgniter\Config\BaseConfig;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enable/disable backward compatibility breaking features.
|
|
||||||
*/
|
|
||||||
class Feature extends BaseConfig
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Use improved new auto routing instead of the legacy version.
|
|
||||||
*/
|
|
||||||
public bool $autoRoutesImproved = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use filter execution order in 4.4 or before.
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @copyright 2022 Ad Aures
|
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
|
||||||
* @link https://castopod.org/
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Config;
|
|
||||||
|
|
||||||
use App\Libraries\NoteObject;
|
|
||||||
use Exception;
|
|
||||||
use Modules\Fediverse\Config\Fediverse as FediverseBaseConfig;
|
|
||||||
|
|
||||||
class Fediverse extends FediverseBaseConfig
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* --------------------------------------------------------------------
|
|
||||||
* ActivityPub Objects
|
|
||||||
* --------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
public string $noteObject = NoteObject::class;
|
|
||||||
|
|
||||||
public string $defaultAvatarImagePath = 'castopod-avatar_thumbnail.webp';
|
|
||||||
|
|
||||||
public string $defaultAvatarImageMimetype = 'image/webp';
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
|
|
||||||
try {
|
|
||||||
$appTheme = service('settings')
|
|
||||||
->get('App.theme');
|
|
||||||
$defaultBanner = config('Images')
|
|
||||||
->podcastBannerDefaultPaths[$appTheme] ?? config('Images')->podcastBannerDefaultPaths['default'];
|
|
||||||
} catch (Exception) {
|
|
||||||
$defaultBanner = config('Images')
|
|
||||||
->podcastBannerDefaultPaths['default'];
|
|
||||||
}
|
|
||||||
|
|
||||||
['dirname' => $dirname, 'extension' => $extension, 'filename' => $filename] = pathinfo(
|
|
||||||
$defaultBanner['path'],
|
|
||||||
);
|
|
||||||
$defaultBannerPath = $filename;
|
|
||||||
if ($dirname !== '.') {
|
|
||||||
$defaultBannerPathList = [$dirname, $filename];
|
|
||||||
$defaultBannerPath = implode('/', $defaultBannerPathList);
|
|
||||||
}
|
|
||||||
|
|
||||||
helper('media');
|
|
||||||
|
|
||||||
$this->defaultCoverImagePath = $defaultBannerPath . '_federation.' . $extension;
|
|
||||||
$this->defaultCoverImageMimetype = $defaultBanner['mimetype'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,101 +4,54 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Config;
|
namespace Config;
|
||||||
|
|
||||||
use App\Filters\AllowCorsFilter;
|
use ActivityPub\Filters\ActivityPubFilter;
|
||||||
|
use App\Filters\PermissionFilter;
|
||||||
use CodeIgniter\Config\BaseConfig;
|
use CodeIgniter\Config\BaseConfig;
|
||||||
use CodeIgniter\Filters\CSRF;
|
use CodeIgniter\Filters\CSRF;
|
||||||
use CodeIgniter\Filters\DebugToolbar;
|
use CodeIgniter\Filters\DebugToolbar;
|
||||||
use CodeIgniter\Filters\ForceHTTPS;
|
|
||||||
use CodeIgniter\Filters\Honeypot;
|
use CodeIgniter\Filters\Honeypot;
|
||||||
use CodeIgniter\Filters\InvalidChars;
|
use Myth\Auth\Filters\LoginFilter;
|
||||||
use CodeIgniter\Filters\PageCache;
|
use Myth\Auth\Filters\RoleFilter;
|
||||||
use CodeIgniter\Filters\PerformanceMetrics;
|
|
||||||
use CodeIgniter\Filters\SecureHeaders;
|
|
||||||
use Modules\Auth\Filters\PermissionFilter;
|
|
||||||
|
|
||||||
class Filters extends BaseConfig
|
class Filters extends BaseConfig
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Configures aliases for Filter classes to make reading things nicer and simpler.
|
* Configures aliases for Filter classes to make reading things nicer and simpler.
|
||||||
*
|
*
|
||||||
* @var array<string, class-string|list<class-string>>
|
* @var array<string, string>
|
||||||
*
|
|
||||||
* [filter_name => classname]
|
|
||||||
* or [filter_name => [classname1, classname2, ...]]
|
|
||||||
*/
|
*/
|
||||||
public array $aliases = [
|
public array $aliases = [
|
||||||
'csrf' => CSRF::class,
|
'csrf' => CSRF::class,
|
||||||
'toolbar' => DebugToolbar::class,
|
'toolbar' => DebugToolbar::class,
|
||||||
'honeypot' => Honeypot::class,
|
'honeypot' => Honeypot::class,
|
||||||
'invalidchars' => InvalidChars::class,
|
'login' => LoginFilter::class,
|
||||||
'secureheaders' => SecureHeaders::class,
|
'role' => RoleFilter::class,
|
||||||
'allow-cors' => AllowCorsFilter::class,
|
'permission' => PermissionFilter::class,
|
||||||
'cors' => Cors::class,
|
'activity-pub' => ActivityPubFilter::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.
|
* List of filter aliases that are always applied before and after every request.
|
||||||
*
|
*
|
||||||
* @var array{
|
* @var array<string, string[]>
|
||||||
* before: array<string, array{except: list<string>|string}>|list<string>,
|
|
||||||
* after: array<string, array{except: list<string>|string}>|list<string>
|
|
||||||
* }
|
|
||||||
*/
|
*/
|
||||||
public array $globals = [
|
public array $globals = [
|
||||||
'before' => [
|
'before' => [
|
||||||
// 'honeypot',
|
// 'honeypot',
|
||||||
'csrf' => [
|
// 'csrf',
|
||||||
'except' => [
|
|
||||||
'@[a-zA-Z0-9\_]{1,32}/inbox',
|
|
||||||
'api/rest/v1/episodes',
|
|
||||||
'api/rest/v1/episodes/[0-9]+/publish',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
// 'invalidchars',
|
|
||||||
],
|
],
|
||||||
'after' => [
|
'after' => [
|
||||||
|
'toolbar',
|
||||||
// 'honeypot',
|
// 'honeypot',
|
||||||
// 'secureheaders',
|
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of filter aliases that works on a particular HTTP method (GET, POST, etc.).
|
* List of filter aliases that works on a particular HTTP method (GET, POST, etc.).
|
||||||
*
|
*
|
||||||
* Example: 'POST' => ['foo', 'bar']
|
* Example: 'post' => ['csrf', 'throttle']
|
||||||
*
|
*
|
||||||
* If you use this, you should disable auto-routing because auto-routing permits any HTTP method to access a
|
* @var array<string, string[]>
|
||||||
* controller. Accessing the controller with a method you don’t expect could bypass the filter.
|
|
||||||
*
|
|
||||||
* @var array<string, list<string>>
|
|
||||||
*/
|
*/
|
||||||
public array $methods = [];
|
public array $methods = [];
|
||||||
|
|
||||||
|
|
@ -107,7 +60,7 @@ class Filters extends BaseConfig
|
||||||
*
|
*
|
||||||
* Example: 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']]
|
* Example: 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']]
|
||||||
*
|
*
|
||||||
* @var array<string, array<string, list<string>>>
|
* @var array<string, array<string, string[]>>
|
||||||
*/
|
*/
|
||||||
public array $filters = [];
|
public array $filters = [];
|
||||||
|
|
||||||
|
|
@ -116,14 +69,9 @@ class Filters extends BaseConfig
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|
||||||
$this->filters = [
|
$this->filters = [
|
||||||
'session' => [
|
'login' => [
|
||||||
'before' => [config('Admin')->gateway . '*', config('Analytics')->gateway . '*'],
|
'before' => [config('App')->adminGateway . '*'],
|
||||||
],
|
|
||||||
'podcast-unlock' => [
|
|
||||||
'before' => ['*@*/episodes/*'],
|
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->aliases['permission'] = PermissionFilter::class;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,6 @@ namespace Config;
|
||||||
|
|
||||||
use CodeIgniter\Config\ForeignCharacters as BaseForeignCharacters;
|
use CodeIgniter\Config\ForeignCharacters as BaseForeignCharacters;
|
||||||
|
|
||||||
/**
|
|
||||||
* @immutable
|
|
||||||
*/
|
|
||||||
class ForeignCharacters extends BaseForeignCharacters
|
class ForeignCharacters extends BaseForeignCharacters
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||||
namespace Config;
|
namespace Config;
|
||||||
|
|
||||||
use CodeIgniter\Config\BaseConfig;
|
use CodeIgniter\Config\BaseConfig;
|
||||||
|
use CodeIgniter\Format\FormatterInterface;
|
||||||
use CodeIgniter\Format\JSONFormatter;
|
use CodeIgniter\Format\JSONFormatter;
|
||||||
use CodeIgniter\Format\XMLFormatter;
|
use CodeIgniter\Format\XMLFormatter;
|
||||||
|
|
||||||
|
|
@ -23,7 +24,7 @@ class Format extends BaseConfig
|
||||||
* These formats are only checked when the data passed to the respond()
|
* These formats are only checked when the data passed to the respond()
|
||||||
* method is an array.
|
* method is an array.
|
||||||
*
|
*
|
||||||
* @var list<string>
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
public array $supportedResponseFormats = [
|
public array $supportedResponseFormats = [
|
||||||
'application/json',
|
'application/json',
|
||||||
|
|
@ -44,8 +45,8 @@ class Format extends BaseConfig
|
||||||
*/
|
*/
|
||||||
public array $formatters = [
|
public array $formatters = [
|
||||||
'application/json' => JSONFormatter::class,
|
'application/json' => JSONFormatter::class,
|
||||||
'application/xml' => XMLFormatter::class,
|
'application/xml' => XMLFormatter::class,
|
||||||
'text/xml' => XMLFormatter::class,
|
'text/xml' => XMLFormatter::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -60,16 +61,19 @@ class Format extends BaseConfig
|
||||||
*/
|
*/
|
||||||
public array $formatterOptions = [
|
public array $formatterOptions = [
|
||||||
'application/json' => JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,
|
'application/json' => JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,
|
||||||
'application/xml' => 0,
|
'application/xml' => 0,
|
||||||
'text/xml' => 0,
|
'text/xml' => 0,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------------
|
* A Factory method to return the appropriate formatter for the given mime type.
|
||||||
* Maximum depth for JSON encoding.
|
|
||||||
* --------------------------------------------------------------------------
|
|
||||||
*
|
*
|
||||||
* This value determines how deep the JSON encoder will traverse nested structures.
|
* @deprecated This is an alias of `\CodeIgniter\Format\Format::getFormatter`. Use that instead.
|
||||||
*/
|
*/
|
||||||
public int $jsonEncodeDepth = 512;
|
public function getFormatter(string $mime): FormatterInterface
|
||||||
|
{
|
||||||
|
return Services::format()->getFormatter($mime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,22 +25,22 @@ class Generators extends BaseConfig
|
||||||
*
|
*
|
||||||
* YOU HAVE BEEN WARNED!
|
* YOU HAVE BEEN WARNED!
|
||||||
*
|
*
|
||||||
* @var array<string, string|array<string,string>>
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
public array $views = [
|
public array $views = [
|
||||||
'make:cell' => [
|
'make:command' =>
|
||||||
'class' => 'CodeIgniter\Commands\Generators\Views\cell.tpl.php',
|
'CodeIgniter\Commands\Generators\Views\command.tpl.php',
|
||||||
'view' => 'CodeIgniter\Commands\Generators\Views\cell_view.tpl.php',
|
'make:controller' =>
|
||||||
],
|
'CodeIgniter\Commands\Generators\Views\controller.tpl.php',
|
||||||
'make:command' => 'CodeIgniter\Commands\Generators\Views\command.tpl.php',
|
'make:entity' => 'CodeIgniter\Commands\Generators\Views\entity.tpl.php',
|
||||||
'make:config' => 'CodeIgniter\Commands\Generators\Views\config.tpl.php',
|
'make:filter' => 'CodeIgniter\Commands\Generators\Views\filter.tpl.php',
|
||||||
'make:controller' => 'CodeIgniter\Commands\Generators\Views\controller.tpl.php',
|
'make:migration' =>
|
||||||
'make:entity' => 'CodeIgniter\Commands\Generators\Views\entity.tpl.php',
|
'CodeIgniter\Commands\Generators\Views\migration.tpl.php',
|
||||||
'make:filter' => 'CodeIgniter\Commands\Generators\Views\filter.tpl.php',
|
'make:model' => 'CodeIgniter\Commands\Generators\Views\model.tpl.php',
|
||||||
'make:migration' => 'CodeIgniter\Commands\Generators\Views\migration.tpl.php',
|
'make:seeder' => 'CodeIgniter\Commands\Generators\Views\seeder.tpl.php',
|
||||||
'make:model' => 'CodeIgniter\Commands\Generators\Views\model.tpl.php',
|
'make:validation' =>
|
||||||
'make:seeder' => 'CodeIgniter\Commands\Generators\Views\seeder.tpl.php',
|
'CodeIgniter\Commands\Generators\Views\validation.tpl.php',
|
||||||
'make:validation' => 'CodeIgniter\Commands\Generators\Views\validation.tpl.php',
|
'session:migration' =>
|
||||||
'session:migration' => 'CodeIgniter\Commands\Generators\Views\migration.tpl.php',
|
'CodeIgniter\Commands\Generators\Views\migration.tpl.php',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,15 +30,6 @@ class Honeypot extends BaseConfig
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Honeypot container
|
* Honeypot container
|
||||||
*
|
|
||||||
* If you enabled CSP, you can remove `style="display:none"`.
|
|
||||||
*/
|
*/
|
||||||
public string $container = '<div style="display:none">{template}</div>';
|
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';
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
<?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,8 +17,6 @@ class Images extends BaseConfig
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The path to the image library. Required for ImageMagick, GraphicsMagick, or NetPBM.
|
* 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';
|
public string $libraryPath = '/usr/local/bin/convert';
|
||||||
|
|
||||||
|
|
@ -28,181 +26,48 @@ class Images extends BaseConfig
|
||||||
* @var array<string, string>
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
public array $handlers = [
|
public array $handlers = [
|
||||||
'gd' => GDHandler::class,
|
'gd' => GDHandler::class,
|
||||||
'imagick' => ImageMagickHandler::class,
|
'imagick' => ImageMagickHandler::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Uploaded images sizes (in px)
|
| Uploaded images resizing sizes (in px)
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| The sizes listed below determine the resizing of images when uploaded.
|
| The sizes listed below determine the resizing of images when uploaded.
|
||||||
|
| All uploaded images are of 1:1 ratio (width and height are the same).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
public int $thumbnailSize = 150;
|
||||||
* Podcast cover image sizes
|
|
||||||
*
|
public int $mediumSize = 320;
|
||||||
* Uploaded podcast covers are of 1:1 ratio (width and height are the same).
|
|
||||||
*
|
public int $largeSize = 1024;
|
||||||
* Size of images linked in the rss feed (should be between 1400 and 3000). Size for ID3 tag cover art (should be
|
|
||||||
* between 300 and 800)
|
|
||||||
*
|
|
||||||
* Array values are as follows: 'name' => [width, height]
|
|
||||||
*
|
|
||||||
* @var array<string, array<string, int|string>>
|
|
||||||
*/
|
|
||||||
public array $podcastCoverSizes = [
|
|
||||||
'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',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Podcast header cover image
|
* Size of images linked in the rss feed (should be between 1400 and 3000)
|
||||||
*
|
|
||||||
* Uploaded podcast header covers are of 3:1 ratio
|
|
||||||
*
|
|
||||||
* @var array<string, array<string, int|string>>
|
|
||||||
*/
|
*/
|
||||||
public array $podcastBannerSizes = [
|
public int $feedSize = 1400;
|
||||||
'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,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
public string $avatarDefaultPath = 'assets/images/castopod-avatar.jpg';
|
|
||||||
|
|
||||||
public string $avatarDefaultMimeType = 'image/jpg';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array<string, array<string, string>>
|
* Size for ID3 tag cover art (should be between 300 and 800)
|
||||||
*/
|
*/
|
||||||
public array $podcastBannerDefaultPaths = [
|
public int $id3Size = 500;
|
||||||
'default' => [
|
|
||||||
'path' => 'assets/images/castopod-banner-pine.jpg',
|
|
||||||
'mimetype' => 'image/jpeg',
|
|
||||||
],
|
|
||||||
'pine' => [
|
|
||||||
'path' => 'assets/images/castopod-banner-pine.jpg',
|
|
||||||
'mimetype' => 'image/jpeg',
|
|
||||||
],
|
|
||||||
'crimson' => [
|
|
||||||
'path' => 'assets/images/castopod-banner-crimson.jpg',
|
|
||||||
'mimetype' => 'image/jpeg',
|
|
||||||
],
|
|
||||||
'amber' => [
|
|
||||||
'path' => 'assets/images/castopod-banner-amber.jpg',
|
|
||||||
'mimetype' => 'image/jpeg',
|
|
||||||
],
|
|
||||||
'lake' => [
|
|
||||||
'path' => 'assets/images/castopod-banner-lake.jpg',
|
|
||||||
'mimetype' => 'image/jpeg',
|
|
||||||
],
|
|
||||||
'jacaranda' => [
|
|
||||||
'path' => 'assets/images/castopod-banner-jacaranda.jpg',
|
|
||||||
'mimetype' => 'image/jpeg',
|
|
||||||
],
|
|
||||||
'onyx' => [
|
|
||||||
'path' => 'assets/images/castopod-banner-onyx.jpg',
|
|
||||||
'mimetype' => 'image/jpeg',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
public string $podcastBannerDefaultMimeType = 'image/jpeg';
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Uploaded images naming extensions
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| The properties listed below set the name extensions for the resized images
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
public string $thumbnailSuffix = '_thumbnail';
|
||||||
* Person image
|
|
||||||
*
|
public string $mediumSuffix = '_medium';
|
||||||
* Uploaded person images are of 1:1 ratio (width and height are the same).
|
|
||||||
*
|
public string $largeSuffix = '_large';
|
||||||
* Array values are as follows: 'name' => [width, height]
|
|
||||||
*
|
public string $feedSuffix = '_feed';
|
||||||
* @var array<string, array<string, int|string>>
|
|
||||||
*/
|
public string $id3Suffix = '_id3';
|
||||||
public array $personAvatarSizes = [
|
|
||||||
'federation' => [
|
|
||||||
'width' => 400,
|
|
||||||
'height' => 400,
|
|
||||||
],
|
|
||||||
'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',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Config;
|
namespace Config;
|
||||||
|
|
||||||
use Kint\Parser\ConstructablePluginInterface;
|
use CodeIgniter\Config\BaseConfig;
|
||||||
use Kint\Renderer\Rich\TabPluginInterface;
|
use Kint\Renderer\Renderer;
|
||||||
use Kint\Renderer\Rich\ValuePluginInterface;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
|
* Kint
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
*
|
*
|
||||||
* We use Kint's `RichRenderer` and `CLIRenderer`. This area contains options
|
* We use Kint's `RichRenderer` and `CLIRenderer`. This area contains options
|
||||||
|
|
@ -17,7 +17,7 @@ use Kint\Renderer\Rich\ValuePluginInterface;
|
||||||
*
|
*
|
||||||
* @see https://kint-php.github.io/kint/ for details on these settings.
|
* @see https://kint-php.github.io/kint/ for details on these settings.
|
||||||
*/
|
*/
|
||||||
class Kint
|
class Kint extends BaseConfig
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
@ -26,9 +26,9 @@ class Kint
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var list<class-string<ConstructablePluginInterface>|ConstructablePluginInterface>|null
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
public ?array $plugins = [];
|
public array $plugins = [];
|
||||||
|
|
||||||
public int $maxDepth = 6;
|
public int $maxDepth = 6;
|
||||||
|
|
||||||
|
|
@ -46,15 +46,17 @@ class Kint
|
||||||
|
|
||||||
public bool $richFolder = false;
|
public bool $richFolder = false;
|
||||||
|
|
||||||
/**
|
public int $richSort = Renderer::SORT_FULL;
|
||||||
* @var array<string, class-string<ValuePluginInterface>>|null
|
|
||||||
*/
|
|
||||||
public ?array $richObjectPlugins = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array<string, class-string<TabPluginInterface>>|null
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
public ?array $richTabPlugins = [];
|
public array $richObjectPlugins = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
public array $richTabPlugins = [];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ namespace Config;
|
||||||
|
|
||||||
use CodeIgniter\Config\BaseConfig;
|
use CodeIgniter\Config\BaseConfig;
|
||||||
use CodeIgniter\Log\Handlers\FileHandler;
|
use CodeIgniter\Log\Handlers\FileHandler;
|
||||||
use CodeIgniter\Log\Handlers\HandlerInterface;
|
|
||||||
|
|
||||||
class Logger extends BaseConfig
|
class Logger extends BaseConfig
|
||||||
{
|
{
|
||||||
|
|
@ -39,9 +38,9 @@ class Logger extends BaseConfig
|
||||||
* For a live site you'll usually enable Critical or higher (3) to be logged otherwise
|
* For a live site you'll usually enable Critical or higher (3) to be logged otherwise
|
||||||
* your log files will fill up very fast.
|
* your log files will fill up very fast.
|
||||||
*
|
*
|
||||||
* @var int|list<int>
|
* @var int|int[]
|
||||||
*/
|
*/
|
||||||
public int | array $threshold = (ENVIRONMENT === 'production') ? 4 : 9;
|
public int | array $threshold = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
|
|
@ -61,7 +60,7 @@ class Logger extends BaseConfig
|
||||||
* The logging system supports multiple actions to be taken when something
|
* The logging system supports multiple actions to be taken when something
|
||||||
* is logged. This is done by allowing for multiple Handlers, special classes
|
* is logged. This is done by allowing for multiple Handlers, special classes
|
||||||
* designed to write the log to their chosen destinations, whether that is
|
* designed to write the log to their chosen destinations, whether that is
|
||||||
* a file on the server, a cloud-based service, or even taking actions such
|
* a file on the getServer, a cloud-based service, or even taking actions such
|
||||||
* as emailing the dev team.
|
* as emailing the dev team.
|
||||||
*
|
*
|
||||||
* Each handler is defined by the class name used for that handler, and it
|
* Each handler is defined by the class name used for that handler, and it
|
||||||
|
|
@ -76,7 +75,7 @@ class Logger extends BaseConfig
|
||||||
* Handlers are executed in the order defined in this array, starting with
|
* Handlers are executed in the order defined in this array, starting with
|
||||||
* the handler on top and continuing down.
|
* the handler on top and continuing down.
|
||||||
*
|
*
|
||||||
* @var array<class-string<HandlerInterface>, array<string, int|list<string>|string>>
|
* @var array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public array $handlers = [
|
public array $handlers = [
|
||||||
/*
|
/*
|
||||||
|
|
@ -115,32 +114,5 @@ class Logger extends BaseConfig
|
||||||
*/
|
*/
|
||||||
'path' => '',
|
'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,7 +27,9 @@ class Migrations extends BaseConfig
|
||||||
*
|
*
|
||||||
* This is the name of the table that will store the current migrations state.
|
* 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
|
* When migrations runs it will store in a database table which migration
|
||||||
* files have already been run.
|
* 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.
|
||||||
*/
|
*/
|
||||||
public string $table = 'migrations';
|
public string $table = 'migrations';
|
||||||
|
|
||||||
|
|
@ -46,19 +48,4 @@ class Migrations extends BaseConfig
|
||||||
* - Y_m_d_His_
|
* - Y_m_d_His_
|
||||||
*/
|
*/
|
||||||
public string $timestampFormat = '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,6 +5,8 @@ declare(strict_types=1);
|
||||||
namespace Config;
|
namespace Config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Mimes
|
||||||
|
*
|
||||||
* This file contains an array of mime types. It is used by the Upload class to help identify allowed file types.
|
* 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
|
* When more than one variation for an extension exist (like jpg, jpeg, etc) the most common one should be first in the
|
||||||
|
|
@ -13,12 +15,13 @@ namespace Config;
|
||||||
* When working with mime types, please make sure you have the ´fileinfo´ extension enabled to reliably detect the
|
* When working with mime types, please make sure you have the ´fileinfo´ extension enabled to reliably detect the
|
||||||
* media types.
|
* media types.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class Mimes
|
class Mimes
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Map of extensions to mime types.
|
* Map of extensions to mime types.
|
||||||
*
|
*
|
||||||
* @var array<string, list<string>|string>
|
* @var array<string, string|string[]>
|
||||||
*/
|
*/
|
||||||
public static $mimes = [
|
public static $mimes = [
|
||||||
'hqx' => [
|
'hqx' => [
|
||||||
|
|
@ -50,24 +53,21 @@ class Mimes
|
||||||
'dms' => 'application/octet-stream',
|
'dms' => 'application/octet-stream',
|
||||||
'lha' => 'application/octet-stream',
|
'lha' => 'application/octet-stream',
|
||||||
'lzh' => 'application/octet-stream',
|
'lzh' => 'application/octet-stream',
|
||||||
'exe' => ['application/octet-stream',
|
'exe' => ['application/octet-stream', 'application/x-msdownload'],
|
||||||
'application/vnd.microsoft.portable-executable',
|
|
||||||
'application/x-dosexec',
|
|
||||||
'application/x-msdownload'],
|
|
||||||
'class' => 'application/octet-stream',
|
'class' => 'application/octet-stream',
|
||||||
'psd' => ['application/x-photoshop', 'image/vnd.adobe.photoshop'],
|
'psd' => ['application/x-photoshop', 'image/vnd.adobe.photoshop'],
|
||||||
'so' => 'application/octet-stream',
|
'so' => 'application/octet-stream',
|
||||||
'sea' => 'application/octet-stream',
|
'sea' => 'application/octet-stream',
|
||||||
'dll' => 'application/octet-stream',
|
'dll' => 'application/octet-stream',
|
||||||
'oda' => 'application/oda',
|
'oda' => 'application/oda',
|
||||||
'pdf' => ['application/pdf', 'application/force-download', 'application/x-download'],
|
'pdf' => ['application/pdf', 'application/force-download', 'application/x-download'],
|
||||||
'ai' => ['application/pdf', 'application/postscript'],
|
'ai' => ['application/pdf', 'application/postscript'],
|
||||||
'eps' => 'application/postscript',
|
'eps' => 'application/postscript',
|
||||||
'ps' => 'application/postscript',
|
'ps' => 'application/postscript',
|
||||||
'smi' => 'application/smil',
|
'smi' => 'application/smil',
|
||||||
'smil' => 'application/smil',
|
'smil' => 'application/smil',
|
||||||
'mif' => 'application/vnd.mif',
|
'mif' => 'application/vnd.mif',
|
||||||
'xls' => [
|
'xls' => [
|
||||||
'application/vnd.ms-excel',
|
'application/vnd.ms-excel',
|
||||||
'application/msexcel',
|
'application/msexcel',
|
||||||
'application/x-msexcel',
|
'application/x-msexcel',
|
||||||
|
|
@ -87,17 +87,21 @@ class Mimes
|
||||||
'application/vnd.ms-office',
|
'application/vnd.ms-office',
|
||||||
'application/msword',
|
'application/msword',
|
||||||
],
|
],
|
||||||
'pptx' => ['application/vnd.openxmlformats-officedocument.presentationml.presentation'],
|
'pptx' => [
|
||||||
|
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||||
|
'application/x-zip',
|
||||||
|
'application/zip',
|
||||||
|
],
|
||||||
'wbxml' => 'application/wbxml',
|
'wbxml' => 'application/wbxml',
|
||||||
'wmlc' => 'application/wmlc',
|
'wmlc' => 'application/wmlc',
|
||||||
'dcr' => 'application/x-director',
|
'dcr' => 'application/x-director',
|
||||||
'dir' => 'application/x-director',
|
'dir' => 'application/x-director',
|
||||||
'dxr' => 'application/x-director',
|
'dxr' => 'application/x-director',
|
||||||
'dvi' => 'application/x-dvi',
|
'dvi' => 'application/x-dvi',
|
||||||
'gtar' => 'application/x-gtar',
|
'gtar' => 'application/x-gtar',
|
||||||
'gz' => 'application/x-gzip',
|
'gz' => 'application/x-gzip',
|
||||||
'gzip' => 'application/x-gzip',
|
'gzip' => 'application/x-gzip',
|
||||||
'php' => [
|
'php' => [
|
||||||
'application/x-php',
|
'application/x-php',
|
||||||
'application/x-httpd-php',
|
'application/x-httpd-php',
|
||||||
'application/php',
|
'application/php',
|
||||||
|
|
@ -105,41 +109,41 @@ class Mimes
|
||||||
'text/x-php',
|
'text/x-php',
|
||||||
'application/x-httpd-php-source',
|
'application/x-httpd-php-source',
|
||||||
],
|
],
|
||||||
'php4' => 'application/x-httpd-php',
|
'php4' => 'application/x-httpd-php',
|
||||||
'php3' => 'application/x-httpd-php',
|
'php3' => 'application/x-httpd-php',
|
||||||
'phtml' => 'application/x-httpd-php',
|
'phtml' => 'application/x-httpd-php',
|
||||||
'phps' => 'application/x-httpd-php-source',
|
'phps' => 'application/x-httpd-php-source',
|
||||||
'js' => ['application/x-javascript', 'text/plain'],
|
'js' => ['application/x-javascript', 'text/plain'],
|
||||||
'swf' => 'application/x-shockwave-flash',
|
'swf' => 'application/x-shockwave-flash',
|
||||||
'sit' => 'application/x-stuffit',
|
'sit' => 'application/x-stuffit',
|
||||||
'tar' => 'application/x-tar',
|
'tar' => 'application/x-tar',
|
||||||
'tgz' => ['application/x-tar', 'application/x-gzip-compressed'],
|
'tgz' => ['application/x-tar', 'application/x-gzip-compressed'],
|
||||||
'z' => 'application/x-compress',
|
'z' => 'application/x-compress',
|
||||||
'xhtml' => 'application/xhtml+xml',
|
'xhtml' => 'application/xhtml+xml',
|
||||||
'xht' => 'application/xhtml+xml',
|
'xht' => 'application/xhtml+xml',
|
||||||
'zip' => [
|
'zip' => [
|
||||||
'application/x-zip',
|
'application/x-zip',
|
||||||
'application/zip',
|
'application/zip',
|
||||||
'application/x-zip-compressed',
|
'application/x-zip-compressed',
|
||||||
'application/s-compressed',
|
'application/s-compressed',
|
||||||
'multipart/x-zip',
|
'multipart/x-zip',
|
||||||
],
|
],
|
||||||
'rar' => ['application/vnd.rar', 'application/x-rar', 'application/rar', 'application/x-rar-compressed'],
|
'rar' => ['application/vnd.rar', 'application/x-rar', 'application/rar', 'application/x-rar-compressed'],
|
||||||
'mid' => 'audio/midi',
|
'mid' => 'audio/midi',
|
||||||
'midi' => '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',
|
'mpga' => 'audio/mpeg',
|
||||||
'mp2' => 'audio/mpeg',
|
'mp2' => 'audio/mpeg',
|
||||||
'aif' => ['audio/x-aiff', 'audio/aiff'],
|
'aif' => ['audio/x-aiff', 'audio/aiff'],
|
||||||
'aiff' => ['audio/x-aiff', 'audio/aiff'],
|
'aiff' => ['audio/x-aiff', 'audio/aiff'],
|
||||||
'aifc' => 'audio/x-aiff',
|
'aifc' => 'audio/x-aiff',
|
||||||
'ram' => 'audio/x-pn-realaudio',
|
'ram' => 'audio/x-pn-realaudio',
|
||||||
'rm' => 'audio/x-pn-realaudio',
|
'rm' => 'audio/x-pn-realaudio',
|
||||||
'rpm' => 'audio/x-pn-realaudio-plugin',
|
'rpm' => 'audio/x-pn-realaudio-plugin',
|
||||||
'ra' => 'audio/x-realaudio',
|
'ra' => 'audio/x-realaudio',
|
||||||
'rv' => 'video/vnd.rn-realvideo',
|
'rv' => 'video/vnd.rn-realvideo',
|
||||||
'wav' => ['audio/x-wav', 'audio/wave', 'audio/wav'],
|
'wav' => ['audio/x-wav', 'audio/wave', 'audio/wav'],
|
||||||
'bmp' => [
|
'bmp' => [
|
||||||
'image/bmp',
|
'image/bmp',
|
||||||
'image/x-bmp',
|
'image/x-bmp',
|
||||||
'image/x-bitmap',
|
'image/x-bitmap',
|
||||||
|
|
@ -152,48 +156,47 @@ class Mimes
|
||||||
'application/x-bmp',
|
'application/x-bmp',
|
||||||
'application/x-win-bitmap',
|
'application/x-win-bitmap',
|
||||||
],
|
],
|
||||||
'gif' => 'image/gif',
|
'gif' => 'image/gif',
|
||||||
'jpg' => ['image/jpeg', 'image/pjpeg'],
|
'jpg' => ['image/jpeg', 'image/pjpeg'],
|
||||||
'jpeg' => ['image/jpeg', 'image/pjpeg'],
|
'jpeg' => ['image/jpeg', 'image/pjpeg'],
|
||||||
'jpe' => ['image/jpeg', 'image/pjpeg'],
|
'jpe' => ['image/jpeg', 'image/pjpeg'],
|
||||||
'jp2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
'jp2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||||
'j2k' => ['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'],
|
'jpf' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||||
'jpg2' => ['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'],
|
'jpx' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||||
'jpm' => ['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'],
|
'mj2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||||
'mjp2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
'mjp2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
|
||||||
'png' => ['image/png', 'image/x-png'],
|
'png' => ['image/png', 'image/x-png'],
|
||||||
'webp' => 'image/webp',
|
'tif' => 'image/tiff',
|
||||||
'tif' => 'image/tiff',
|
'tiff' => 'image/tiff',
|
||||||
'tiff' => 'image/tiff',
|
'css' => ['text/css', 'text/plain'],
|
||||||
'css' => ['text/css', 'text/plain'],
|
'html' => ['text/html', 'text/plain'],
|
||||||
'html' => ['text/html', 'text/plain'],
|
'htm' => ['text/html', 'text/plain'],
|
||||||
'htm' => ['text/html', 'text/plain'],
|
|
||||||
'shtml' => ['text/html', 'text/plain'],
|
'shtml' => ['text/html', 'text/plain'],
|
||||||
'txt' => 'text/plain',
|
'txt' => 'text/plain',
|
||||||
'text' => 'text/plain',
|
'text' => 'text/plain',
|
||||||
'log' => ['text/plain', 'text/x-log'],
|
'log' => ['text/plain', 'text/x-log'],
|
||||||
'rtx' => 'text/richtext',
|
'rtx' => 'text/richtext',
|
||||||
'rtf' => 'text/rtf',
|
'rtf' => 'text/rtf',
|
||||||
'xml' => ['application/xml', 'text/xml', 'text/plain'],
|
'xml' => ['application/xml', 'text/xml', 'text/plain'],
|
||||||
'xsl' => ['application/xml', 'text/xsl', 'text/xml'],
|
'xsl' => ['application/xml', 'text/xsl', 'text/xml'],
|
||||||
'mpeg' => 'video/mpeg',
|
'mpeg' => 'video/mpeg',
|
||||||
'mpg' => 'video/mpeg',
|
'mpg' => 'video/mpeg',
|
||||||
'mpe' => 'video/mpeg',
|
'mpe' => 'video/mpeg',
|
||||||
'qt' => 'video/quicktime',
|
'qt' => 'video/quicktime',
|
||||||
'mov' => 'video/quicktime',
|
'mov' => 'video/quicktime',
|
||||||
'avi' => ['video/x-msvideo', 'video/msvideo', 'video/avi', 'application/x-troff-msvideo'],
|
'avi' => ['video/x-msvideo', 'video/msvideo', 'video/avi', 'application/x-troff-msvideo'],
|
||||||
'movie' => 'video/x-sgi-movie',
|
'movie' => 'video/x-sgi-movie',
|
||||||
'doc' => ['application/msword', 'application/vnd.ms-office'],
|
'doc' => ['application/msword', 'application/vnd.ms-office'],
|
||||||
'docx' => [
|
'docx' => [
|
||||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||||
'application/zip',
|
'application/zip',
|
||||||
'application/msword',
|
'application/msword',
|
||||||
'application/x-zip',
|
'application/x-zip',
|
||||||
],
|
],
|
||||||
'dot' => ['application/msword', 'application/vnd.ms-office'],
|
'dot' => ['application/msword', 'application/vnd.ms-office'],
|
||||||
'dotx' => [
|
'dotx' => [
|
||||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||||
'application/zip',
|
'application/zip',
|
||||||
|
|
@ -209,49 +212,49 @@ class Mimes
|
||||||
'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
|
'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
|
||||||
'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12',
|
'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12',
|
||||||
'word' => ['application/msword', 'application/octet-stream'],
|
'word' => ['application/msword', 'application/octet-stream'],
|
||||||
'xl' => 'application/excel',
|
'xl' => 'application/excel',
|
||||||
'eml' => 'message/rfc822',
|
'eml' => 'message/rfc822',
|
||||||
'json' => ['application/json', 'text/json', 'text/plain'],
|
'json' => ['application/json', 'text/json'],
|
||||||
'pem' => ['application/x-x509-user-cert', 'application/x-pem-file', 'application/octet-stream'],
|
'pem' => ['application/x-x509-user-cert', 'application/x-pem-file', 'application/octet-stream'],
|
||||||
'p10' => ['application/x-pkcs10', 'application/pkcs10'],
|
'p10' => ['application/x-pkcs10', 'application/pkcs10'],
|
||||||
'p12' => 'application/x-pkcs12',
|
'p12' => 'application/x-pkcs12',
|
||||||
'p7a' => 'application/x-pkcs7-signature',
|
'p7a' => 'application/x-pkcs7-signature',
|
||||||
'p7c' => ['application/pkcs7-mime', 'application/x-pkcs7-mime'],
|
'p7c' => ['application/pkcs7-mime', 'application/x-pkcs7-mime'],
|
||||||
'p7m' => ['application/pkcs7-mime', 'application/x-pkcs7-mime'],
|
'p7m' => ['application/pkcs7-mime', 'application/x-pkcs7-mime'],
|
||||||
'p7r' => 'application/x-pkcs7-certreqresp',
|
'p7r' => 'application/x-pkcs7-certreqresp',
|
||||||
'p7s' => 'application/pkcs7-signature',
|
'p7s' => 'application/pkcs7-signature',
|
||||||
'crt' => ['application/x-x509-ca-cert', 'application/x-x509-user-cert', 'application/pkix-cert'],
|
'crt' => ['application/x-x509-ca-cert', 'application/x-x509-user-cert', 'application/pkix-cert'],
|
||||||
'crl' => ['application/pkix-crl', 'application/pkcs-crl'],
|
'crl' => ['application/pkix-crl', 'application/pkcs-crl'],
|
||||||
'der' => 'application/x-x509-ca-cert',
|
'der' => 'application/x-x509-ca-cert',
|
||||||
'kdb' => 'application/octet-stream',
|
'kdb' => 'application/octet-stream',
|
||||||
'pgp' => 'application/pgp',
|
'pgp' => 'application/pgp',
|
||||||
'gpg' => 'application/gpg-keys',
|
'gpg' => 'application/gpg-keys',
|
||||||
'sst' => 'application/octet-stream',
|
'sst' => 'application/octet-stream',
|
||||||
'csr' => 'application/octet-stream',
|
'csr' => 'application/octet-stream',
|
||||||
'rsa' => 'application/x-pkcs7',
|
'rsa' => 'application/x-pkcs7',
|
||||||
'cer' => ['application/pkix-cert', 'application/x-x509-ca-cert'],
|
'cer' => ['application/pkix-cert', 'application/x-x509-ca-cert'],
|
||||||
'3g2' => 'video/3gpp2',
|
'3g2' => 'video/3gpp2',
|
||||||
'3gp' => ['video/3gp', 'video/3gpp'],
|
'3gp' => ['video/3gp', 'video/3gpp'],
|
||||||
'mp4' => 'video/mp4',
|
'mp4' => 'video/mp4',
|
||||||
'm4a' => ['audio/m4a', 'audio/x-m4a', 'application/octet-stream'],
|
'm4a' => ['audio/m4a', 'audio/x-m4a', 'application/octet-stream'],
|
||||||
'f4v' => ['video/mp4', 'video/x-f4v'],
|
'f4v' => ['video/mp4', 'video/x-f4v'],
|
||||||
'flv' => 'video/x-flv',
|
'flv' => 'video/x-flv',
|
||||||
'webm' => 'video/webm',
|
'webm' => 'video/webm',
|
||||||
'aac' => 'audio/x-acc',
|
'aac' => 'audio/x-acc',
|
||||||
'm4u' => 'application/vnd.mpegurl',
|
'm4u' => 'application/vnd.mpegurl',
|
||||||
'm3u' => 'text/plain',
|
'm3u' => 'text/plain',
|
||||||
'xspf' => 'application/xspf+xml',
|
'xspf' => 'application/xspf+xml',
|
||||||
'vlc' => 'application/videolan',
|
'vlc' => 'application/videolan',
|
||||||
'wmv' => ['video/x-ms-wmv', 'video/x-ms-asf'],
|
'wmv' => ['video/x-ms-wmv', 'video/x-ms-asf'],
|
||||||
'au' => 'audio/x-au',
|
'au' => 'audio/x-au',
|
||||||
'ac3' => 'audio/ac3',
|
'ac3' => 'audio/ac3',
|
||||||
'flac' => 'audio/x-flac',
|
'flac' => 'audio/x-flac',
|
||||||
'ogg' => ['audio/ogg', 'video/ogg', 'application/ogg'],
|
'ogg' => ['audio/ogg', 'video/ogg', 'application/ogg'],
|
||||||
'kmz' => ['application/vnd.google-earth.kmz', 'application/zip', 'application/x-zip'],
|
'kmz' => ['application/vnd.google-earth.kmz', 'application/zip', 'application/x-zip'],
|
||||||
'kml' => ['application/vnd.google-earth.kml+xml', 'application/xml', 'text/xml'],
|
'kml' => ['application/vnd.google-earth.kml+xml', 'application/xml', 'text/xml'],
|
||||||
'ics' => 'text/calendar',
|
'ics' => 'text/calendar',
|
||||||
'ical' => 'text/calendar',
|
'ical' => 'text/calendar',
|
||||||
'zsh' => 'text/x-scriptzsh',
|
'zsh' => 'text/x-scriptzsh',
|
||||||
'7zip' => [
|
'7zip' => [
|
||||||
'application/x-compressed',
|
'application/x-compressed',
|
||||||
'application/x-zip-compressed',
|
'application/x-zip-compressed',
|
||||||
|
|
@ -276,11 +279,10 @@ class Mimes
|
||||||
],
|
],
|
||||||
'svg' => ['image/svg+xml', 'image/svg', 'application/xml', 'text/xml'],
|
'svg' => ['image/svg+xml', 'image/svg', 'application/xml', 'text/xml'],
|
||||||
'vcf' => 'text/x-vcard',
|
'vcf' => 'text/x-vcard',
|
||||||
'srt' => ['application/x-subrip', 'text/srt', 'text/plain', 'application/octet-stream'],
|
'srt' => ['text/srt', 'text/plain', 'application/octet-stream'],
|
||||||
'vtt' => ['text/vtt', 'text/plain'],
|
'vtt' => ['text/vtt', 'text/plain'],
|
||||||
'ico' => ['image/x-icon', 'image/x-ico', 'image/vnd.microsoft.icon'],
|
'ico' => ['image/x-icon', 'image/x-ico', 'image/vnd.microsoft.icon'],
|
||||||
'stl' => ['application/sla', 'application/vnd.ms-pki.stl', 'application/x-navistyle', 'model/stl',
|
'stl' => ['application/sla', 'application/vnd.ms-pki.stl', 'application/x-navistyle'],
|
||||||
'application/octet-stream', ],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -304,28 +306,35 @@ class Mimes
|
||||||
/**
|
/**
|
||||||
* Attempts to determine the best file extension for a given mime type.
|
* Attempts to determine the best file extension for a given mime type.
|
||||||
*
|
*
|
||||||
* @param string|null $proposedExtension - default extension (in case there is more than one with the same mime type)
|
* @param string $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.
|
* @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 = ''): ?string
|
||||||
{
|
{
|
||||||
$type = trim(strtolower($type), '. ');
|
$type = trim(strtolower($type), '. ');
|
||||||
|
|
||||||
$proposedExtension = trim(strtolower($proposedExtension ?? ''));
|
$proposedExtension = trim(strtolower($proposedExtension));
|
||||||
|
|
||||||
if (
|
if ($proposedExtension !== '') {
|
||||||
$proposedExtension !== ''
|
if (array_key_exists($proposedExtension, static::$mimes) && in_array(
|
||||||
&& array_key_exists($proposedExtension, static::$mimes)
|
$type,
|
||||||
&& in_array($type, (array) static::$mimes[$proposedExtension], true)
|
is_string(static::$mimes[$proposedExtension]) ? [
|
||||||
) {
|
static::$mimes[$proposedExtension],
|
||||||
// The detected mime type matches with the proposed extension.
|
] : static::$mimes[$proposedExtension],
|
||||||
return $proposedExtension;
|
true
|
||||||
|
)) {
|
||||||
|
// The detected mime type matches with the proposed extension.
|
||||||
|
return $proposedExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
// An extension was proposed, but the media type does not match the mime type list.
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reverse check the mime type list if no extension was proposed.
|
// Reverse check the mime type list if no extension was proposed.
|
||||||
// This search is order sensitive!
|
// This search is order sensitive!
|
||||||
foreach (static::$mimes as $ext => $types) {
|
foreach (static::$mimes as $ext => $types) {
|
||||||
if (in_array($type, (array) $types, true)) {
|
if ((is_string($types) && $types === $type) || (is_array($types) && in_array($type, $types, true))) {
|
||||||
return $ext;
|
return $ext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,6 @@ namespace Config;
|
||||||
|
|
||||||
use CodeIgniter\Modules\Modules as BaseModules;
|
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
|
class Modules extends BaseModules
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
@ -39,29 +33,6 @@ class Modules extends BaseModules
|
||||||
*/
|
*/
|
||||||
public $discoverInComposer = true;
|
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
|
* Auto-Discovery Rules
|
||||||
|
|
@ -72,7 +43,7 @@ class Modules extends BaseModules
|
||||||
*
|
*
|
||||||
* If it is not listed, only the base application elements will be used.
|
* If it is not listed, only the base application elements will be used.
|
||||||
*
|
*
|
||||||
* @var list<string>
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
public $aliases = ['events', 'filters', 'registrars', 'routes', 'services'];
|
public $aliases = ['events', 'filters', 'registrars', 'routes', 'services'];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
<?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,11 +21,13 @@ class Pager extends BaseConfig
|
||||||
* and the desired group as $pagerGroup;
|
* and the desired group as $pagerGroup;
|
||||||
*
|
*
|
||||||
* @var array<string, string>
|
* @var array<string, string>
|
||||||
|
*
|
||||||
|
* @noRector Rector\Php55\Rector\String_\StringClassNameToClassConstantRector
|
||||||
*/
|
*/
|
||||||
public array $templates = [
|
public $templates = [
|
||||||
'default_full' => 'App\Views\pager\default_full',
|
'default_full' => 'App\Views\pager\default_full',
|
||||||
'default_simple' => 'CodeIgniter\Pager\Views\default_simple',
|
'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;
|
namespace Config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Paths
|
||||||
|
*
|
||||||
* Holds the paths that are used by the system to locate the main directories, app, system, etc.
|
* 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
|
* Modifying these allows you to restructure your application, share a system folder between multiple applications, and
|
||||||
* more.
|
* more.
|
||||||
*
|
*
|
||||||
* All paths are relative to the project's root folder.
|
* 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
|
class Paths
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
@ -26,7 +26,7 @@ class Paths
|
||||||
* the path if the folder is not in the same directory as this file.
|
* the path if the folder is not in the same directory as this file.
|
||||||
*/
|
*/
|
||||||
public string $systemDirectory =
|
public string $systemDirectory =
|
||||||
__DIR__ . '/../../vendor/codeigniter4/framework/system';
|
__DIR__ . '/../../vendor/codeigniter4/codeigniter4/system';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ---------------------------------------------------------------
|
* ---------------------------------------------------------------
|
||||||
|
|
@ -35,8 +35,8 @@ class Paths
|
||||||
*
|
*
|
||||||
* If you want this front controller to use a different "app"
|
* If you want this front controller to use a different "app"
|
||||||
* folder than the default one you can set its name here. The folder
|
* folder than the default one you can set its name here. The folder
|
||||||
* can also be renamed or relocated anywhere on your server. If
|
* can also be renamed or relocated anywhere on your getServer. If
|
||||||
* you do, use a full server path.
|
* you do, use a full getServer path.
|
||||||
*
|
*
|
||||||
* @see http://codeigniter.com/user_guide/general/managing_apps.html
|
* @see http://codeigniter.com/user_guide/general/managing_apps.html
|
||||||
*/
|
*/
|
||||||
|
|
@ -72,7 +72,7 @@ class Paths
|
||||||
* This variable must contain the name of the directory that
|
* This variable must contain the name of the directory that
|
||||||
* contains the view files used by your application. By
|
* contains the view files used by your application. By
|
||||||
* default this is in `app/Views`. This value
|
* default this is in `app/Views`. This value
|
||||||
* is used when no value is provided to `service('renderer')`.
|
* is used when no value is provided to `Services::renderer()`.
|
||||||
*/
|
*/
|
||||||
public string $viewDirectory = __DIR__ . '/../Views';
|
public string $viewDirectory = __DIR__ . '/../Views';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Config;
|
|
||||||
|
|
||||||
use CodeIgniter\Config\Publisher as BasePublisher;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Publisher Configuration
|
|
||||||
*
|
|
||||||
* Defines basic security restrictions for the Publisher class to prevent abuse by injecting malicious files into a
|
|
||||||
* project.
|
|
||||||
*/
|
|
||||||
class Publisher extends BasePublisher
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* A list of allowed destinations with a (pseudo-)regex of allowed files for each destination. Attempts to publish
|
|
||||||
* 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>
|
|
||||||
*/
|
|
||||||
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',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
@ -2,19 +2,41 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use CodeIgniter\Router\RouteCollection;
|
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 (file_exists(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();
|
||||||
|
$routes->setAutoRoute(false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------
|
* --------------------------------------------------------------------
|
||||||
* Placeholder definitions
|
* Placeholder definitions
|
||||||
* --------------------------------------------------------------------
|
* --------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
/** @var RouteCollection $routes */
|
|
||||||
$routes->addPlaceholder('podcastHandle', '[a-zA-Z0-9\_]{1,32}');
|
$routes->addPlaceholder('podcastName', '[a-zA-Z0-9\_]{1,32}');
|
||||||
$routes->addPlaceholder('slug', '[a-zA-Z0-9\-]{1,128}');
|
$routes->addPlaceholder('slug', '[a-zA-Z0-9\-]{1,191}');
|
||||||
$routes->addPlaceholder('base64', '[A-Za-z0-9\.\_]+\-{0,2}');
|
$routes->addPlaceholder('base64', '[A-Za-z0-9\.\_]+\-{0,2}');
|
||||||
$routes->addPlaceholder('postAction', '\bfavourite|\breblog|\breply');
|
$routes->addPlaceholder('platformType', '\bpodcasting|\bsocial|\bfunding');
|
||||||
$routes->addPlaceholder('embedTheme', '\blight|\bdark|\blight-transparent|\bdark-transparent');
|
$routes->addPlaceholder('statusAction', '\bfavourite|\breblog|\breply');
|
||||||
|
$routes->addPlaceholder('embeddablePlayerTheme', '\blight|\bdark|\blight-transparent|\bdark-transparent');
|
||||||
$routes->addPlaceholder(
|
$routes->addPlaceholder(
|
||||||
'uuid',
|
'uuid',
|
||||||
'[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}',
|
'[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}',
|
||||||
|
|
@ -26,111 +48,700 @@ $routes->addPlaceholder(
|
||||||
* --------------------------------------------------------------------
|
* --------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$routes->get('manifest.webmanifest', 'WebmanifestController', [
|
|
||||||
'as' => 'webmanifest',
|
|
||||||
]);
|
|
||||||
$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
|
// We get a performance increase by specifying the default
|
||||||
// route since we don't have to scan directories.
|
// route since we don't have to scan directories.
|
||||||
$routes->get('/', 'HomeController', [
|
$routes->get('/', 'HomeController::index', [
|
||||||
'as' => 'home',
|
'as' => 'home',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Install Wizard route
|
||||||
|
$routes->group(config('App')->installGateway, function ($routes): void {
|
||||||
|
$routes->get('/', 'InstallController', [
|
||||||
|
'as' => 'install',
|
||||||
|
]);
|
||||||
|
$routes->post('instance-config', 'InstallController::attemptInstanceConfig', [
|
||||||
|
'as' => 'instance-config',
|
||||||
|
]);
|
||||||
|
$routes->post('database-config', 'InstallController::attemptDatabaseConfig', [
|
||||||
|
'as' => 'database-config',
|
||||||
|
]);
|
||||||
|
$routes->post('cache-config', 'InstallController::attemptCacheConfig', [
|
||||||
|
'as' => 'cache-config',
|
||||||
|
]);
|
||||||
|
$routes->post(
|
||||||
|
'create-superadmin',
|
||||||
|
'InstallController::attemptCreateSuperAdmin',
|
||||||
|
[
|
||||||
|
'as' => 'create-superadmin',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
$routes->get('.well-known/platforms', 'Platform');
|
$routes->get('.well-known/platforms', 'Platform');
|
||||||
|
|
||||||
service('auth')
|
// Admin area
|
||||||
->routes($routes);
|
$routes->group(
|
||||||
|
config('App')
|
||||||
|
->adminGateway,
|
||||||
|
[
|
||||||
|
'namespace' => 'App\Controllers\Admin',
|
||||||
|
],
|
||||||
|
function ($routes): void {
|
||||||
|
$routes->get('/', 'HomeController', [
|
||||||
|
'as' => 'admin',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$routes->group('persons', function ($routes): void {
|
||||||
|
$routes->get('/', 'PersonController', [
|
||||||
|
'as' => 'person-list',
|
||||||
|
'filter' => 'permission:person-list',
|
||||||
|
]);
|
||||||
|
$routes->get('new', 'PersonController::create', [
|
||||||
|
'as' => 'person-create',
|
||||||
|
'filter' => 'permission:person-create',
|
||||||
|
]);
|
||||||
|
$routes->post('new', 'PersonController::attemptCreate', [
|
||||||
|
'filter' => 'permission:person-create',
|
||||||
|
]);
|
||||||
|
$routes->group('(:num)', function ($routes): void {
|
||||||
|
$routes->get('/', 'PersonController::view/$1', [
|
||||||
|
'as' => 'person-view',
|
||||||
|
'filter' => 'permission:person-view',
|
||||||
|
]);
|
||||||
|
$routes->get('edit', 'PersonController::edit/$1', [
|
||||||
|
'as' => 'person-edit',
|
||||||
|
'filter' => 'permission:person-edit',
|
||||||
|
]);
|
||||||
|
$routes->post('edit', 'PersonController::attemptEdit/$1', [
|
||||||
|
'filter' => 'permission:person-edit',
|
||||||
|
]);
|
||||||
|
$routes->add('delete', 'PersonController::delete/$1', [
|
||||||
|
'as' => 'person-delete',
|
||||||
|
'filter' => 'permission:person-delete',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Podcasts
|
||||||
|
$routes->group('podcasts', function ($routes): void {
|
||||||
|
$routes->get('/', 'PodcastController::list', [
|
||||||
|
'as' => 'podcast-list',
|
||||||
|
]);
|
||||||
|
$routes->get('new', 'PodcastController::create', [
|
||||||
|
'as' => 'podcast-create',
|
||||||
|
'filter' => 'permission:podcasts-create',
|
||||||
|
]);
|
||||||
|
$routes->post('new', 'PodcastController::attemptCreate', [
|
||||||
|
'filter' => 'permission:podcasts-create',
|
||||||
|
]);
|
||||||
|
$routes->get('import', 'PodcastImportController', [
|
||||||
|
'as' => 'podcast-import',
|
||||||
|
'filter' => 'permission:podcasts-import',
|
||||||
|
]);
|
||||||
|
$routes->post('import', 'PodcastImportController::attemptImport', [
|
||||||
|
'filter' => 'permission:podcasts-import',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Podcast
|
||||||
|
// Use ids in admin area to help permission and group lookups
|
||||||
|
$routes->group('(:num)', function ($routes): void {
|
||||||
|
$routes->get('/', 'PodcastController::view/$1', [
|
||||||
|
'as' => 'podcast-view',
|
||||||
|
'filter' => 'permission:podcasts-view,podcast-view',
|
||||||
|
]);
|
||||||
|
$routes->get('edit', 'PodcastController::edit/$1', [
|
||||||
|
'as' => 'podcast-edit',
|
||||||
|
'filter' => 'permission:podcast-edit',
|
||||||
|
]);
|
||||||
|
$routes->post('edit', 'PodcastController::attemptEdit/$1', [
|
||||||
|
'filter' => 'permission:podcast-edit',
|
||||||
|
]);
|
||||||
|
$routes->get('delete', 'PodcastController::delete/$1', [
|
||||||
|
'as' => 'podcast-delete',
|
||||||
|
'filter' => 'permission:podcasts-delete',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$routes->group('persons', function ($routes): void {
|
||||||
|
$routes->get('/', 'PodcastPersonController/$1', [
|
||||||
|
'as' => 'podcast-person-manage',
|
||||||
|
'filter' => 'permission:podcast-edit',
|
||||||
|
]);
|
||||||
|
$routes->post(
|
||||||
|
'/',
|
||||||
|
'PodcastPersonController::attemptAdd/$1',
|
||||||
|
[
|
||||||
|
'filter' => 'permission:podcast-edit',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
$routes->get(
|
||||||
|
'(:num)/remove',
|
||||||
|
'PodcastPersonController::remove/$1/$2',
|
||||||
|
[
|
||||||
|
'as' => 'podcast-person-remove',
|
||||||
|
'filter' => 'permission:podcast-edit',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
$routes->group('analytics', function ($routes): void {
|
||||||
|
$routes->get('/', 'PodcastController::viewAnalytics/$1', [
|
||||||
|
'as' => 'podcast-analytics',
|
||||||
|
'filter' => 'permission:podcasts-view,podcast-view',
|
||||||
|
]);
|
||||||
|
$routes->get(
|
||||||
|
'webpages',
|
||||||
|
'PodcastController::viewAnalyticsWebpages/$1',
|
||||||
|
[
|
||||||
|
'as' => 'podcast-analytics-webpages',
|
||||||
|
'filter' => 'permission:podcasts-view,podcast-view',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->get(
|
||||||
|
'locations',
|
||||||
|
'PodcastController::viewAnalyticsLocations/$1',
|
||||||
|
[
|
||||||
|
'as' => 'podcast-analytics-locations',
|
||||||
|
'filter' => 'permission:podcasts-view,podcast-view',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->get(
|
||||||
|
'unique-listeners',
|
||||||
|
'PodcastController::viewAnalyticsUniqueListeners/$1',
|
||||||
|
[
|
||||||
|
'as' => 'podcast-analytics-unique-listeners',
|
||||||
|
'filter' => 'permission:podcasts-view,podcast-view',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->get(
|
||||||
|
'listening-time',
|
||||||
|
'PodcastController::viewAnalyticsListeningTime/$1',
|
||||||
|
[
|
||||||
|
'as' => 'podcast-analytics-listening-time',
|
||||||
|
'filter' => 'permission:podcasts-view,podcast-view',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->get(
|
||||||
|
'time-periods',
|
||||||
|
'PodcastController::viewAnalyticsTimePeriods/$1',
|
||||||
|
[
|
||||||
|
'as' => 'podcast-analytics-time-periods',
|
||||||
|
'filter' => 'permission:podcasts-view,podcast-view',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->get(
|
||||||
|
'players',
|
||||||
|
'PodcastController::viewAnalyticsPlayers/$1',
|
||||||
|
[
|
||||||
|
'as' => 'podcast-analytics-players',
|
||||||
|
'filter' => 'permission:podcasts-view,podcast-view',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Podcast episodes
|
||||||
|
$routes->group('episodes', function ($routes): void {
|
||||||
|
$routes->get('/', 'EpisodeController::list/$1', [
|
||||||
|
'as' => 'episode-list',
|
||||||
|
'filter' =>
|
||||||
|
'permission:episodes-list,podcast_episodes-list',
|
||||||
|
]);
|
||||||
|
$routes->get('new', 'EpisodeController::create/$1', [
|
||||||
|
'as' => 'episode-create',
|
||||||
|
'filter' => 'permission:podcast_episodes-create',
|
||||||
|
]);
|
||||||
|
$routes->post(
|
||||||
|
'new',
|
||||||
|
'EpisodeController::attemptCreate/$1',
|
||||||
|
[
|
||||||
|
'filter' => 'permission:podcast_episodes-create',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Episode
|
||||||
|
$routes->group('(:num)', function ($routes): void {
|
||||||
|
$routes->get('/', 'EpisodeController::view/$1/$2', [
|
||||||
|
'as' => 'episode-view',
|
||||||
|
'filter' =>
|
||||||
|
'permission:episodes-view,podcast_episodes-view',
|
||||||
|
]);
|
||||||
|
$routes->get('edit', 'EpisodeController::edit/$1/$2', [
|
||||||
|
'as' => 'episode-edit',
|
||||||
|
'filter' => 'permission:podcast_episodes-edit',
|
||||||
|
]);
|
||||||
|
$routes->post(
|
||||||
|
'edit',
|
||||||
|
'EpisodeController::attemptEdit/$1/$2',
|
||||||
|
[
|
||||||
|
'filter' => 'permission:podcast_episodes-edit',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->get(
|
||||||
|
'publish',
|
||||||
|
'EpisodeController::publish/$1/$2',
|
||||||
|
[
|
||||||
|
'as' => 'episode-publish',
|
||||||
|
'filter' =>
|
||||||
|
'permission:podcast-manage_publications',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->post(
|
||||||
|
'publish',
|
||||||
|
'EpisodeController::attemptPublish/$1/$2',
|
||||||
|
[
|
||||||
|
'filter' =>
|
||||||
|
'permission:podcast-manage_publications',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->get(
|
||||||
|
'publish-edit',
|
||||||
|
'EpisodeController::publishEdit/$1/$2',
|
||||||
|
[
|
||||||
|
'as' => 'episode-publish_edit',
|
||||||
|
'filter' =>
|
||||||
|
'permission:podcast-manage_publications',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->post(
|
||||||
|
'publish-edit',
|
||||||
|
'EpisodeController::attemptPublishEdit/$1/$2',
|
||||||
|
[
|
||||||
|
'filter' =>
|
||||||
|
'permission:podcast-manage_publications',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->get(
|
||||||
|
'publish-cancel',
|
||||||
|
'EpisodeController::publishCancel/$1/$2',
|
||||||
|
[
|
||||||
|
'as' => 'episode-publish-cancel',
|
||||||
|
'filter' =>
|
||||||
|
'permission:podcast-manage_publications',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->get(
|
||||||
|
'unpublish',
|
||||||
|
'EpisodeController::unpublish/$1/$2',
|
||||||
|
[
|
||||||
|
'as' => 'episode-unpublish',
|
||||||
|
'filter' =>
|
||||||
|
'permission:podcast-manage_publications',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->post(
|
||||||
|
'unpublish',
|
||||||
|
'EpisodeController::attemptUnpublish/$1/$2',
|
||||||
|
[
|
||||||
|
'filter' =>
|
||||||
|
'permission:podcast-manage_publications',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->get(
|
||||||
|
'delete',
|
||||||
|
'EpisodeController::delete/$1/$2',
|
||||||
|
[
|
||||||
|
'as' => 'episode-delete',
|
||||||
|
'filter' =>
|
||||||
|
'permission:podcast_episodes-delete',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->get(
|
||||||
|
'transcript-delete',
|
||||||
|
'EpisodeController::transcriptDelete/$1/$2',
|
||||||
|
[
|
||||||
|
'as' => 'transcript-delete',
|
||||||
|
'filter' => 'permission:podcast_episodes-edit',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->get(
|
||||||
|
'chapters-delete',
|
||||||
|
'EpisodeController::chaptersDelete/$1/$2',
|
||||||
|
[
|
||||||
|
'as' => 'chapters-delete',
|
||||||
|
'filter' => 'permission:podcast_episodes-edit',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->get(
|
||||||
|
'soundbites',
|
||||||
|
'EpisodeController::soundbitesEdit/$1/$2',
|
||||||
|
[
|
||||||
|
'as' => 'soundbites-edit',
|
||||||
|
'filter' => 'permission:podcast_episodes-edit',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->post(
|
||||||
|
'soundbites',
|
||||||
|
'EpisodeController::soundbitesAttemptEdit/$1/$2',
|
||||||
|
[
|
||||||
|
'filter' => 'permission:podcast_episodes-edit',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->get(
|
||||||
|
'soundbites/(:num)/delete',
|
||||||
|
'EpisodeController::soundbiteDelete/$1/$2/$3',
|
||||||
|
[
|
||||||
|
'as' => 'soundbite-delete',
|
||||||
|
'filter' => 'permission:podcast_episodes-edit',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->get(
|
||||||
|
'embeddable-player',
|
||||||
|
'EpisodeController::embeddablePlayer/$1/$2',
|
||||||
|
[
|
||||||
|
'as' => 'embeddable-player-add',
|
||||||
|
'filter' => 'permission:podcast_episodes-edit',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
$routes->group('persons', function ($routes): void {
|
||||||
|
$routes->get('/', 'EpisodePersonController/$1/$2', [
|
||||||
|
'as' => 'episode-person-manage',
|
||||||
|
'filter' => 'permission:podcast_episodes-edit',
|
||||||
|
]);
|
||||||
|
$routes->post(
|
||||||
|
'/',
|
||||||
|
'EpisodePersonController::attemptAdd/$1/$2',
|
||||||
|
[
|
||||||
|
'filter' =>
|
||||||
|
'permission:podcast_episodes-edit',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->get(
|
||||||
|
'(:num)/remove',
|
||||||
|
'EpisodePersonController::remove/$1/$2/$3',
|
||||||
|
[
|
||||||
|
'as' => 'episode-person-remove',
|
||||||
|
'filter' =>
|
||||||
|
'permission:podcast_episodes-edit',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Podcast contributors
|
||||||
|
$routes->group('contributors', function ($routes): void {
|
||||||
|
$routes->get('/', 'ContributorController::list/$1', [
|
||||||
|
'as' => 'contributor-list',
|
||||||
|
'filter' =>
|
||||||
|
'permission:podcasts-view,podcast-manage_contributors',
|
||||||
|
]);
|
||||||
|
$routes->get('add', 'ContributorController::add/$1', [
|
||||||
|
'as' => 'contributor-add',
|
||||||
|
'filter' => 'permission:podcast-manage_contributors',
|
||||||
|
]);
|
||||||
|
$routes->post(
|
||||||
|
'add',
|
||||||
|
'ContributorController::attemptAdd/$1',
|
||||||
|
[
|
||||||
|
'filter' =>
|
||||||
|
'permission:podcast-manage_contributors',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Contributor
|
||||||
|
$routes->group('(:num)', function ($routes): void {
|
||||||
|
$routes->get('/', 'ContributorController::view/$1/$2', [
|
||||||
|
'as' => 'contributor-view',
|
||||||
|
'filter' =>
|
||||||
|
'permission:podcast-manage_contributors',
|
||||||
|
]);
|
||||||
|
$routes->get(
|
||||||
|
'edit',
|
||||||
|
'ContributorController::edit/$1/$2',
|
||||||
|
[
|
||||||
|
'as' => 'contributor-edit',
|
||||||
|
'filter' =>
|
||||||
|
'permission:podcast-manage_contributors',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->post(
|
||||||
|
'edit',
|
||||||
|
'ContributorController::attemptEdit/$1/$2',
|
||||||
|
[
|
||||||
|
'filter' =>
|
||||||
|
'permission:podcast-manage_contributors',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->get(
|
||||||
|
'remove',
|
||||||
|
'ContributorController::remove/$1/$2',
|
||||||
|
[
|
||||||
|
'as' => 'contributor-remove',
|
||||||
|
'filter' =>
|
||||||
|
'permission:podcast-manage_contributors',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$routes->group('platforms', function ($routes): void {
|
||||||
|
$routes->get(
|
||||||
|
'/',
|
||||||
|
'PodcastPlatformController::platforms/$1/podcasting',
|
||||||
|
[
|
||||||
|
'as' => 'platforms-podcasting',
|
||||||
|
'filter' => 'permission:podcast-manage_platforms',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->get(
|
||||||
|
'social',
|
||||||
|
'PodcastPlatformController::platforms/$1/social',
|
||||||
|
[
|
||||||
|
'as' => 'platforms-social',
|
||||||
|
'filter' => 'permission:podcast-manage_platforms',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->get(
|
||||||
|
'funding',
|
||||||
|
'PodcastPlatformController::platforms/$1/funding',
|
||||||
|
[
|
||||||
|
'as' => 'platforms-funding',
|
||||||
|
'filter' => 'permission:podcast-manage_platforms',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->post(
|
||||||
|
'save/(:platformType)',
|
||||||
|
'PodcastPlatformController::attemptPlatformsUpdate/$1/$2',
|
||||||
|
[
|
||||||
|
'as' => 'platforms-save',
|
||||||
|
'filter' => 'permission:podcast-manage_platforms',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->get(
|
||||||
|
'(:slug)/podcast-platform-remove',
|
||||||
|
'PodcastPlatformController::removePodcastPlatform/$1/$2',
|
||||||
|
[
|
||||||
|
'as' => 'podcast-platform-remove',
|
||||||
|
'filter' => 'permission:podcast-manage_platforms',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Instance wide Fediverse config
|
||||||
|
$routes->group('fediverse', function ($routes): void {
|
||||||
|
$routes->get('/', 'FediverseController::dashboard', [
|
||||||
|
'as' => 'fediverse-dashboard',
|
||||||
|
]);
|
||||||
|
$routes->get(
|
||||||
|
'blocked-actors',
|
||||||
|
'FediverseController::blockedActors',
|
||||||
|
[
|
||||||
|
'as' => 'fediverse-blocked-actors',
|
||||||
|
'filter' => 'permission:fediverse-block_actors',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->get(
|
||||||
|
'blocked-domains',
|
||||||
|
'FediverseController::blockedDomains',
|
||||||
|
[
|
||||||
|
'as' => 'fediverse-blocked-domains',
|
||||||
|
'filter' => 'permission:fediverse-block_domains',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pages
|
||||||
|
$routes->group('pages', function ($routes): void {
|
||||||
|
$routes->get('/', 'PageController::list', [
|
||||||
|
'as' => 'page-list',
|
||||||
|
]);
|
||||||
|
$routes->get('new', 'PageController::create', [
|
||||||
|
'as' => 'page-create',
|
||||||
|
'filter' => 'permission:pages-manage',
|
||||||
|
]);
|
||||||
|
$routes->post('new', 'PageController::attemptCreate', [
|
||||||
|
'filter' => 'permission:pages-manage',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$routes->group('(:num)', function ($routes): void {
|
||||||
|
$routes->get('/', 'PageController::view/$1', [
|
||||||
|
'as' => 'page-view',
|
||||||
|
]);
|
||||||
|
$routes->get('edit', 'PageController::edit/$1', [
|
||||||
|
'as' => 'page-edit',
|
||||||
|
'filter' => 'permission:pages-manage',
|
||||||
|
]);
|
||||||
|
$routes->post('edit', 'PageController::attemptEdit/$1', [
|
||||||
|
'filter' => 'permission:pages-manage',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$routes->get('delete', 'PageController::delete/$1', [
|
||||||
|
'as' => 'page-delete',
|
||||||
|
'filter' => 'permission:pages-manage',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Users
|
||||||
|
$routes->group('users', function ($routes): void {
|
||||||
|
$routes->get('/', 'UserController::list', [
|
||||||
|
'as' => 'user-list',
|
||||||
|
'filter' => 'permission:users-list',
|
||||||
|
]);
|
||||||
|
$routes->get('new', 'UserController::create', [
|
||||||
|
'as' => 'user-create',
|
||||||
|
'filter' => 'permission:users-create',
|
||||||
|
]);
|
||||||
|
$routes->post('new', 'UserController::attemptCreate', [
|
||||||
|
'filter' => 'permission:users-create',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// User
|
||||||
|
$routes->group('(:num)', function ($routes): void {
|
||||||
|
$routes->get('/', 'UserController::view/$1', [
|
||||||
|
'as' => 'user-view',
|
||||||
|
'filter' => 'permission:users-view',
|
||||||
|
]);
|
||||||
|
$routes->get('edit', 'UserController::edit/$1', [
|
||||||
|
'as' => 'user-edit',
|
||||||
|
'filter' => 'permission:users-manage_authorizations',
|
||||||
|
]);
|
||||||
|
$routes->post('edit', 'UserController::attemptEdit/$1', [
|
||||||
|
'filter' => 'permission:users-manage_authorizations',
|
||||||
|
]);
|
||||||
|
$routes->get('ban', 'UserController::ban/$1', [
|
||||||
|
'as' => 'user-ban',
|
||||||
|
'filter' => 'permission:users-manage_bans',
|
||||||
|
]);
|
||||||
|
$routes->get('unban', 'UserController::unBan/$1', [
|
||||||
|
'as' => 'user-unban',
|
||||||
|
'filter' => 'permission:users-manage_bans',
|
||||||
|
]);
|
||||||
|
$routes->get(
|
||||||
|
'force-pass-reset',
|
||||||
|
'UserController::forcePassReset/$1',
|
||||||
|
[
|
||||||
|
'as' => 'user-force_pass_reset',
|
||||||
|
'filter' => 'permission:users-force_pass_reset',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->get('delete', 'UserController::delete/$1', [
|
||||||
|
'as' => 'user-delete',
|
||||||
|
'filter' => 'permission:users-delete',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// My account
|
||||||
|
$routes->group('my-account', function ($routes): void {
|
||||||
|
$routes->get('/', 'MyAccountController', [
|
||||||
|
'as' => 'my-account',
|
||||||
|
]);
|
||||||
|
$routes->get(
|
||||||
|
'change-password',
|
||||||
|
'MyAccountController::changePassword/$1',
|
||||||
|
[
|
||||||
|
'as' => 'change-password',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$routes->post('change-password', 'MyAccountController::attemptChange/$1');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overwriting Myth:auth routes file
|
||||||
|
*/
|
||||||
|
$routes->group(config('App')->authGateway, function ($routes): void {
|
||||||
|
// Login/out
|
||||||
|
$routes->get('login', 'AuthController::login', [
|
||||||
|
'as' => 'login',
|
||||||
|
]);
|
||||||
|
$routes->post('login', 'AuthController::attemptLogin');
|
||||||
|
$routes->get('logout', 'AuthController::logout', [
|
||||||
|
'as' => 'logout',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Registration
|
||||||
|
$routes->get('register', 'AuthController::register', [
|
||||||
|
'as' => 'register',
|
||||||
|
]);
|
||||||
|
$routes->post('register', 'AuthController::attemptRegister');
|
||||||
|
|
||||||
|
// Activation
|
||||||
|
$routes->get('activate-account', 'AuthController::activateAccount', [
|
||||||
|
'as' => 'activate-account',
|
||||||
|
]);
|
||||||
|
$routes->get(
|
||||||
|
'resend-activate-account',
|
||||||
|
'AuthController::resendActivateAccount',
|
||||||
|
[
|
||||||
|
'as' => 'resend-activate-account',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Forgot/Resets
|
||||||
|
$routes->get('forgot', 'AuthController::forgotPassword', [
|
||||||
|
'as' => 'forgot',
|
||||||
|
]);
|
||||||
|
$routes->post('forgot', 'AuthController::attemptForgot');
|
||||||
|
$routes->get('reset-password', 'AuthController::resetPassword', [
|
||||||
|
'as' => 'reset-password',
|
||||||
|
]);
|
||||||
|
$routes->post('reset-password', 'AuthController::attemptReset');
|
||||||
|
});
|
||||||
|
|
||||||
// Podcast's Public routes
|
// Podcast's Public routes
|
||||||
$routes->group('@(:podcastHandle)', static function ($routes): void {
|
$routes->group('@(:podcastName)', function ($routes): void {
|
||||||
// override default Fediverse Library's actor route
|
$routes->get('/', 'PodcastController::activity/$1', [
|
||||||
|
'as' => 'podcast-activity',
|
||||||
|
]);
|
||||||
|
// override default ActivityPub Library's actor route
|
||||||
$routes->options('/', 'ActivityPubController::preflight');
|
$routes->options('/', 'ActivityPubController::preflight');
|
||||||
$routes->get('/', 'PodcastController::activity/$1', [
|
$routes->get('/', 'PodcastController::activity/$1', [
|
||||||
'as' => 'podcast-activity',
|
'as' => 'actor',
|
||||||
'alternate-content' => [
|
'alternate-content' => [
|
||||||
'application/activity+json' => [
|
'application/activity+json' => [
|
||||||
'namespace' => 'Modules\Fediverse\Controllers',
|
'namespace' => 'ActivityPub\Controllers',
|
||||||
'controller-method' => 'ActorController::index/$1',
|
'controller-method' => 'ActorController/$1',
|
||||||
],
|
],
|
||||||
'application/podcast-activity+json' => [
|
'application/podcast-activity+json' => [
|
||||||
'namespace' => 'App\Controllers',
|
'namespace' => 'App\Controllers',
|
||||||
'controller-method' => 'PodcastController::podcastActor/$1',
|
'controller-method' => 'PodcastController::podcastActor/$1',
|
||||||
],
|
],
|
||||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
|
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
|
||||||
'namespace' => 'Modules\Fediverse\Controllers',
|
'namespace' => 'ActivityPub\Controllers',
|
||||||
'controller-method' => 'ActorController::index/$1',
|
'controller-method' => 'ActorController/$1',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'filter' => 'allow-cors',
|
|
||||||
]);
|
|
||||||
$routes->get('manifest.webmanifest', 'WebmanifestController::podcastManifest/$1', [
|
|
||||||
'as' => 'podcast-webmanifest',
|
|
||||||
]);
|
|
||||||
$routes->get('links', 'PodcastController::links/$1', [
|
|
||||||
'as' => 'podcast-links',
|
|
||||||
]);
|
|
||||||
$routes->get('about', 'PodcastController::about/$1', [
|
|
||||||
'as' => 'podcast-about',
|
|
||||||
]);
|
]);
|
||||||
$routes->options('episodes', 'ActivityPubController::preflight');
|
$routes->options('episodes', 'ActivityPubController::preflight');
|
||||||
$routes->get('episodes', 'PodcastController::episodes/$1', [
|
$routes->get('episodes', 'PodcastController::episodes/$1', [
|
||||||
'as' => 'podcast-episodes',
|
'as' => 'podcast-episodes',
|
||||||
'alternate-content' => [
|
'alternate-content' => [
|
||||||
'application/activity+json' => [
|
'application/activity+json' => [
|
||||||
'namespace' => 'App\Controllers',
|
|
||||||
'controller-method' => 'PodcastController::episodeCollection/$1',
|
'controller-method' => 'PodcastController::episodeCollection/$1',
|
||||||
],
|
],
|
||||||
'application/podcast-activity+json' => [
|
'application/podcast-activity+json' => [
|
||||||
'namespace' => 'App\Controllers',
|
|
||||||
'controller-method' => 'PodcastController::episodeCollection/$1',
|
'controller-method' => 'PodcastController::episodeCollection/$1',
|
||||||
],
|
],
|
||||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
|
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
|
||||||
'namespace' => 'App\Controllers',
|
|
||||||
'controller-method' => 'PodcastController::episodeCollection/$1',
|
'controller-method' => 'PodcastController::episodeCollection/$1',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'filter' => 'allow-cors',
|
|
||||||
]);
|
]);
|
||||||
$routes->group('episodes/(:slug)', static function ($routes): void {
|
$routes->group('episodes/(:slug)', function ($routes): void {
|
||||||
$routes->options('/', 'ActivityPubController::preflight');
|
$routes->options('/', 'ActivityPubController::preflight');
|
||||||
$routes->get('/', 'EpisodeController::index/$1/$2', [
|
$routes->get('/', 'EpisodeController/$1/$2', [
|
||||||
'as' => 'episode',
|
'as' => 'episode',
|
||||||
'alternate-content' => [
|
'alternate-content' => [
|
||||||
'application/activity+json' => [
|
'application/activity+json' => [
|
||||||
'namespace' => 'App\Controllers',
|
|
||||||
'controller-method' => 'EpisodeController::episodeObject/$1/$2',
|
'controller-method' => 'EpisodeController::episodeObject/$1/$2',
|
||||||
],
|
],
|
||||||
'application/podcast-activity+json' => [
|
'application/podcast-activity+json' => [
|
||||||
'namespace' => 'App\Controllers',
|
|
||||||
'controller-method' => 'EpisodeController::episodeObject/$1/$2',
|
'controller-method' => 'EpisodeController::episodeObject/$1/$2',
|
||||||
],
|
],
|
||||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
|
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
|
||||||
'namespace' => 'App\Controllers',
|
|
||||||
'controller-method' => 'EpisodeController::episodeObject/$1/$2',
|
'controller-method' => 'EpisodeController::episodeObject/$1/$2',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'filter' => 'allow-cors',
|
|
||||||
]);
|
|
||||||
$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->options('comments', 'ActivityPubController::preflight');
|
||||||
$routes->get('comments', 'EpisodeController::comments/$1/$2', [
|
$routes->get('comments', 'EpisodeController::comments/$1/$2', [
|
||||||
'as' => 'episode-comments',
|
'as' => 'episode-comments',
|
||||||
'application/activity+json' => [
|
'application/activity+json' => [
|
||||||
'controller-method' => 'EpisodeController::comments/$1/$2',
|
'controller-method' => 'EpisodeController::comments/$1/$2',
|
||||||
],
|
],
|
||||||
|
|
@ -140,27 +751,6 @@ $routes->group('@(:podcastHandle)', static function ($routes): void {
|
||||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
|
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
|
||||||
'controller-method' => 'EpisodeController::comments/$1/$2',
|
'controller-method' => 'EpisodeController::comments/$1/$2',
|
||||||
],
|
],
|
||||||
'filter' => 'allow-cors',
|
|
||||||
]);
|
|
||||||
$routes->options('comments/(:uuid)', 'ActivityPubController::preflight');
|
|
||||||
$routes->get('comments/(:uuid)', 'EpisodeCommentController::view/$1/$2/$3', [
|
|
||||||
'as' => 'episode-comment',
|
|
||||||
'application/activity+json' => [
|
|
||||||
'controller-method' => 'EpisodeController::commentObject/$1/$2',
|
|
||||||
],
|
|
||||||
'application/podcast-activity+json' => [
|
|
||||||
'controller-method' => 'EpisodeController::commentObject/$1/$2',
|
|
||||||
],
|
|
||||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
|
|
||||||
'controller-method' => 'EpisodeController::commentObject/$1/$2',
|
|
||||||
],
|
|
||||||
'filter' => 'allow-cors',
|
|
||||||
]);
|
|
||||||
$routes->get('comments/(:uuid)/replies', 'EpisodeCommentController::replies/$1/$2/$3', [
|
|
||||||
'as' => 'episode-comment-replies',
|
|
||||||
]);
|
|
||||||
$routes->post('comments/(:uuid)/like', 'EpisodeCommentController::likeAction/$1/$2/$3', [
|
|
||||||
'as' => 'episode-comment-attempt-like',
|
|
||||||
]);
|
]);
|
||||||
$routes->get('oembed.json', 'EpisodeController::oembedJSON/$1/$2', [
|
$routes->get('oembed.json', 'EpisodeController::oembedJSON/$1/$2', [
|
||||||
'as' => 'episode-oembed-json',
|
'as' => 'episode-oembed-json',
|
||||||
|
|
@ -168,139 +758,144 @@ $routes->group('@(:podcastHandle)', static function ($routes): void {
|
||||||
$routes->get('oembed.xml', 'EpisodeController::oembedXML/$1/$2', [
|
$routes->get('oembed.xml', 'EpisodeController::oembedXML/$1/$2', [
|
||||||
'as' => 'episode-oembed-xml',
|
'as' => 'episode-oembed-xml',
|
||||||
]);
|
]);
|
||||||
$routes->group('embed', static function ($routes): void {
|
$routes->group('embeddable-player', function ($routes): void {
|
||||||
$routes->get('/', 'EpisodeController::embed/$1/$2', [
|
$routes->get('/', 'EpisodeController::embeddablePlayer/$1/$2', [
|
||||||
'as' => 'embed',
|
'as' => 'embeddable-player',
|
||||||
]);
|
]);
|
||||||
$routes->get('(:embedTheme)', 'EpisodeController::embed/$1/$2/$3', [
|
$routes->get(
|
||||||
'as' => 'embed-theme',
|
'(:embeddablePlayerTheme)',
|
||||||
],);
|
'EpisodeController::embeddablePlayer/$1/$2/$3',
|
||||||
|
[
|
||||||
|
'as' => 'embeddable-player-theme',
|
||||||
|
],
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
$routes->head('feed.xml', 'FeedController::index/$1', [
|
|
||||||
'as' => 'podcast-rss-feed',
|
$routes->head('feed.xml', 'FeedController/$1', [
|
||||||
|
'as' => 'podcast_feed',
|
||||||
]);
|
]);
|
||||||
$routes->get('feed.xml', 'FeedController::index/$1', [
|
$routes->get('feed.xml', 'FeedController/$1', [
|
||||||
'as' => 'podcast-rss-feed',
|
'as' => 'podcast_feed',
|
||||||
]);
|
]);
|
||||||
$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
|
// Other pages
|
||||||
$routes->get('/credits', 'CreditsController', [
|
$routes->get('/credits', 'CreditsController', [
|
||||||
'as' => 'credits',
|
'as' => 'credits',
|
||||||
]);
|
]);
|
||||||
$routes->get('/map', 'MapController', [
|
$routes->get('/map', 'MapMarkerController', [
|
||||||
'as' => 'map',
|
'as' => 'map',
|
||||||
]);
|
]);
|
||||||
$routes->get('/episodes-markers', 'MapController::getEpisodesMarkers', [
|
$routes->get('/episodes-markers', 'MapMarkerController::getEpisodesMarkers', [
|
||||||
'as' => 'episodes-markers',
|
'as' => 'episodes-markers',
|
||||||
]);
|
]);
|
||||||
$routes->get('/pages/(:slug)', 'PageController::index/$1', [
|
$routes->get('/pages/(:slug)', 'PageController/$1', [
|
||||||
'as' => 'page',
|
'as' => 'page',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// interacting as an actor
|
||||||
|
$routes->post('interact-as-actor', 'AuthController::attemptInteractAsActor', [
|
||||||
|
'as' => 'interact-as-actor',
|
||||||
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overwriting Fediverse routes file
|
* Overwriting ActivityPub routes file
|
||||||
*/
|
*/
|
||||||
$routes->group('@(:podcastHandle)', static function ($routes): void {
|
$routes->group('@(:podcastName)', function ($routes): void {
|
||||||
$routes->post('posts/new', 'PostController::createAction/$1', [
|
$routes->post('statuses/new', 'StatusController::attemptCreate/$1', [
|
||||||
'as' => 'post-attempt-create',
|
'as' => 'status-attempt-create',
|
||||||
'filter' => 'permission:podcast$1.manage-publications',
|
'filter' => 'permission:podcast-manage_publications',
|
||||||
]);
|
]);
|
||||||
// Post
|
// Status
|
||||||
$routes->group('posts/(:uuid)', static function ($routes): void {
|
$routes->group('statuses/(:uuid)', function ($routes): void {
|
||||||
$routes->options('/', 'ActivityPubController::preflight');
|
$routes->options('/', 'ActivityPubController::preflight');
|
||||||
$routes->get('/', 'PostController::view/$1/$2', [
|
$routes->get('/', 'StatusController::view/$1/$2', [
|
||||||
'as' => 'post',
|
'as' => 'status',
|
||||||
'alternate-content' => [
|
'alternate-content' => [
|
||||||
'application/activity+json' => [
|
'application/activity+json' => [
|
||||||
'namespace' => 'Modules\Fediverse\Controllers',
|
'namespace' => 'ActivityPub\Controllers',
|
||||||
'controller-method' => 'PostController::index/$2',
|
'controller-method' => 'StatusController/$2',
|
||||||
],
|
],
|
||||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
|
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
|
||||||
'namespace' => 'Modules\Fediverse\Controllers',
|
'namespace' => 'ActivityPub\Controllers',
|
||||||
'controller-method' => 'PostController::index/$2',
|
'controller-method' => 'StatusController/$2',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'filter' => 'allow-cors',
|
|
||||||
]);
|
]);
|
||||||
$routes->options('replies', 'ActivityPubController::preflight');
|
$routes->options('replies', 'ActivityPubController::preflight');
|
||||||
$routes->get('replies', 'PostController::index/$1/$2', [
|
$routes->get('replies', 'StatusController/$1/$2', [
|
||||||
'as' => 'post-replies',
|
'as' => 'status-replies',
|
||||||
'alternate-content' => [
|
'alternate-content' => [
|
||||||
'application/activity+json' => [
|
'application/activity+json' => [
|
||||||
'namespace' => 'Modules\Fediverse\Controllers',
|
'namespace' => 'ActivityPub\Controllers',
|
||||||
'controller-method' => 'PostController::replies/$2',
|
'controller-method' => 'StatusController::replies/$2',
|
||||||
],
|
],
|
||||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
|
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
|
||||||
'namespace' => 'Modules\Fediverse\Controllers',
|
'namespace' => 'ActivityPub\Controllers',
|
||||||
'controller-method' => 'PostController::replies/$2',
|
'controller-method' => 'StatusController::replies/$2',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'filter' => 'allow-cors',
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
$routes->post('action', 'PostController::action/$1/$2', [
|
$routes->post('action', 'StatusController::attemptAction/$1/$2', [
|
||||||
'as' => 'post-attempt-action',
|
'as' => 'status-attempt-action',
|
||||||
'filter' => 'permission:podcast$1.interact-as',
|
'filter' => 'permission:podcast-interact_as',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$routes->post(
|
$routes->post(
|
||||||
'block-actor',
|
'block-actor',
|
||||||
'PostController::blockActorAction/$1/$2',
|
'StatusController::attemptBlockActor/$1/$2',
|
||||||
[
|
[
|
||||||
'as' => 'post-attempt-block-actor',
|
'as' => 'status-attempt-block-actor',
|
||||||
'filter' => 'permission:fediverse.manage-blocks',
|
'filter' => 'permission:fediverse-block_actors',
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
$routes->post(
|
$routes->post(
|
||||||
'block-domain',
|
'block-domain',
|
||||||
'PostController::blockDomainAction/$1/$2',
|
'StatusController::attemptBlockDomain/$1/$2',
|
||||||
[
|
[
|
||||||
'as' => 'post-attempt-block-domain',
|
'as' => 'status-attempt-block-domain',
|
||||||
'filter' => 'permission:fediverse.manage-blocks',
|
'filter' => 'permission:fediverse-block_domains',
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
$routes->post('delete', 'PostController::deleteAction/$1/$2', [
|
$routes->post('delete', 'StatusController::attemptDelete/$1/$2', [
|
||||||
'as' => 'post-attempt-delete',
|
'as' => 'status-attempt-delete',
|
||||||
'filter' => 'permission:podcast$1.manage-publications',
|
'filter' => 'permission:podcast-manage_publications',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$routes->get(
|
$routes->get(
|
||||||
'remote/(:postAction)',
|
'remote/(:statusAction)',
|
||||||
'PostController::remoteActionAction/$1/$2/$3',
|
'StatusController::remoteAction/$1/$2/$3',
|
||||||
[
|
[
|
||||||
'as' => 'post-remote-action',
|
'as' => 'status-remote-action',
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
$routes->get('follow', 'ActorController::followView/$1', [
|
|
||||||
|
$routes->get('follow', 'ActorController::follow/$1', [
|
||||||
'as' => 'follow',
|
'as' => 'follow',
|
||||||
]);
|
]);
|
||||||
$routes->get('outbox', 'ActorController::outbox/$1', [
|
$routes->get('outbox', 'ActorController::outbox/$1', [
|
||||||
'as' => 'outbox',
|
'as' => 'outbox',
|
||||||
'filter' => 'fediverse:verify-activitystream',
|
'filter' => 'activity-pub: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 (file_exists(APPPATH . 'Config/' . ENVIRONMENT . '/Routes.php')) {
|
||||||
|
require APPPATH . 'Config/' . ENVIRONMENT . '/Routes.php';
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,151 +0,0 @@
|
||||||
<?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;
|
|
||||||
}
|
|
||||||
|
|
@ -8,32 +8,12 @@ use CodeIgniter\Config\BaseConfig;
|
||||||
|
|
||||||
class Security extends BaseConfig
|
class Security extends BaseConfig
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* --------------------------------------------------------------------------
|
|
||||||
* CSRF Protection Method
|
|
||||||
* --------------------------------------------------------------------------
|
|
||||||
*
|
|
||||||
* Protection Method for Cross Site Request Forgery protection.
|
|
||||||
*
|
|
||||||
* @var 'cookie'|'session'
|
|
||||||
*/
|
|
||||||
public string $csrfProtection = 'session';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* --------------------------------------------------------------------------
|
|
||||||
* CSRF Token Randomization
|
|
||||||
* --------------------------------------------------------------------------
|
|
||||||
*
|
|
||||||
* Randomize the CSRF Token for added security.
|
|
||||||
*/
|
|
||||||
public bool $tokenRandomize = true;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
* CSRF Token Name
|
* CSRF Token Name
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
*
|
*
|
||||||
* Token name for Cross Site Request Forgery protection.
|
* Token name for Cross Site Request Forgery protection cookie.
|
||||||
*/
|
*/
|
||||||
public string $tokenName = 'csrf_test_name';
|
public string $tokenName = 'csrf_test_name';
|
||||||
|
|
||||||
|
|
@ -42,7 +22,7 @@ class Security extends BaseConfig
|
||||||
* CSRF Header Name
|
* CSRF Header Name
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
*
|
*
|
||||||
* Header name for Cross Site Request Forgery protection.
|
* Token name for Cross Site Request Forgery protection cookie.
|
||||||
*/
|
*/
|
||||||
public string $headerName = 'X-CSRF-TOKEN';
|
public string $headerName = 'X-CSRF-TOKEN';
|
||||||
|
|
||||||
|
|
@ -51,7 +31,7 @@ class Security extends BaseConfig
|
||||||
* CSRF Cookie Name
|
* CSRF Cookie Name
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
*
|
*
|
||||||
* Cookie name for Cross Site Request Forgery protection.
|
* Cookie name for Cross Site Request Forgery protection cookie.
|
||||||
*/
|
*/
|
||||||
public string $cookieName = 'csrf_cookie_name';
|
public string $cookieName = 'csrf_cookie_name';
|
||||||
|
|
||||||
|
|
@ -71,7 +51,7 @@ class Security extends BaseConfig
|
||||||
* CSRF Regenerate
|
* CSRF Regenerate
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
*
|
*
|
||||||
* Regenerate CSRF Token on every submission.
|
* Regenerate CSRF Token on every request.
|
||||||
*/
|
*/
|
||||||
public bool $regenerate = true;
|
public bool $regenerate = true;
|
||||||
|
|
||||||
|
|
@ -80,7 +60,7 @@ class Security extends BaseConfig
|
||||||
* CSRF Redirect
|
* CSRF Redirect
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
*
|
*
|
||||||
* @see https://codeigniter4.github.io/userguide/libraries/security.html#redirection-on-failure
|
* Redirect to previous page with error on failure.
|
||||||
*/
|
*/
|
||||||
public bool $redirect = (ENVIRONMENT === 'production');
|
public bool $redirect = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,20 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Config;
|
namespace Config;
|
||||||
|
|
||||||
|
use App\Authorization\FlatAuthorization;
|
||||||
|
use App\Authorization\GroupModel;
|
||||||
|
use App\Authorization\PermissionModel;
|
||||||
use App\Libraries\Breadcrumb;
|
use App\Libraries\Breadcrumb;
|
||||||
use App\Libraries\HtmlHead;
|
|
||||||
use App\Libraries\Negotiate;
|
use App\Libraries\Negotiate;
|
||||||
use App\Libraries\Router;
|
use App\Libraries\Router;
|
||||||
|
use App\Libraries\Vite;
|
||||||
|
use App\Models\UserModel;
|
||||||
use CodeIgniter\Config\BaseService;
|
use CodeIgniter\Config\BaseService;
|
||||||
use CodeIgniter\HTTP\Negotiate as CodeIgniterHTTPNegotiate;
|
|
||||||
use CodeIgniter\HTTP\Request;
|
use CodeIgniter\HTTP\Request;
|
||||||
use CodeIgniter\HTTP\RequestInterface;
|
use CodeIgniter\HTTP\RequestInterface;
|
||||||
|
use CodeIgniter\Model;
|
||||||
use CodeIgniter\Router\RouteCollectionInterface;
|
use CodeIgniter\Router\RouteCollectionInterface;
|
||||||
use CodeIgniter\Router\Router as CodeIgniterRouter;
|
use Myth\Auth\Models\LoginModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Services Configuration file.
|
* Services Configuration file.
|
||||||
|
|
@ -30,18 +34,20 @@ class Services extends BaseService
|
||||||
/**
|
/**
|
||||||
* The Router class uses a RouteCollection's array of routes, and determines the correct Controller and Method to
|
* The Router class uses a RouteCollection's array of routes, and determines the correct Controller and Method to
|
||||||
* execute.
|
* execute.
|
||||||
|
*
|
||||||
|
* @noRector PHPStan\Reflection\MissingMethodFromReflectionException
|
||||||
*/
|
*/
|
||||||
public static function router(
|
public static function router(
|
||||||
?RouteCollectionInterface $routes = null,
|
?RouteCollectionInterface $routes = null,
|
||||||
?Request $request = null,
|
?Request $request = null,
|
||||||
bool $getShared = true,
|
bool $getShared = true
|
||||||
): CodeIgniterRouter {
|
): Router {
|
||||||
if ($getShared) {
|
if ($getShared) {
|
||||||
return static::getSharedInstance('router', $routes, $request);
|
return static::getSharedInstance('router', $routes, $request);
|
||||||
}
|
}
|
||||||
|
|
||||||
$routes ??= static::routes();
|
$routes = $routes ?? static::routes();
|
||||||
$request ??= static::request();
|
$request = $request ?? static::request();
|
||||||
|
|
||||||
return new Router($routes, $request);
|
return new Router($routes, $request);
|
||||||
}
|
}
|
||||||
|
|
@ -49,20 +55,82 @@ class Services extends BaseService
|
||||||
/**
|
/**
|
||||||
* The Negotiate class provides the content negotiation features for working the request to determine correct
|
* The Negotiate class provides the content negotiation features for working the request to determine correct
|
||||||
* language, encoding, charset, and more.
|
* language, encoding, charset, and more.
|
||||||
|
*
|
||||||
|
* @noRector PHPStan\Reflection\MissingMethodFromReflectionException
|
||||||
*/
|
*/
|
||||||
public static function negotiator(
|
public static function negotiator(?RequestInterface $request = null, bool $getShared = true): Negotiate
|
||||||
?RequestInterface $request = null,
|
{
|
||||||
bool $getShared = true,
|
|
||||||
): CodeIgniterHTTPNegotiate {
|
|
||||||
if ($getShared) {
|
if ($getShared) {
|
||||||
return static::getSharedInstance('negotiator', $request);
|
return static::getSharedInstance('negotiator', $request);
|
||||||
}
|
}
|
||||||
|
|
||||||
$request ??= static::request();
|
$request = $request ?? static::request();
|
||||||
|
|
||||||
return new Negotiate($request);
|
return new Negotiate($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public static function authentication(
|
||||||
|
string $lib = 'local',
|
||||||
|
Model $userModel = null,
|
||||||
|
Model $loginModel = null,
|
||||||
|
bool $getShared = true
|
||||||
|
) {
|
||||||
|
if ($getShared) {
|
||||||
|
return self::getSharedInstance('authentication', $lib, $userModel, $loginModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// config() checks first in app/Config
|
||||||
|
$config = config('Auth');
|
||||||
|
|
||||||
|
$class = $config->authenticationLibs[$lib];
|
||||||
|
|
||||||
|
$instance = new $class($config);
|
||||||
|
|
||||||
|
if ($userModel === null) {
|
||||||
|
$userModel = new UserModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($loginModel === null) {
|
||||||
|
$loginModel = new LoginModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $instance->setUserModel($userModel)
|
||||||
|
->setLoginModel($loginModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public static function authorization(
|
||||||
|
Model $groupModel = null,
|
||||||
|
Model $permissionModel = null,
|
||||||
|
Model $userModel = null,
|
||||||
|
bool $getShared = true
|
||||||
|
) {
|
||||||
|
if ($getShared) {
|
||||||
|
return self::getSharedInstance('authorization', $groupModel, $permissionModel, $userModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($groupModel === null) {
|
||||||
|
$groupModel = new GroupModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($permissionModel === null) {
|
||||||
|
$permissionModel = new PermissionModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
$instance = new FlatAuthorization($groupModel, $permissionModel);
|
||||||
|
|
||||||
|
if ($userModel === null) {
|
||||||
|
$userModel = new UserModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $instance->setUserModel($userModel);
|
||||||
|
}
|
||||||
|
|
||||||
public static function breadcrumb(bool $getShared = true): Breadcrumb
|
public static function breadcrumb(bool $getShared = true): Breadcrumb
|
||||||
{
|
{
|
||||||
if ($getShared) {
|
if ($getShared) {
|
||||||
|
|
@ -72,12 +140,12 @@ class Services extends BaseService
|
||||||
return new Breadcrumb();
|
return new Breadcrumb();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function html_head(bool $getShared = true): HtmlHead
|
public static function vite(bool $getShared = true): Vite
|
||||||
{
|
{
|
||||||
if ($getShared) {
|
if ($getShared) {
|
||||||
return self::getSharedInstance('html_head');
|
return self::getSharedInstance('vite');
|
||||||
}
|
}
|
||||||
|
|
||||||
return new HtmlHead();
|
return new Vite();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
<?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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
<?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
|
* List of toolbar collectors that will be called when Debug Toolbar
|
||||||
* fires up and collects data from.
|
* fires up and collects data from.
|
||||||
*
|
*
|
||||||
* @var list<class-string>
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
public array $collectors = [
|
public array $collectors = [
|
||||||
Timers::class,
|
Timers::class,
|
||||||
|
|
@ -46,16 +46,6 @@ class Toolbar extends BaseConfig
|
||||||
Events::class,
|
Events::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* --------------------------------------------------------------------------
|
|
||||||
* Collect Var Data
|
|
||||||
* --------------------------------------------------------------------------
|
|
||||||
*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
* Max History
|
* Max History
|
||||||
|
|
@ -90,56 +80,4 @@ class Toolbar extends BaseConfig
|
||||||
* `$maxQueries` defines the maximum amount of queries that will be stored.
|
* `$maxQueries` defines the maximum amount of queries that will be stored.
|
||||||
*/
|
*/
|
||||||
public int $maxQueries = 100;
|
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 = [
|
public array $platforms = [
|
||||||
'windows nt 10.0' => 'Windows 10',
|
'windows nt 10.0' => 'Windows 10',
|
||||||
'windows nt 6.3' => 'Windows 8.1',
|
'windows nt 6.3' => 'Windows 8.1',
|
||||||
'windows nt 6.2' => 'Windows 8',
|
'windows nt 6.2' => 'Windows 8',
|
||||||
'windows nt 6.1' => 'Windows 7',
|
'windows nt 6.1' => 'Windows 7',
|
||||||
'windows nt 6.0' => 'Windows Vista',
|
'windows nt 6.0' => 'Windows Vista',
|
||||||
'windows nt 5.2' => 'Windows 2003',
|
'windows nt 5.2' => 'Windows 2003',
|
||||||
'windows nt 5.1' => 'Windows XP',
|
'windows nt 5.1' => 'Windows XP',
|
||||||
'windows nt 5.0' => 'Windows 2000',
|
'windows nt 5.0' => 'Windows 2000',
|
||||||
'windows nt 4.0' => 'Windows NT 4.0',
|
'windows nt 4.0' => 'Windows NT 4.0',
|
||||||
'winnt4.0' => 'Windows NT 4.0',
|
'winnt4.0' => 'Windows NT 4.0',
|
||||||
'winnt 4.0' => 'Windows NT',
|
'winnt 4.0' => 'Windows NT',
|
||||||
'winnt' => 'Windows NT',
|
'winnt' => 'Windows NT',
|
||||||
'windows 98' => 'Windows 98',
|
'windows 98' => 'Windows 98',
|
||||||
'win98' => 'Windows 98',
|
'win98' => 'Windows 98',
|
||||||
'windows 95' => 'Windows 95',
|
'windows 95' => 'Windows 95',
|
||||||
'win95' => 'Windows 95',
|
'win95' => 'Windows 95',
|
||||||
'windows phone' => 'Windows Phone',
|
'windows phone' => 'Windows Phone',
|
||||||
'windows' => 'Unknown Windows OS',
|
'windows' => 'Unknown Windows OS',
|
||||||
'android' => 'Android',
|
'android' => 'Android',
|
||||||
'blackberry' => 'BlackBerry',
|
'blackberry' => 'BlackBerry',
|
||||||
'iphone' => 'iOS',
|
'iphone' => 'iOS',
|
||||||
'ipad' => 'iOS',
|
'ipad' => 'iOS',
|
||||||
'ipod' => 'iOS',
|
'ipod' => 'iOS',
|
||||||
'os x' => 'Mac OS X',
|
'os x' => 'Mac OS X',
|
||||||
'ppc mac' => 'Power PC Mac',
|
'ppc mac' => 'Power PC Mac',
|
||||||
'freebsd' => 'FreeBSD',
|
'freebsd' => 'FreeBSD',
|
||||||
'ppc' => 'Macintosh',
|
'ppc' => 'Macintosh',
|
||||||
'linux' => 'Linux',
|
'linux' => 'Linux',
|
||||||
'debian' => 'Debian',
|
'debian' => 'Debian',
|
||||||
'sunos' => 'Sun Solaris',
|
'sunos' => 'Sun Solaris',
|
||||||
'beos' => 'BeOS',
|
'beos' => 'BeOS',
|
||||||
'apachebench' => 'ApacheBench',
|
'apachebench' => 'ApacheBench',
|
||||||
'aix' => 'AIX',
|
'aix' => 'AIX',
|
||||||
'irix' => 'Irix',
|
'irix' => 'Irix',
|
||||||
'osf' => 'DEC OSF',
|
'osf' => 'DEC OSF',
|
||||||
'hp-ux' => 'HP-UX',
|
'hp-ux' => 'HP-UX',
|
||||||
'netbsd' => 'NetBSD',
|
'netbsd' => 'NetBSD',
|
||||||
'bsdi' => 'BSDi',
|
'bsdi' => 'BSDi',
|
||||||
'openbsd' => 'OpenBSD',
|
'openbsd' => 'OpenBSD',
|
||||||
'gnu' => 'GNU/Linux',
|
'gnu' => 'GNU/Linux',
|
||||||
'unix' => 'Unknown Unix OS',
|
'unix' => 'Unknown Unix OS',
|
||||||
'symbian' => 'Symbian OS',
|
'symbian' => 'Symbian OS',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -81,37 +81,37 @@ class UserAgents extends BaseConfig
|
||||||
* @var array<string, string>
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
public array $browsers = [
|
public array $browsers = [
|
||||||
'OPR' => 'Opera',
|
'OPR' => 'Opera',
|
||||||
'Flock' => 'Flock',
|
'Flock' => 'Flock',
|
||||||
'Edge' => 'Spartan',
|
'Edge' => 'Spartan',
|
||||||
'Edg' => 'Edge',
|
'Edg' => 'Edge',
|
||||||
'Chrome' => 'Chrome',
|
'Chrome' => 'Chrome',
|
||||||
// Opera 10+ always reports Opera/9.80 and appends Version/<real version> to the user agent string
|
// Opera 10+ always reports Opera/9.80 and appends Version/<real version> to the user agent string
|
||||||
'Opera.*?Version' => 'Opera',
|
'Opera.*?Version' => 'Opera',
|
||||||
'Opera' => 'Opera',
|
'Opera' => 'Opera',
|
||||||
'MSIE' => 'Internet Explorer',
|
'MSIE' => 'Internet Explorer',
|
||||||
'Internet Explorer' => 'Internet Explorer',
|
'Internet Explorer' => 'Internet Explorer',
|
||||||
'Trident.* rv' => 'Internet Explorer',
|
'Trident.* rv' => 'Internet Explorer',
|
||||||
'Shiira' => 'Shiira',
|
'Shiira' => 'Shiira',
|
||||||
'Firefox' => 'Firefox',
|
'Firefox' => 'Firefox',
|
||||||
'Chimera' => 'Chimera',
|
'Chimera' => 'Chimera',
|
||||||
'Phoenix' => 'Phoenix',
|
'Phoenix' => 'Phoenix',
|
||||||
'Firebird' => 'Firebird',
|
'Firebird' => 'Firebird',
|
||||||
'Camino' => 'Camino',
|
'Camino' => 'Camino',
|
||||||
'Netscape' => 'Netscape',
|
'Netscape' => 'Netscape',
|
||||||
'OmniWeb' => 'OmniWeb',
|
'OmniWeb' => 'OmniWeb',
|
||||||
'Safari' => 'Safari',
|
'Safari' => 'Safari',
|
||||||
'Mozilla' => 'Mozilla',
|
'Mozilla' => 'Mozilla',
|
||||||
'Konqueror' => 'Konqueror',
|
'Konqueror' => 'Konqueror',
|
||||||
'icab' => 'iCab',
|
'icab' => 'iCab',
|
||||||
'Lynx' => 'Lynx',
|
'Lynx' => 'Lynx',
|
||||||
'Links' => 'Links',
|
'Links' => 'Links',
|
||||||
'hotjava' => 'HotJava',
|
'hotjava' => 'HotJava',
|
||||||
'amaya' => 'Amaya',
|
'amaya' => 'Amaya',
|
||||||
'IBrowse' => 'IBrowse',
|
'IBrowse' => 'IBrowse',
|
||||||
'Maxthon' => 'Maxthon',
|
'Maxthon' => 'Maxthon',
|
||||||
'Ubuntu' => 'Ubuntu Web Browser',
|
'Ubuntu' => 'Ubuntu Web Browser',
|
||||||
'Vivaldi' => 'Vivaldi',
|
'Vivaldi' => 'Vivaldi',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -139,86 +139,86 @@ class UserAgents extends BaseConfig
|
||||||
// 'motorola' => 'Motorola'
|
// 'motorola' => 'Motorola'
|
||||||
|
|
||||||
// Phones and Manufacturers
|
// Phones and Manufacturers
|
||||||
'motorola' => 'Motorola',
|
'motorola' => 'Motorola',
|
||||||
'nokia' => 'Nokia',
|
'nokia' => 'Nokia',
|
||||||
'palm' => 'Palm',
|
'palm' => 'Palm',
|
||||||
'iphone' => 'Apple iPhone',
|
'iphone' => 'Apple iPhone',
|
||||||
'ipad' => 'iPad',
|
'ipad' => 'iPad',
|
||||||
'ipod' => 'Apple iPod Touch',
|
'ipod' => 'Apple iPod Touch',
|
||||||
'sony' => 'Sony Ericsson',
|
'sony' => 'Sony Ericsson',
|
||||||
'ericsson' => 'Sony Ericsson',
|
'ericsson' => 'Sony Ericsson',
|
||||||
'blackberry' => 'BlackBerry',
|
'blackberry' => 'BlackBerry',
|
||||||
'cocoon' => 'O2 Cocoon',
|
'cocoon' => 'O2 Cocoon',
|
||||||
'blazer' => 'Treo',
|
'blazer' => 'Treo',
|
||||||
'lg' => 'LG',
|
'lg' => 'LG',
|
||||||
'amoi' => 'Amoi',
|
'amoi' => 'Amoi',
|
||||||
'xda' => 'XDA',
|
'xda' => 'XDA',
|
||||||
'mda' => 'MDA',
|
'mda' => 'MDA',
|
||||||
'vario' => 'Vario',
|
'vario' => 'Vario',
|
||||||
'htc' => 'HTC',
|
'htc' => 'HTC',
|
||||||
'samsung' => 'Samsung',
|
'samsung' => 'Samsung',
|
||||||
'sharp' => 'Sharp',
|
'sharp' => 'Sharp',
|
||||||
'sie-' => 'Siemens',
|
'sie-' => 'Siemens',
|
||||||
'alcatel' => 'Alcatel',
|
'alcatel' => 'Alcatel',
|
||||||
'benq' => 'BenQ',
|
'benq' => 'BenQ',
|
||||||
'ipaq' => 'HP iPaq',
|
'ipaq' => 'HP iPaq',
|
||||||
'mot-' => 'Motorola',
|
'mot-' => 'Motorola',
|
||||||
'playstation portable' => 'PlayStation Portable',
|
'playstation portable' => 'PlayStation Portable',
|
||||||
'playstation 3' => 'PlayStation 3',
|
'playstation 3' => 'PlayStation 3',
|
||||||
'playstation vita' => 'PlayStation Vita',
|
'playstation vita' => 'PlayStation Vita',
|
||||||
'hiptop' => 'Danger Hiptop',
|
'hiptop' => 'Danger Hiptop',
|
||||||
'nec-' => 'NEC',
|
'nec-' => 'NEC',
|
||||||
'panasonic' => 'Panasonic',
|
'panasonic' => 'Panasonic',
|
||||||
'philips' => 'Philips',
|
'philips' => 'Philips',
|
||||||
'sagem' => 'Sagem',
|
'sagem' => 'Sagem',
|
||||||
'sanyo' => 'Sanyo',
|
'sanyo' => 'Sanyo',
|
||||||
'spv' => 'SPV',
|
'spv' => 'SPV',
|
||||||
'zte' => 'ZTE',
|
'zte' => 'ZTE',
|
||||||
'sendo' => 'Sendo',
|
'sendo' => 'Sendo',
|
||||||
'nintendo dsi' => 'Nintendo DSi',
|
'nintendo dsi' => 'Nintendo DSi',
|
||||||
'nintendo ds' => 'Nintendo DS',
|
'nintendo ds' => 'Nintendo DS',
|
||||||
'nintendo 3ds' => 'Nintendo 3DS',
|
'nintendo 3ds' => 'Nintendo 3DS',
|
||||||
'wii' => 'Nintendo Wii',
|
'wii' => 'Nintendo Wii',
|
||||||
'open web' => 'Open Web',
|
'open web' => 'Open Web',
|
||||||
'openweb' => 'OpenWeb',
|
'openweb' => 'OpenWeb',
|
||||||
|
|
||||||
// Operating Systems
|
// Operating Systems
|
||||||
'android' => 'Android',
|
'android' => 'Android',
|
||||||
'symbian' => 'Symbian',
|
'symbian' => 'Symbian',
|
||||||
'SymbianOS' => 'SymbianOS',
|
'SymbianOS' => 'SymbianOS',
|
||||||
'elaine' => 'Palm',
|
'elaine' => 'Palm',
|
||||||
'series60' => 'Symbian S60',
|
'series60' => 'Symbian S60',
|
||||||
'windows ce' => 'Windows CE',
|
'windows ce' => 'Windows CE',
|
||||||
|
|
||||||
// Browsers
|
// Browsers
|
||||||
'obigo' => 'Obigo',
|
'obigo' => 'Obigo',
|
||||||
'netfront' => 'Netfront Browser',
|
'netfront' => 'Netfront Browser',
|
||||||
'openwave' => 'Openwave Browser',
|
'openwave' => 'Openwave Browser',
|
||||||
'mobilexplorer' => 'Mobile Explorer',
|
'mobilexplorer' => 'Mobile Explorer',
|
||||||
'operamini' => 'Opera Mini',
|
'operamini' => 'Opera Mini',
|
||||||
'opera mini' => 'Opera Mini',
|
'opera mini' => 'Opera Mini',
|
||||||
'opera mobi' => 'Opera Mobile',
|
'opera mobi' => 'Opera Mobile',
|
||||||
'fennec' => 'Firefox Mobile',
|
'fennec' => 'Firefox Mobile',
|
||||||
|
|
||||||
// Other
|
// Other
|
||||||
'digital paths' => 'Digital Paths',
|
'digital paths' => 'Digital Paths',
|
||||||
'avantgo' => 'AvantGo',
|
'avantgo' => 'AvantGo',
|
||||||
'xiino' => 'Xiino',
|
'xiino' => 'Xiino',
|
||||||
'novarra' => 'Novarra Transcoder',
|
'novarra' => 'Novarra Transcoder',
|
||||||
'vodafone' => 'Vodafone',
|
'vodafone' => 'Vodafone',
|
||||||
'docomo' => 'NTT DoCoMo',
|
'docomo' => 'NTT DoCoMo',
|
||||||
'o2' => 'O2',
|
'o2' => 'O2',
|
||||||
|
|
||||||
// Fallback
|
// Fallback
|
||||||
'mobile' => 'Generic Mobile',
|
'mobile' => 'Generic Mobile',
|
||||||
'wireless' => 'Generic Mobile',
|
'wireless' => 'Generic Mobile',
|
||||||
'j2me' => 'Generic Mobile',
|
'j2me' => 'Generic Mobile',
|
||||||
'midp' => 'Generic Mobile',
|
'midp' => 'Generic Mobile',
|
||||||
'cldc' => 'Generic Mobile',
|
'cldc' => 'Generic Mobile',
|
||||||
'up.link' => 'Generic Mobile',
|
'up.link' => 'Generic Mobile',
|
||||||
'up.browser' => 'Generic Mobile',
|
'up.browser' => 'Generic Mobile',
|
||||||
'smartphone' => 'Generic Mobile',
|
'smartphone' => 'Generic Mobile',
|
||||||
'cellphone' => 'Generic Mobile',
|
'cellphone' => 'Generic Mobile',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -231,34 +231,24 @@ class UserAgents extends BaseConfig
|
||||||
* @var array<string, string>
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
public array $robots = [
|
public array $robots = [
|
||||||
'googlebot' => 'Googlebot',
|
'googlebot' => 'Googlebot',
|
||||||
'google-pagerenderer' => 'Google Page Renderer',
|
'msnbot' => 'MSNBot',
|
||||||
'google-read-aloud' => 'Google Read Aloud',
|
'baiduspider' => 'Baiduspider',
|
||||||
'google-safety' => 'Google Safety Bot',
|
'bingbot' => 'Bing',
|
||||||
'msnbot' => 'MSNBot',
|
'slurp' => 'Inktomi Slurp',
|
||||||
'baiduspider' => 'Baiduspider',
|
'yahoo' => 'Yahoo',
|
||||||
'bingbot' => 'Bing',
|
'ask jeeves' => 'Ask Jeeves',
|
||||||
'bingpreview' => 'BingPreview',
|
'fastcrawler' => 'FastCrawler',
|
||||||
'slurp' => 'Inktomi Slurp',
|
'infoseek' => 'InfoSeek Robot 1.0',
|
||||||
'yahoo' => 'Yahoo',
|
'lycos' => 'Lycos',
|
||||||
'ask jeeves' => 'Ask Jeeves',
|
'yandex' => 'YandexBot',
|
||||||
'fastcrawler' => 'FastCrawler',
|
|
||||||
'infoseek' => 'InfoSeek Robot 1.0',
|
|
||||||
'lycos' => 'Lycos',
|
|
||||||
'yandex' => 'YandexBot',
|
|
||||||
'mediapartners-google' => 'MediaPartners Google',
|
'mediapartners-google' => 'MediaPartners Google',
|
||||||
'CRAZYWEBCRAWLER' => 'Crazy Webcrawler',
|
'CRAZYWEBCRAWLER' => 'Crazy Webcrawler',
|
||||||
'adsbot-google' => 'AdsBot Google',
|
'adsbot-google' => 'AdsBot Google',
|
||||||
'feedfetcher-google' => 'Feedfetcher Google',
|
'feedfetcher-google' => 'Feedfetcher Google',
|
||||||
'curious george' => 'Curious George',
|
'curious george' => 'Curious George',
|
||||||
'ia_archiver' => 'Alexa Crawler',
|
'ia_archiver' => 'Alexa Crawler',
|
||||||
'MJ12bot' => 'Majestic-12',
|
'MJ12bot' => 'Majestic-12',
|
||||||
'Uptimebot' => 'Uptimebot',
|
'Uptimebot' => 'Uptimebot',
|
||||||
'duckduckbot' => 'DuckDuckBot',
|
|
||||||
'sogou' => 'Sogou Spider',
|
|
||||||
'exabot' => 'Exabot',
|
|
||||||
'bot' => 'Generic Bot',
|
|
||||||
'crawler' => 'Generic Crawler',
|
|
||||||
'spider' => 'Generic Spider',
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,27 +5,28 @@ declare(strict_types=1);
|
||||||
namespace Config;
|
namespace Config;
|
||||||
|
|
||||||
use App\Validation\FileRules as AppFileRules;
|
use App\Validation\FileRules as AppFileRules;
|
||||||
use App\Validation\OtherRules;
|
use App\Validation\Rules as AppRules;
|
||||||
use CodeIgniter\Config\BaseConfig;
|
use CodeIgniter\Validation\CreditCardRules;
|
||||||
use CodeIgniter\Validation\StrictRules\CreditCardRules;
|
use CodeIgniter\Validation\FileRules;
|
||||||
use CodeIgniter\Validation\StrictRules\FileRules;
|
use CodeIgniter\Validation\FormatRules;
|
||||||
use CodeIgniter\Validation\StrictRules\FormatRules;
|
use CodeIgniter\Validation\Rules;
|
||||||
use CodeIgniter\Validation\StrictRules\Rules;
|
use Myth\Auth\Authentication\Passwords\ValidationRules as PasswordRules;
|
||||||
|
|
||||||
class Validation extends BaseConfig
|
class Validation
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Stores the classes that contain the rules that are available.
|
* Stores the classes that contain the rules that are available.
|
||||||
*
|
*
|
||||||
* @var list<string>
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
public array $ruleSets = [
|
public array $ruleSets = [
|
||||||
Rules::class,
|
Rules::class,
|
||||||
FormatRules::class,
|
FormatRules::class,
|
||||||
FileRules::class,
|
FileRules::class,
|
||||||
CreditCardRules::class,
|
CreditCardRules::class,
|
||||||
|
AppRules::class,
|
||||||
AppFileRules::class,
|
AppFileRules::class,
|
||||||
OtherRules::class,
|
PasswordRules::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -34,7 +35,7 @@ class Validation extends BaseConfig
|
||||||
* @var array<string, string>
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
public array $templates = [
|
public array $templates = [
|
||||||
'list' => 'CodeIgniter\Validation\Views\list',
|
'list' => 'CodeIgniter\Validation\Views\list',
|
||||||
'single' => 'CodeIgniter\Validation\Views\single',
|
'single' => 'CodeIgniter\Validation\Views\single',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,7 @@ declare(strict_types=1);
|
||||||
namespace Config;
|
namespace Config;
|
||||||
|
|
||||||
use CodeIgniter\Config\View as BaseView;
|
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
|
class View extends BaseView
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
@ -31,8 +25,7 @@ class View extends BaseView
|
||||||
*
|
*
|
||||||
* Examples: { title|esc(js) } { created_on|date(Y-m-d)|esc(attr) }
|
* Examples: { title|esc(js) } { created_on|date(Y-m-d)|esc(attr) }
|
||||||
*
|
*
|
||||||
* @var array<string, string>
|
* @var string[]
|
||||||
* @phpstan-var array<string, parser_callable_string>
|
|
||||||
*/
|
*/
|
||||||
public $filters = [];
|
public $filters = [];
|
||||||
|
|
||||||
|
|
@ -40,35 +33,7 @@ class View extends BaseView
|
||||||
* Parser Plugins provide a way to extend the functionality provided by the core Parser by creating aliases that
|
* 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.
|
* will be replaced with any callable. Can be single or tag pair.
|
||||||
*
|
*
|
||||||
* @var array<string, callable|list<string>|string>
|
* @var string[]
|
||||||
* @phpstan-var array<string, list<parser_callable_string>|parser_callable_string|parser_callable>
|
|
||||||
*/
|
*/
|
||||||
public $plugins = [];
|
public $plugins = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* View Decorators are class methods that will be run in sequence to have a chance to alter the generated output
|
|
||||||
* just prior to caching the results.
|
|
||||||
*
|
|
||||||
* All classes must implement CodeIgniter\View\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';
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Config;
|
|
||||||
|
|
||||||
use ViewComponents\Config\ViewComponents as ViewComponentsConfig;
|
|
||||||
|
|
||||||
class ViewComponents extends ViewComponentsConfig
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var string[]
|
|
||||||
*/
|
|
||||||
public array $lookupPaths = [
|
|
||||||
ROOTPATH . 'themes/cp_app/',
|
|
||||||
ROOTPATH . 'themes/cp_admin/',
|
|
||||||
ROOTPATH . 'themes/cp_auth/',
|
|
||||||
ROOTPATH . 'themes/cp_install/',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
<?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'],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
<?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;
|
|
||||||
}
|
|
||||||
|
|
@ -3,15 +3,28 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @copyright 2021 Ad Aures
|
* @copyright 2021 Podlibre
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
use Modules\Fediverse\Controllers\ActivityPubController as FediverseActivityPubController;
|
use CodeIgniter\Controller;
|
||||||
|
use CodeIgniter\HTTP\Response;
|
||||||
|
|
||||||
class ActivityPubController extends FediverseActivityPubController
|
class ActivityPubController extends Controller
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @noRector ReturnTypeDeclarationRector
|
||||||
|
*/
|
||||||
|
public function preflight(): Response
|
||||||
|
{
|
||||||
|
return $this->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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,37 +3,46 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @copyright 2020 Ad Aures
|
* @copyright 2020 Podlibre
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
use Modules\Analytics\AnalyticsTrait;
|
use ActivityPub\Controllers\ActorController as ActivityPubActorController;
|
||||||
use Modules\Fediverse\Controllers\ActorController as FediverseActorController;
|
use Analytics\AnalyticsTrait;
|
||||||
|
|
||||||
class ActorController extends FediverseActorController
|
class ActorController extends ActivityPubActorController
|
||||||
{
|
{
|
||||||
use AnalyticsTrait;
|
use AnalyticsTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var list<string>
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
protected $helpers = ['svg', 'components', 'misc', 'seo'];
|
protected $helpers = ['auth', 'svg', 'components', 'misc'];
|
||||||
|
|
||||||
public function followView(): string
|
public function follow(): string
|
||||||
{
|
{
|
||||||
// @phpstan-ignore-next-line
|
// Prevent analytics hit when authenticated
|
||||||
$this->registerPodcastWebpageHit($this->actor->podcast->id);
|
if (! can_user_interact()) {
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
$this->registerPodcastWebpageHit($this->actor->podcast->id);
|
||||||
|
}
|
||||||
|
|
||||||
helper(['form', 'components', 'svg']);
|
$cacheName = "page_podcast-{$this->actor->username}_follow";
|
||||||
// @phpstan-ignore-next-line
|
if (! ($cachedView = cache($cacheName))) {
|
||||||
set_follow_metatags($this->actor);
|
helper(['form', 'components', 'svg']);
|
||||||
$data = [
|
$data = [
|
||||||
'actor' => $this->actor,
|
'actor' => $this->actor,
|
||||||
];
|
];
|
||||||
|
|
||||||
return view('podcast/follow', $data);
|
return view('podcast/follow', $data, [
|
||||||
|
'cache' => DECADE,
|
||||||
|
'cache_name' => $cacheName,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $cachedView;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
42
app/Controllers/Admin/BaseController.php
Normal file
42
app/Controllers/Admin/BaseController.php
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Controllers\Admin;
|
||||||
|
|
||||||
|
use CodeIgniter\Controller;
|
||||||
|
use CodeIgniter\HTTP\RequestInterface;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* For security be sure to declare any new methods as protected or private.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class BaseController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* An array of helpers to be loaded automatically upon class instantiation. These helpers will be available to all
|
||||||
|
* other controllers that extend BaseController.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $helpers = ['auth', 'breadcrumb', 'svg', 'components', 'misc'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public function initController(
|
||||||
|
RequestInterface $request,
|
||||||
|
ResponseInterface $response,
|
||||||
|
LoggerInterface $logger
|
||||||
|
): void {
|
||||||
|
// Do Not Edit This Line
|
||||||
|
parent::initController($request, $response, $logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
199
app/Controllers/Admin/ContributorController.php
Normal file
199
app/Controllers/Admin/ContributorController.php
Normal file
|
|
@ -0,0 +1,199 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Authorization\GroupModel;
|
||||||
|
use App\Entities\Podcast;
|
||||||
|
use App\Entities\User;
|
||||||
|
use App\Models\PodcastModel;
|
||||||
|
use App\Models\UserModel;
|
||||||
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||||
|
use CodeIgniter\HTTP\RedirectResponse;
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class ContributorController extends BaseController
|
||||||
|
{
|
||||||
|
protected Podcast $podcast;
|
||||||
|
|
||||||
|
protected ?User $user;
|
||||||
|
|
||||||
|
public function _remap(string $method, string ...$params): mixed
|
||||||
|
{
|
||||||
|
if (count($params) === 0) {
|
||||||
|
throw PageNotFoundException::forPageNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($podcast = (new PodcastModel())->getPodcastById((int) $params[0])) === null) {
|
||||||
|
throw PageNotFoundException::forPageNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->podcast = $podcast;
|
||||||
|
|
||||||
|
if (count($params) <= 1) {
|
||||||
|
return $this->{$method}();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($this->user = (new UserModel())->getPodcastContributor((int) $params[1], (int) $params[0])) !== null) {
|
||||||
|
return $this->{$method}();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw PageNotFoundException::forPageNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function list(): string
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
]);
|
||||||
|
return view('admin/contributor/list', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(): string
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'contributor' => (new UserModel())->getPodcastContributor($this->user->id, $this->podcast->id),
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
1 => $this->user->username,
|
||||||
|
]);
|
||||||
|
return view('admin/contributor/view', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add(): string
|
||||||
|
{
|
||||||
|
helper('form');
|
||||||
|
|
||||||
|
$users = (new UserModel())->findAll();
|
||||||
|
$userOptions = array_reduce(
|
||||||
|
$users,
|
||||||
|
function ($result, $user) {
|
||||||
|
$result[$user->id] = $user->username;
|
||||||
|
return $result;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
$roles = (new GroupModel())->getContributorRoles();
|
||||||
|
$roleOptions = array_reduce(
|
||||||
|
$roles,
|
||||||
|
function ($result, $role) {
|
||||||
|
$result[$role->id] = lang('Contributor.roles.' . $role->name);
|
||||||
|
return $result;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
'userOptions' => $userOptions,
|
||||||
|
'roleOptions' => $roleOptions,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
]);
|
||||||
|
return view('admin/contributor/add', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptAdd(): RedirectResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
(new PodcastModel())->addPodcastContributor(
|
||||||
|
(int) $this->request->getPost('user'),
|
||||||
|
$this->podcast->id,
|
||||||
|
(int) $this->request->getPost('role'),
|
||||||
|
);
|
||||||
|
} catch (Exception) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', [lang('Contributor.messages.alreadyAddedError')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('contributor-list', [$this->podcast->id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(): string
|
||||||
|
{
|
||||||
|
helper('form');
|
||||||
|
|
||||||
|
$roles = (new GroupModel())->getContributorRoles();
|
||||||
|
$roleOptions = array_reduce(
|
||||||
|
$roles,
|
||||||
|
function ($result, $role) {
|
||||||
|
$result[$role->id] = lang('Contributor.roles.' . $role->name);
|
||||||
|
return $result;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
'user' => $this->user,
|
||||||
|
'contributorGroupId' => (new PodcastModel())->getContributorGroupId(
|
||||||
|
$this->user->id,
|
||||||
|
$this->podcast->id,
|
||||||
|
),
|
||||||
|
'roleOptions' => $roleOptions,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
1 => $this->user->username,
|
||||||
|
]);
|
||||||
|
return view('admin/contributor/edit', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptEdit(): RedirectResponse
|
||||||
|
{
|
||||||
|
(new PodcastModel())->updatePodcastContributor(
|
||||||
|
$this->user->id,
|
||||||
|
$this->podcast->id,
|
||||||
|
(int) $this->request->getPost('role'),
|
||||||
|
);
|
||||||
|
|
||||||
|
return redirect()->route('contributor-list', [$this->podcast->id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remove(): RedirectResponse
|
||||||
|
{
|
||||||
|
if ($this->podcast->created_by === $this->user->id) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('errors', [lang('Contributor.messages.removeOwnerContributorError')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$podcastModel = new PodcastModel();
|
||||||
|
if (
|
||||||
|
! $podcastModel->removePodcastContributor($this->user->id, $this->podcast->id)
|
||||||
|
) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('errors', $podcastModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with(
|
||||||
|
'message',
|
||||||
|
lang('Contributor.messages.removeContributorSuccess', [
|
||||||
|
'username' => $this->user->username,
|
||||||
|
'podcastTitle' => $this->podcast->title,
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
785
app/Controllers/Admin/EpisodeController.php
Normal file
785
app/Controllers/Admin/EpisodeController.php
Normal file
|
|
@ -0,0 +1,785 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Entities\Episode;
|
||||||
|
use App\Entities\Image;
|
||||||
|
use App\Entities\Location;
|
||||||
|
use App\Entities\Podcast;
|
||||||
|
use App\Entities\Status;
|
||||||
|
use App\Models\EpisodeModel;
|
||||||
|
use App\Models\PodcastModel;
|
||||||
|
use App\Models\SoundbiteModel;
|
||||||
|
use App\Models\StatusModel;
|
||||||
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||||
|
use CodeIgniter\HTTP\RedirectResponse;
|
||||||
|
use CodeIgniter\I18n\Time;
|
||||||
|
|
||||||
|
class EpisodeController extends BaseController
|
||||||
|
{
|
||||||
|
protected Podcast $podcast;
|
||||||
|
|
||||||
|
protected Episode $episode;
|
||||||
|
|
||||||
|
public function _remap(string $method, string ...$params): mixed
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
($podcast = (new PodcastModel())->getPodcastById((int) $params[0])) === null
|
||||||
|
) {
|
||||||
|
throw PageNotFoundException::forPageNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->podcast = $podcast;
|
||||||
|
|
||||||
|
if (count($params) > 1) {
|
||||||
|
if (
|
||||||
|
! ($episode = (new EpisodeModel())
|
||||||
|
->where([
|
||||||
|
'id' => $params[1],
|
||||||
|
'podcast_id' => $params[0],
|
||||||
|
])
|
||||||
|
->first())
|
||||||
|
) {
|
||||||
|
throw PageNotFoundException::forPageNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->episode = $episode;
|
||||||
|
|
||||||
|
unset($params[1]);
|
||||||
|
unset($params[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->{$method}(...$params);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function list(): string
|
||||||
|
{
|
||||||
|
$episodes = (new EpisodeModel())
|
||||||
|
->where('podcast_id', $this->podcast->id)
|
||||||
|
->orderBy('created_at', 'desc');
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
'episodes' => $episodes->paginate(10),
|
||||||
|
'pager' => $episodes->pager,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
]);
|
||||||
|
return view('admin/episode/list', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(): string
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
'episode' => $this->episode,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
1 => $this->episode->title,
|
||||||
|
]);
|
||||||
|
return view('admin/episode/view', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(): string
|
||||||
|
{
|
||||||
|
helper(['form']);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
]);
|
||||||
|
return view('admin/episode/create', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptCreate(): RedirectResponse
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'audio_file' => 'uploaded[audio_file]|ext_in[audio_file,mp3,m4a]',
|
||||||
|
'image' =>
|
||||||
|
'is_image[image]|ext_in[image,jpg,png]|min_dims[image,1400,1400]|is_image_squared[image]',
|
||||||
|
'transcript_file' =>
|
||||||
|
'ext_in[transcript,txt,html,srt,json]|permit_empty',
|
||||||
|
'chapters_file' => 'ext_in[chapters,json]|permit_empty',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$image = null;
|
||||||
|
$imageFile = $this->request->getFile('image');
|
||||||
|
if ($imageFile !== null && $imageFile->isValid()) {
|
||||||
|
$image = new Image($imageFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
$newEpisode = new Episode([
|
||||||
|
'podcast_id' => $this->podcast->id,
|
||||||
|
'title' => $this->request->getPost('title'),
|
||||||
|
'slug' => $this->request->getPost('slug'),
|
||||||
|
'guid' => null,
|
||||||
|
'audio_file' => $this->request->getFile('audio_file'),
|
||||||
|
'description_markdown' => $this->request->getPost('description'),
|
||||||
|
'image' => $image,
|
||||||
|
'location' => $this->request->getPost('location_name') === '' ? null : new Location($this->request->getPost(
|
||||||
|
'location_name'
|
||||||
|
)),
|
||||||
|
'transcript' => $this->request->getFile('transcript'),
|
||||||
|
'chapters' => $this->request->getFile('chapters'),
|
||||||
|
'parental_advisory' =>
|
||||||
|
$this->request->getPost('parental_advisory') !== 'undefined'
|
||||||
|
? $this->request->getPost('parental_advisory')
|
||||||
|
: null,
|
||||||
|
'number' => $this->request->getPost('episode_number')
|
||||||
|
? $this->request->getPost('episode_number')
|
||||||
|
: null,
|
||||||
|
'season_number' => $this->request->getPost('season_number')
|
||||||
|
? $this->request->getPost('season_number')
|
||||||
|
: null,
|
||||||
|
'type' => $this->request->getPost('type'),
|
||||||
|
'is_blocked' => $this->request->getPost('block') === 'yes',
|
||||||
|
'custom_rss_string' => $this->request->getPost('custom_rss'),
|
||||||
|
'created_by' => user_id(),
|
||||||
|
'updated_by' => user_id(),
|
||||||
|
'published_at' => null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$transcriptChoice = $this->request->getPost('transcript-choice');
|
||||||
|
if (
|
||||||
|
$transcriptChoice === 'upload-file'
|
||||||
|
&& ($transcriptFile = $this->request->getFile('transcript_file'))
|
||||||
|
&& $transcriptFile->isValid()
|
||||||
|
) {
|
||||||
|
$newEpisode->transcript_file = $transcriptFile;
|
||||||
|
} elseif ($transcriptChoice === 'remote-url') {
|
||||||
|
$newEpisode->transcript_file_remote_url = $this->request->getPost(
|
||||||
|
'transcript_file_remote_url'
|
||||||
|
) === '' ? null : $this->request->getPost('transcript_file_remote_url');
|
||||||
|
}
|
||||||
|
|
||||||
|
$chaptersChoice = $this->request->getPost('chapters-choice');
|
||||||
|
if (
|
||||||
|
$chaptersChoice === 'upload-file'
|
||||||
|
&& ($chaptersFile = $this->request->getFile('chapters_file'))
|
||||||
|
&& $chaptersFile->isValid()
|
||||||
|
) {
|
||||||
|
$newEpisode->chapters_file = $chaptersFile;
|
||||||
|
} elseif ($chaptersChoice === 'remote-url') {
|
||||||
|
$newEpisode->chapters_file_remote_url = $this->request->getPost(
|
||||||
|
'chapters_file_remote_url'
|
||||||
|
) === '' ? null : $this->request->getPost('chapters_file_remote_url');
|
||||||
|
}
|
||||||
|
|
||||||
|
$episodeModel = new EpisodeModel();
|
||||||
|
|
||||||
|
if (! ($newEpisodeId = $episodeModel->insert($newEpisode, true))) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $episodeModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
// update podcast's episode_description_footer_markdown if changed
|
||||||
|
$this->podcast->episode_description_footer_markdown = $this->request->getPost(
|
||||||
|
'description_footer'
|
||||||
|
) === '' ? null : $this->request->getPost('description_footer');
|
||||||
|
|
||||||
|
if ($this->podcast->hasChanged('episode_description_footer_markdown')) {
|
||||||
|
$podcastModel = new PodcastModel();
|
||||||
|
|
||||||
|
if (! $podcastModel->update($this->podcast->id, $this->podcast)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $podcastModel->errors());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('episode-view', [$this->podcast->id, $newEpisodeId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(): string
|
||||||
|
{
|
||||||
|
helper(['form']);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
'episode' => $this->episode,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
1 => $this->episode->title,
|
||||||
|
]);
|
||||||
|
return view('admin/episode/edit', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptEdit(): RedirectResponse
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'audio_file' =>
|
||||||
|
'uploaded[audio_file]|ext_in[audio_file,mp3,m4a]|permit_empty',
|
||||||
|
'image' =>
|
||||||
|
'is_image[image]|ext_in[image,jpg,png]|min_dims[image,1400,1400]|is_image_squared[image]',
|
||||||
|
'transcript_file' =>
|
||||||
|
'ext_in[transcript_file,txt,html,srt,json]|permit_empty',
|
||||||
|
'chapters_file' => 'ext_in[chapters_file,json]|permit_empty',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->episode->title = $this->request->getPost('title');
|
||||||
|
$this->episode->slug = $this->request->getPost('slug');
|
||||||
|
$this->episode->description_markdown = $this->request->getPost('description');
|
||||||
|
$this->episode->location = $this->request->getPost('location_name') === '' ? null : new Location(
|
||||||
|
$this->request->getPost('location_name')
|
||||||
|
);
|
||||||
|
$this->episode->parental_advisory =
|
||||||
|
$this->request->getPost('parental_advisory') !== 'undefined'
|
||||||
|
? $this->request->getPost('parental_advisory')
|
||||||
|
: null;
|
||||||
|
$this->episode->number = $this->request->getPost('episode_number')
|
||||||
|
? $this->request->getPost('episode_number')
|
||||||
|
: null;
|
||||||
|
$this->episode->season_number = $this->request->getPost('season_number')
|
||||||
|
? $this->request->getPost('season_number')
|
||||||
|
: null;
|
||||||
|
$this->episode->type = $this->request->getPost('type');
|
||||||
|
$this->episode->is_blocked = $this->request->getPost('block') === 'yes';
|
||||||
|
$this->episode->custom_rss_string = $this->request->getPost('custom_rss');
|
||||||
|
|
||||||
|
$this->episode->updated_by = (int) user_id();
|
||||||
|
|
||||||
|
$audioFile = $this->request->getFile('audio_file');
|
||||||
|
if ($audioFile !== null && $audioFile->isValid()) {
|
||||||
|
$this->episode->audio_file = $audioFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
$imageFile = $this->request->getFile('image');
|
||||||
|
if ($imageFile !== null && $imageFile->isValid()) {
|
||||||
|
$this->episode->image = new Image($imageFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
$transcriptChoice = $this->request->getPost('transcript-choice');
|
||||||
|
if ($transcriptChoice === 'upload-file') {
|
||||||
|
$transcriptFile = $this->request->getFile('transcript_file');
|
||||||
|
if ($transcriptFile !== null && $transcriptFile->isValid()) {
|
||||||
|
$this->episode->transcript_file = $transcriptFile;
|
||||||
|
$this->episode->transcript_file_remote_url = null;
|
||||||
|
}
|
||||||
|
} elseif ($transcriptChoice === 'remote-url') {
|
||||||
|
if (
|
||||||
|
($transcriptFileRemoteUrl = $this->request->getPost('transcript_file_remote_url')) &&
|
||||||
|
(($transcriptFile = $this->episode->transcript_file) !== null)
|
||||||
|
) {
|
||||||
|
unlink((string) $transcriptFile);
|
||||||
|
$this->episode->transcript_file_path = null;
|
||||||
|
}
|
||||||
|
$this->episode->transcript_file_remote_url = $transcriptFileRemoteUrl === '' ? null : $transcriptFileRemoteUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
$chaptersChoice = $this->request->getPost('chapters-choice');
|
||||||
|
if ($chaptersChoice === 'upload-file') {
|
||||||
|
$chaptersFile = $this->request->getFile('chapters_file');
|
||||||
|
if ($chaptersFile !== null && $chaptersFile->isValid()) {
|
||||||
|
$this->episode->chapters_file = $chaptersFile;
|
||||||
|
$this->episode->chapters_file_remote_url = null;
|
||||||
|
}
|
||||||
|
} elseif ($chaptersChoice === 'remote-url') {
|
||||||
|
if (
|
||||||
|
($chaptersFileRemoteUrl = $this->request->getPost('chapters_file_remote_url')) &&
|
||||||
|
(($chaptersFile = $this->episode->chapters_file) !== null)
|
||||||
|
) {
|
||||||
|
unlink((string) $chaptersFile);
|
||||||
|
$this->episode->chapters_file_path = null;
|
||||||
|
}
|
||||||
|
$this->episode->chapters_file_remote_url = $chaptersFileRemoteUrl === '' ? null : $chaptersFileRemoteUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = db_connect();
|
||||||
|
$db->transStart();
|
||||||
|
|
||||||
|
$episodeModel = new EpisodeModel();
|
||||||
|
|
||||||
|
if (! $episodeModel->update($this->episode->id, $this->episode)) {
|
||||||
|
$db->transRollback();
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $episodeModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
// update podcast's episode_description_footer_markdown if changed
|
||||||
|
$this->podcast->episode_description_footer_markdown = $this->request->getPost(
|
||||||
|
'description_footer'
|
||||||
|
) === '' ? null : $this->request->getPost('description_footer');
|
||||||
|
|
||||||
|
if ($this->podcast->hasChanged('episode_description_footer_markdown')) {
|
||||||
|
$podcastModel = new PodcastModel();
|
||||||
|
if (! $podcastModel->update($this->podcast->id, $this->podcast)) {
|
||||||
|
$db->transRollback();
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $podcastModel->errors());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$db->transComplete();
|
||||||
|
|
||||||
|
return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transcriptDelete(): RedirectResponse
|
||||||
|
{
|
||||||
|
unlink((string) $this->episode->transcript_file);
|
||||||
|
$this->episode->transcript_file_path = null;
|
||||||
|
|
||||||
|
$episodeModel = new EpisodeModel();
|
||||||
|
|
||||||
|
if (! $episodeModel->update($this->episode->id, $this->episode)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $episodeModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function chaptersDelete(): RedirectResponse
|
||||||
|
{
|
||||||
|
unlink((string) $this->episode->chapters_file);
|
||||||
|
$this->episode->chapters_file_path = null;
|
||||||
|
|
||||||
|
$episodeModel = new EpisodeModel();
|
||||||
|
|
||||||
|
if (! $episodeModel->update($this->episode->id, $this->episode)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $episodeModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function publish(): string | RedirectResponse
|
||||||
|
{
|
||||||
|
if ($this->episode->publication_status === 'not_published') {
|
||||||
|
helper(['form']);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
'episode' => $this->episode,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
1 => $this->episode->title,
|
||||||
|
]);
|
||||||
|
return view('admin/episode/publish', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with(
|
||||||
|
'error',
|
||||||
|
lang('Episode.publish_error')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptPublish(): RedirectResponse
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'publication_method' => 'required',
|
||||||
|
'scheduled_publication_date' =>
|
||||||
|
'valid_date[Y-m-d H:i]|permit_empty',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = db_connect();
|
||||||
|
$db->transStart();
|
||||||
|
|
||||||
|
$newStatus = new Status([
|
||||||
|
'actor_id' => $this->podcast->actor_id,
|
||||||
|
'episode_id' => $this->episode->id,
|
||||||
|
'message' => $this->request->getPost('message'),
|
||||||
|
'created_by' => user_id(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$publishMethod = $this->request->getPost('publication_method');
|
||||||
|
if ($publishMethod === 'schedule') {
|
||||||
|
$scheduledPublicationDate = $this->request->getPost('scheduled_publication_date');
|
||||||
|
if ($scheduledPublicationDate) {
|
||||||
|
$this->episode->published_at = Time::createFromFormat(
|
||||||
|
'Y-m-d H:i',
|
||||||
|
$scheduledPublicationDate,
|
||||||
|
$this->request->getPost('client_timezone'),
|
||||||
|
)->setTimezone('UTC');
|
||||||
|
} else {
|
||||||
|
$db->transRollback();
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('error', 'Schedule date must be set!');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->episode->published_at = Time::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
$newStatus->published_at = $this->episode->published_at;
|
||||||
|
|
||||||
|
$statusModel = new StatusModel();
|
||||||
|
if (! $statusModel->addStatus($newStatus)) {
|
||||||
|
$db->transRollback();
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $statusModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$episodeModel = new EpisodeModel();
|
||||||
|
if (! $episodeModel->update($this->episode->id, $this->episode)) {
|
||||||
|
$db->transRollback();
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $episodeModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$db->transComplete();
|
||||||
|
|
||||||
|
return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function publishEdit(): string | RedirectResponse
|
||||||
|
{
|
||||||
|
if ($this->episode->publication_status === 'scheduled') {
|
||||||
|
helper(['form']);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
'episode' => $this->episode,
|
||||||
|
'status' => (new StatusModel())
|
||||||
|
->where([
|
||||||
|
'actor_id' => $this->podcast->actor_id,
|
||||||
|
'episode_id' => $this->episode->id,
|
||||||
|
])
|
||||||
|
->first(),
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
1 => $this->episode->title,
|
||||||
|
]);
|
||||||
|
return view('admin/episode/publish_edit', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with(
|
||||||
|
'error',
|
||||||
|
lang('Episode.publish_edit_error')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptPublishEdit(): RedirectResponse
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'status_id' => 'required',
|
||||||
|
'publication_method' => 'required',
|
||||||
|
'scheduled_publication_date' =>
|
||||||
|
'valid_date[Y-m-d H:i]|permit_empty',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = db_connect();
|
||||||
|
$db->transStart();
|
||||||
|
|
||||||
|
$publishMethod = $this->request->getPost('publication_method');
|
||||||
|
if ($publishMethod === 'schedule') {
|
||||||
|
$scheduledPublicationDate = $this->request->getPost('scheduled_publication_date');
|
||||||
|
if ($scheduledPublicationDate) {
|
||||||
|
$this->episode->published_at = Time::createFromFormat(
|
||||||
|
'Y-m-d H:i',
|
||||||
|
$scheduledPublicationDate,
|
||||||
|
$this->request->getPost('client_timezone'),
|
||||||
|
)->setTimezone('UTC');
|
||||||
|
} else {
|
||||||
|
$db->transRollback();
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('error', 'Schedule date must be set!');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->episode->published_at = Time::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
$status = (new StatusModel())->getStatusById($this->request->getPost('status_id'));
|
||||||
|
|
||||||
|
if ($status !== null) {
|
||||||
|
$status->message = $this->request->getPost('message');
|
||||||
|
$status->published_at = $this->episode->published_at;
|
||||||
|
|
||||||
|
$statusModel = new StatusModel();
|
||||||
|
if (! $statusModel->editStatus($status)) {
|
||||||
|
$db->transRollback();
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $statusModel->errors());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$episodeModel = new EpisodeModel();
|
||||||
|
if (! $episodeModel->update($this->episode->id, $this->episode)) {
|
||||||
|
$db->transRollback();
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $episodeModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$db->transComplete();
|
||||||
|
|
||||||
|
return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function publishCancel(): RedirectResponse
|
||||||
|
{
|
||||||
|
if ($this->episode->publication_status === 'scheduled') {
|
||||||
|
$db = db_connect();
|
||||||
|
$db->transStart();
|
||||||
|
|
||||||
|
$statusModel = new StatusModel();
|
||||||
|
$status = $statusModel
|
||||||
|
->where([
|
||||||
|
'actor_id' => $this->podcast->actor_id,
|
||||||
|
'episode_id' => $this->episode->id,
|
||||||
|
])
|
||||||
|
->first();
|
||||||
|
$statusModel->removeStatus($status);
|
||||||
|
|
||||||
|
$this->episode->published_at = null;
|
||||||
|
|
||||||
|
$episodeModel = new EpisodeModel();
|
||||||
|
if (! $episodeModel->update($this->episode->id, $this->episode)) {
|
||||||
|
$db->transRollback();
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $episodeModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$db->transComplete();
|
||||||
|
|
||||||
|
return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with(
|
||||||
|
'error',
|
||||||
|
lang('Episode.publish_cancel_error')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unpublish(): string | RedirectResponse
|
||||||
|
{
|
||||||
|
if ($this->episode->publication_status === 'published') {
|
||||||
|
helper(['form']);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
'episode' => $this->episode,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
1 => $this->episode->title,
|
||||||
|
]);
|
||||||
|
return view('admin/episode/unpublish', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id])->with(
|
||||||
|
'error',
|
||||||
|
lang('Episode.unpublish_error')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptUnpublish(): RedirectResponse
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'understand' => 'required',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = db_connect();
|
||||||
|
|
||||||
|
$db->transStart();
|
||||||
|
|
||||||
|
$allStatusesLinkedToEpisode = (new StatusModel())
|
||||||
|
->where([
|
||||||
|
'episode_id' => $this->episode->id,
|
||||||
|
])
|
||||||
|
->findAll();
|
||||||
|
foreach ($allStatusesLinkedToEpisode as $status) {
|
||||||
|
(new StatusModel())->removeStatus($status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set episode published_at to null to unpublish
|
||||||
|
$this->episode->published_at = null;
|
||||||
|
|
||||||
|
$episodeModel = new EpisodeModel();
|
||||||
|
if (! $episodeModel->update($this->episode->id, $this->episode)) {
|
||||||
|
$db->transRollback();
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $episodeModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$db->transComplete();
|
||||||
|
|
||||||
|
return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(): RedirectResponse
|
||||||
|
{
|
||||||
|
(new EpisodeModel())->delete($this->episode->id);
|
||||||
|
|
||||||
|
return redirect()->route('episode-list', [$this->podcast->id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function soundbitesEdit(): string
|
||||||
|
{
|
||||||
|
helper(['form']);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
'episode' => $this->episode,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
1 => $this->episode->title,
|
||||||
|
]);
|
||||||
|
return view('admin/episode/soundbites', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function soundbitesAttemptEdit(): RedirectResponse
|
||||||
|
{
|
||||||
|
$soundbites = $this->request->getPost('soundbites');
|
||||||
|
$rules = [
|
||||||
|
'soundbites.0.start_time' =>
|
||||||
|
'permit_empty|required_with[soundbites.0.duration]|decimal|greater_than_equal_to[0]',
|
||||||
|
'soundbites.0.duration' =>
|
||||||
|
'permit_empty|required_with[soundbites.0.start_time]|decimal|greater_than_equal_to[0]',
|
||||||
|
];
|
||||||
|
foreach (array_keys($soundbites) as $soundbite_id) {
|
||||||
|
$rules += [
|
||||||
|
"soundbites.{$soundbite_id}.start_time" => 'required|decimal|greater_than_equal_to[0]',
|
||||||
|
"soundbites.{$soundbite_id}.duration" => 'required|decimal|greater_than_equal_to[0]',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($soundbites as $soundbite_id => $soundbite) {
|
||||||
|
if ((int) $soundbite['start_time'] < (int) $soundbite['duration']) {
|
||||||
|
$data = [
|
||||||
|
'podcast_id' => $this->podcast->id,
|
||||||
|
'episode_id' => $this->episode->id,
|
||||||
|
'start_time' => (float) $soundbite['start_time'],
|
||||||
|
'duration' => (float) $soundbite['duration'],
|
||||||
|
'label' => $soundbite['label'],
|
||||||
|
'updated_by' => user_id(),
|
||||||
|
];
|
||||||
|
if ($soundbite_id === 0) {
|
||||||
|
$data += [
|
||||||
|
'created_by' => user_id(),
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$data += [
|
||||||
|
'id' => $soundbite_id,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$soundbiteModel = new SoundbiteModel();
|
||||||
|
if (! $soundbiteModel->save($data)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $soundbiteModel->errors());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return redirect()->route('soundbites-edit', [$this->podcast->id, $this->episode->id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function soundbiteDelete(string $soundbiteId): RedirectResponse
|
||||||
|
{
|
||||||
|
(new SoundbiteModel())->deleteSoundbite($this->podcast->id, $this->episode->id, (int) $soundbiteId);
|
||||||
|
|
||||||
|
return redirect()->route('soundbites-edit', [$this->podcast->id, $this->episode->id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function embeddablePlayer(): string
|
||||||
|
{
|
||||||
|
helper(['form']);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
'episode' => $this->episode,
|
||||||
|
'themes' => EpisodeModel::$themes,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
1 => $this->episode->title,
|
||||||
|
]);
|
||||||
|
return view('admin/episode/embeddable_player', $data);
|
||||||
|
}
|
||||||
|
}
|
||||||
97
app/Controllers/Admin/EpisodePersonController.php
Normal file
97
app/Controllers/Admin/EpisodePersonController.php
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Entities\Episode;
|
||||||
|
use App\Entities\Podcast;
|
||||||
|
use App\Models\EpisodeModel;
|
||||||
|
use App\Models\PersonModel;
|
||||||
|
use App\Models\PodcastModel;
|
||||||
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||||
|
use CodeIgniter\HTTP\RedirectResponse;
|
||||||
|
|
||||||
|
class EpisodePersonController extends BaseController
|
||||||
|
{
|
||||||
|
protected Podcast $podcast;
|
||||||
|
|
||||||
|
protected Episode $episode;
|
||||||
|
|
||||||
|
public function _remap(string $method, string ...$params): mixed
|
||||||
|
{
|
||||||
|
if (count($params) < 2) {
|
||||||
|
throw PageNotFoundException::forPageNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
($this->podcast = (new PodcastModel())->getPodcastById((int) $params[0])) &&
|
||||||
|
($this->episode = (new EpisodeModel())
|
||||||
|
->where([
|
||||||
|
'id' => $params[1],
|
||||||
|
'podcast_id' => $params[0],
|
||||||
|
])
|
||||||
|
->first())
|
||||||
|
) {
|
||||||
|
unset($params[1]);
|
||||||
|
unset($params[0]);
|
||||||
|
|
||||||
|
return $this->{$method}(...$params);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw PageNotFoundException::forPageNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(): string
|
||||||
|
{
|
||||||
|
helper('form');
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'episode' => $this->episode,
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
'personOptions' => (new PersonModel())->getPersonOptions(),
|
||||||
|
'taxonomyOptions' => (new PersonModel())->getTaxonomyOptions(),
|
||||||
|
];
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
1 => $this->episode->title,
|
||||||
|
]);
|
||||||
|
return view('admin/episode/persons', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptAdd(): RedirectResponse
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'persons' => 'required',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
(new PersonModel())->addEpisodePersons(
|
||||||
|
$this->podcast->id,
|
||||||
|
$this->episode->id,
|
||||||
|
$this->request->getPost('persons'),
|
||||||
|
$this->request->getPost('roles') ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remove(string $personId): RedirectResponse
|
||||||
|
{
|
||||||
|
(new PersonModel())->removePersonFromEpisode($this->podcast->id, $this->episode->id, (int) $personId);
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
}
|
||||||
43
app/Controllers/Admin/FediverseController.php
Normal file
43
app/Controllers/Admin/FediverseController.php
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Controllers\Admin;
|
||||||
|
|
||||||
|
class FediverseController extends BaseController
|
||||||
|
{
|
||||||
|
public function dashboard(): string
|
||||||
|
{
|
||||||
|
return view('admin/fediverse/dashboard');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function blockedActors(): string
|
||||||
|
{
|
||||||
|
helper(['form']);
|
||||||
|
|
||||||
|
$blockedActors = model('ActorModel')
|
||||||
|
->getBlockedActors();
|
||||||
|
|
||||||
|
return view('admin/fediverse/blocked_actors', [
|
||||||
|
'blockedActors' => $blockedActors,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function blockedDomains(): string
|
||||||
|
{
|
||||||
|
helper(['form']);
|
||||||
|
|
||||||
|
$blockedDomains = model('BlockedDomainModel')
|
||||||
|
->getBlockedDomains();
|
||||||
|
|
||||||
|
return view('admin/fediverse/blocked_domains', [
|
||||||
|
'blockedDomains' => $blockedDomains,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
22
app/Controllers/Admin/HomeController.php
Normal file
22
app/Controllers/Admin/HomeController.php
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Controllers\Admin;
|
||||||
|
|
||||||
|
use CodeIgniter\HTTP\RedirectResponse;
|
||||||
|
|
||||||
|
class HomeController extends BaseController
|
||||||
|
{
|
||||||
|
public function index(): RedirectResponse
|
||||||
|
{
|
||||||
|
session()->keepFlashdata('message');
|
||||||
|
return redirect()->route('podcast-list');
|
||||||
|
}
|
||||||
|
}
|
||||||
78
app/Controllers/Admin/MyAccountController.php
Normal file
78
app/Controllers/Admin/MyAccountController.php
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Models\UserModel;
|
||||||
|
use CodeIgniter\HTTP\RedirectResponse;
|
||||||
|
use Config\Services;
|
||||||
|
|
||||||
|
class MyAccountController extends BaseController
|
||||||
|
{
|
||||||
|
public function index(): string
|
||||||
|
{
|
||||||
|
return view('admin/my_account/view');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function changePassword(): string
|
||||||
|
{
|
||||||
|
helper('form');
|
||||||
|
|
||||||
|
return view('admin/my_account/change_password');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptChange(): RedirectResponse
|
||||||
|
{
|
||||||
|
$auth = Services::authentication();
|
||||||
|
$userModel = new UserModel();
|
||||||
|
|
||||||
|
// Validate here first, since some things,
|
||||||
|
// like the password, can only be validated properly here.
|
||||||
|
$rules = [
|
||||||
|
'password' => 'required',
|
||||||
|
'new_password' => 'required|strong_password|differs[password]',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $userModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$credentials = [
|
||||||
|
'email' => user()
|
||||||
|
->email,
|
||||||
|
'password' => $this->request->getPost('password'),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $auth->validate($credentials)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('error', lang('MyAccount.messages.wrongPasswordError'));
|
||||||
|
}
|
||||||
|
|
||||||
|
user()
|
||||||
|
->password = $this->request->getPost('new_password');
|
||||||
|
|
||||||
|
if (! $userModel->update(user_id(), user())) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $userModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success!
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('message', lang('MyAccount.messages.passwordChangeSuccess'));
|
||||||
|
}
|
||||||
|
}
|
||||||
118
app/Controllers/Admin/PageController.php
Normal file
118
app/Controllers/Admin/PageController.php
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Entities\Page;
|
||||||
|
use App\Models\PageModel;
|
||||||
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||||
|
use CodeIgniter\HTTP\RedirectResponse;
|
||||||
|
|
||||||
|
class PageController extends BaseController
|
||||||
|
{
|
||||||
|
protected ?Page $page;
|
||||||
|
|
||||||
|
public function _remap(string $method, string ...$params): mixed
|
||||||
|
{
|
||||||
|
if (count($params) === 0) {
|
||||||
|
return $this->{$method}();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->page = (new PageModel())->find($params[0])) {
|
||||||
|
return $this->{$method}();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw PageNotFoundException::forPageNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function list(): string
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'pages' => (new PageModel())->findAll(),
|
||||||
|
];
|
||||||
|
|
||||||
|
return view('admin/page/list', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(): string
|
||||||
|
{
|
||||||
|
return view('admin/page/view', [
|
||||||
|
'page' => $this->page,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(): string
|
||||||
|
{
|
||||||
|
helper('form');
|
||||||
|
|
||||||
|
return view('admin/page/create');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptCreate(): RedirectResponse
|
||||||
|
{
|
||||||
|
$page = new Page([
|
||||||
|
'title' => $this->request->getPost('title'),
|
||||||
|
'slug' => $this->request->getPost('slug'),
|
||||||
|
'content_markdown' => $this->request->getPost('content'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pageModel = new PageModel();
|
||||||
|
|
||||||
|
if (! $pageModel->insert($page)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $pageModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->route('page-list')
|
||||||
|
->with('message', lang('Page.messages.createSuccess', [
|
||||||
|
'pageTitle' => $page->title,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(): string
|
||||||
|
{
|
||||||
|
helper('form');
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->page->title,
|
||||||
|
]);
|
||||||
|
return view('admin/page/edit', [
|
||||||
|
'page' => $this->page,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptEdit(): RedirectResponse
|
||||||
|
{
|
||||||
|
$this->page->title = $this->request->getPost('title');
|
||||||
|
$this->page->slug = $this->request->getPost('slug');
|
||||||
|
$this->page->content_markdown = $this->request->getPost('content');
|
||||||
|
|
||||||
|
$pageModel = new PageModel();
|
||||||
|
|
||||||
|
if (! $pageModel->update($this->page->id, $this->page)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $pageModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('page-list');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(): RedirectResponse
|
||||||
|
{
|
||||||
|
(new PageModel())->delete($this->page->id);
|
||||||
|
|
||||||
|
return redirect()->route('page-list');
|
||||||
|
}
|
||||||
|
}
|
||||||
156
app/Controllers/Admin/PersonController.php
Normal file
156
app/Controllers/Admin/PersonController.php
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2021 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Entities\Image;
|
||||||
|
use App\Entities\Person;
|
||||||
|
use App\Models\PersonModel;
|
||||||
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||||
|
use CodeIgniter\HTTP\RedirectResponse;
|
||||||
|
|
||||||
|
class PersonController extends BaseController
|
||||||
|
{
|
||||||
|
protected ?Person $person;
|
||||||
|
|
||||||
|
public function _remap(string $method, string ...$params): mixed
|
||||||
|
{
|
||||||
|
if (count($params) === 0) {
|
||||||
|
return $this->{$method}();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
($this->person = (new PersonModel())->getPersonById((int) $params[0])) !== null
|
||||||
|
) {
|
||||||
|
return $this->{$method}();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw PageNotFoundException::forPageNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(): string
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'persons' => (new PersonModel())->findAll(),
|
||||||
|
];
|
||||||
|
|
||||||
|
return view('admin/person/list', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(): string
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'person' => $this->person,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->person->full_name,
|
||||||
|
]);
|
||||||
|
return view('admin/person/view', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(): string
|
||||||
|
{
|
||||||
|
helper(['form']);
|
||||||
|
|
||||||
|
return view('admin/person/create');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptCreate(): RedirectResponse
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'image' =>
|
||||||
|
'is_image[image]|ext_in[image,jpg,jpeg,png]|min_dims[image,400,400]|is_image_squared[image]',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$person = new Person([
|
||||||
|
'full_name' => $this->request->getPost('full_name'),
|
||||||
|
'unique_name' => $this->request->getPost('unique_name'),
|
||||||
|
'information_url' => $this->request->getPost('information_url'),
|
||||||
|
'image' => new Image($this->request->getFile('image')),
|
||||||
|
'created_by' => user_id(),
|
||||||
|
'updated_by' => user_id(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$personModel = new PersonModel();
|
||||||
|
|
||||||
|
if (! $personModel->insert($person)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $personModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('person-list');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(): string
|
||||||
|
{
|
||||||
|
helper('form');
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'person' => $this->person,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->person->full_name,
|
||||||
|
]);
|
||||||
|
return view('admin/person/edit', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptEdit(): RedirectResponse
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'image' =>
|
||||||
|
'is_image[image]|ext_in[image,jpg,jpeg,png]|min_dims[image,400,400]|is_image_squared[image]',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->person->full_name = $this->request->getPost('full_name');
|
||||||
|
$this->person->unique_name = $this->request->getPost('unique_name');
|
||||||
|
$this->person->information_url = $this->request->getPost('information_url');
|
||||||
|
$imageFile = $this->request->getFile('image');
|
||||||
|
if ($imageFile !== null && $imageFile->isValid()) {
|
||||||
|
$this->person->image = new Image($imageFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->person->updated_by = user_id();
|
||||||
|
|
||||||
|
$personModel = new PersonModel();
|
||||||
|
if (! $personModel->update($this->person->id, $this->person)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $personModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('person-view', [$this->person->id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(): RedirectResponse
|
||||||
|
{
|
||||||
|
(new PersonModel())->delete($this->person->id);
|
||||||
|
|
||||||
|
return redirect()->route('person-list');
|
||||||
|
}
|
||||||
|
}
|
||||||
378
app/Controllers/Admin/PodcastController.php
Normal file
378
app/Controllers/Admin/PodcastController.php
Normal file
|
|
@ -0,0 +1,378 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Entities\Image;
|
||||||
|
use App\Entities\Location;
|
||||||
|
use App\Entities\Podcast;
|
||||||
|
use App\Models\CategoryModel;
|
||||||
|
use App\Models\EpisodeModel;
|
||||||
|
use App\Models\LanguageModel;
|
||||||
|
use App\Models\PodcastModel;
|
||||||
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||||
|
use CodeIgniter\HTTP\RedirectResponse;
|
||||||
|
use Config\Services;
|
||||||
|
|
||||||
|
class PodcastController extends BaseController
|
||||||
|
{
|
||||||
|
protected Podcast $podcast;
|
||||||
|
|
||||||
|
public function _remap(string $method, string ...$params): mixed
|
||||||
|
{
|
||||||
|
if (count($params) === 0) {
|
||||||
|
return $this->{$method}();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
($podcast = (new PodcastModel())->getPodcastById((int) $params[0])) !== null
|
||||||
|
) {
|
||||||
|
$this->podcast = $podcast;
|
||||||
|
return $this->{$method}();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw PageNotFoundException::forPageNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function list(): string
|
||||||
|
{
|
||||||
|
if (! has_permission('podcasts-list')) {
|
||||||
|
$data = [
|
||||||
|
'podcasts' => (new PodcastModel())->getUserPodcasts((int) user_id()),
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$data = [
|
||||||
|
'podcasts' => (new PodcastModel())->findAll(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('admin/podcast/list', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(): string
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
]);
|
||||||
|
return view('admin/podcast/view', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function viewAnalytics(): string
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
]);
|
||||||
|
return view('admin/podcast/analytics/index', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function viewAnalyticsWebpages(): string
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
]);
|
||||||
|
return view('admin/podcast/analytics/webpages', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function viewAnalyticsLocations(): string
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
]);
|
||||||
|
return view('admin/podcast/analytics/locations', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function viewAnalyticsUniqueListeners(): string
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
]);
|
||||||
|
return view('admin/podcast/analytics/unique_listeners', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function viewAnalyticsListeningTime(): string
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
]);
|
||||||
|
return view('admin/podcast/analytics/listening_time', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function viewAnalyticsTimePeriods(): string
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
]);
|
||||||
|
return view('admin/podcast/analytics/time_periods', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function viewAnalyticsPlayers(): string
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
]);
|
||||||
|
return view('admin/podcast/analytics/players', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(): string
|
||||||
|
{
|
||||||
|
helper(['form', 'misc']);
|
||||||
|
|
||||||
|
$languageOptions = (new LanguageModel())->getLanguageOptions();
|
||||||
|
$categoryOptions = (new CategoryModel())->getCategoryOptions();
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'languageOptions' => $languageOptions,
|
||||||
|
'categoryOptions' => $categoryOptions,
|
||||||
|
'browserLang' => get_browser_language($this->request->getServer('HTTP_ACCEPT_LANGUAGE')),
|
||||||
|
];
|
||||||
|
|
||||||
|
return view('admin/podcast/create', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptCreate(): RedirectResponse
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'image' =>
|
||||||
|
'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|min_dims[image,1400,1400]|is_image_squared[image]',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
($partnerId = $this->request->getPost('partner_id')) === '' ||
|
||||||
|
($partnerLinkUrl = $this->request->getPost('partner_link_url')) === '' ||
|
||||||
|
($partnerImageUrl = $this->request->getPost('partner_image_url')) === '') {
|
||||||
|
$partnerId = null;
|
||||||
|
$partnerLinkUrl = null;
|
||||||
|
$partnerImageUrl = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$podcast = new Podcast([
|
||||||
|
'guid' => podcast_uuid(url_to('podcast_feed', $this->request->getPost('name'))),
|
||||||
|
'title' => $this->request->getPost('title'),
|
||||||
|
'name' => $this->request->getPost('name'),
|
||||||
|
'description_markdown' => $this->request->getPost('description'),
|
||||||
|
'image' => new Image($this->request->getFile('image')),
|
||||||
|
'language_code' => $this->request->getPost('language'),
|
||||||
|
'category_id' => $this->request->getPost('category'),
|
||||||
|
'parental_advisory' =>
|
||||||
|
$this->request->getPost('parental_advisory') !== 'undefined'
|
||||||
|
? $this->request->getPost('parental_advisory')
|
||||||
|
: null,
|
||||||
|
'owner_name' => $this->request->getPost('owner_name'),
|
||||||
|
'owner_email' => $this->request->getPost('owner_email'),
|
||||||
|
'publisher' => $this->request->getPost('publisher'),
|
||||||
|
'type' => $this->request->getPost('type'),
|
||||||
|
'copyright' => $this->request->getPost('copyright'),
|
||||||
|
'location' => $this->request->getPost('location_name') === '' ? null : new Location($this->request->getPost(
|
||||||
|
'location_name'
|
||||||
|
)),
|
||||||
|
'payment_pointer' => $this->request->getPost(
|
||||||
|
'payment_pointer'
|
||||||
|
) === '' ? null : $this->request->getPost('payment_pointer'),
|
||||||
|
'custom_rss_string' => $this->request->getPost('custom_rss'),
|
||||||
|
'partner_id' => $partnerId,
|
||||||
|
'partner_link_url' => $partnerLinkUrl,
|
||||||
|
'partner_image_url' => $partnerImageUrl,
|
||||||
|
'is_blocked' => $this->request->getPost('block') === 'yes',
|
||||||
|
'is_completed' => $this->request->getPost('complete') === 'yes',
|
||||||
|
'is_locked' => $this->request->getPost('lock') === 'yes',
|
||||||
|
'created_by' => user_id(),
|
||||||
|
'updated_by' => user_id(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$podcastModel = new PodcastModel();
|
||||||
|
$db = db_connect();
|
||||||
|
|
||||||
|
$db->transStart();
|
||||||
|
|
||||||
|
if (! ($newPodcastId = $podcastModel->insert($podcast, true))) {
|
||||||
|
$db->transRollback();
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $podcastModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$authorize = Services::authorization();
|
||||||
|
$podcastAdminGroup = $authorize->group('podcast_admin');
|
||||||
|
|
||||||
|
$podcastModel->addPodcastContributor(user_id(), $newPodcastId, (int) $podcastAdminGroup->id);
|
||||||
|
|
||||||
|
// set Podcast categories
|
||||||
|
(new CategoryModel())->setPodcastCategories(
|
||||||
|
(int) $newPodcastId,
|
||||||
|
$this->request->getPost('other_categories') ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
|
// set interact as the newly created podcast actor
|
||||||
|
$createdPodcast = (new PodcastModel())->getPodcastById($newPodcastId);
|
||||||
|
set_interact_as_actor($createdPodcast->actor_id);
|
||||||
|
|
||||||
|
$db->transComplete();
|
||||||
|
|
||||||
|
return redirect()->route('podcast-view', [$newPodcastId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(): string
|
||||||
|
{
|
||||||
|
helper('form');
|
||||||
|
|
||||||
|
$languageOptions = (new LanguageModel())->getLanguageOptions();
|
||||||
|
$categoryOptions = (new CategoryModel())->getCategoryOptions();
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
'languageOptions' => $languageOptions,
|
||||||
|
'categoryOptions' => $categoryOptions,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
]);
|
||||||
|
return view('admin/podcast/edit', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptEdit(): RedirectResponse
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'image' =>
|
||||||
|
'is_image[image]|ext_in[image,jpg,png]|min_dims[image,1400,1400]|is_image_squared[image]',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
($partnerId = $this->request->getPost('partner_id')) === '' ||
|
||||||
|
($partnerLinkUrl = $this->request->getPost('partner_link_url')) === '' ||
|
||||||
|
($partnerImageUrl = $this->request->getPost('partner_image_url')) === '') {
|
||||||
|
$partnerId = null;
|
||||||
|
$partnerLinkUrl = null;
|
||||||
|
$partnerImageUrl = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->podcast->title = $this->request->getPost('title');
|
||||||
|
$this->podcast->description_markdown = $this->request->getPost('description');
|
||||||
|
|
||||||
|
$image = $this->request->getFile('image');
|
||||||
|
if ($image !== null && $image->isValid()) {
|
||||||
|
$this->podcast->image = new Image($image);
|
||||||
|
}
|
||||||
|
$this->podcast->language_code = $this->request->getPost('language');
|
||||||
|
$this->podcast->category_id = $this->request->getPost('category');
|
||||||
|
$this->podcast->parental_advisory =
|
||||||
|
$this->request->getPost('parental_advisory') !== 'undefined'
|
||||||
|
? $this->request->getPost('parental_advisory')
|
||||||
|
: null;
|
||||||
|
$this->podcast->publisher = $this->request->getPost('publisher');
|
||||||
|
$this->podcast->owner_name = $this->request->getPost('owner_name');
|
||||||
|
$this->podcast->owner_email = $this->request->getPost('owner_email');
|
||||||
|
$this->podcast->type = $this->request->getPost('type');
|
||||||
|
$this->podcast->copyright = $this->request->getPost('copyright');
|
||||||
|
$this->podcast->location = $this->request->getPost('location_name') === '' ? null : new Location(
|
||||||
|
$this->request->getPost('location_name')
|
||||||
|
);
|
||||||
|
$this->podcast->payment_pointer = $this->request->getPost(
|
||||||
|
'payment_pointer'
|
||||||
|
) === '' ? null : $this->request->getPost('payment_pointer');
|
||||||
|
$this->podcast->custom_rss_string = $this->request->getPost('custom_rss');
|
||||||
|
$this->podcast->partner_id = $partnerId;
|
||||||
|
$this->podcast->partner_link_url = $partnerLinkUrl;
|
||||||
|
$this->podcast->partner_image_url = $partnerImageUrl;
|
||||||
|
$this->podcast->is_blocked = $this->request->getPost('block') === 'yes';
|
||||||
|
$this->podcast->is_completed =
|
||||||
|
$this->request->getPost('complete') === 'yes';
|
||||||
|
$this->podcast->is_locked = $this->request->getPost('lock') === 'yes';
|
||||||
|
$this->podcast->updated_by = (int) user_id();
|
||||||
|
|
||||||
|
$db = db_connect();
|
||||||
|
$db->transStart();
|
||||||
|
|
||||||
|
$podcastModel = new PodcastModel();
|
||||||
|
if (! $podcastModel->update($this->podcast->id, $this->podcast)) {
|
||||||
|
$db->transRollback();
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $podcastModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
// set Podcast categories
|
||||||
|
(new CategoryModel())->setPodcastCategories(
|
||||||
|
$this->podcast->id,
|
||||||
|
$this->request->getPost('other_categories') ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
|
$db->transComplete();
|
||||||
|
|
||||||
|
return redirect()->route('podcast-view', [$this->podcast->id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function latestEpisodes(int $limit, int $podcast_id): string
|
||||||
|
{
|
||||||
|
$episodes = (new EpisodeModel())
|
||||||
|
->where('podcast_id', $podcast_id)
|
||||||
|
->orderBy('created_at', 'desc')
|
||||||
|
->findAll($limit);
|
||||||
|
|
||||||
|
return view('admin/podcast/latest_episodes', [
|
||||||
|
'episodes' => $episodes,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(): RedirectResponse
|
||||||
|
{
|
||||||
|
(new PodcastModel())->delete($this->podcast->id);
|
||||||
|
|
||||||
|
return redirect()->route('podcast-list');
|
||||||
|
}
|
||||||
|
}
|
||||||
453
app/Controllers/Admin/PodcastImportController.php
Normal file
453
app/Controllers/Admin/PodcastImportController.php
Normal file
|
|
@ -0,0 +1,453 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Entities\Episode;
|
||||||
|
use App\Entities\Image;
|
||||||
|
use App\Entities\Location;
|
||||||
|
use App\Entities\Person;
|
||||||
|
use App\Entities\Podcast;
|
||||||
|
use App\Models\CategoryModel;
|
||||||
|
use App\Models\EpisodeModel;
|
||||||
|
use App\Models\LanguageModel;
|
||||||
|
use App\Models\PersonModel;
|
||||||
|
use App\Models\PlatformModel;
|
||||||
|
use App\Models\PodcastModel;
|
||||||
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||||
|
use CodeIgniter\HTTP\RedirectResponse;
|
||||||
|
use Config\Services;
|
||||||
|
use ErrorException;
|
||||||
|
use League\HTMLToMarkdown\HtmlConverter;
|
||||||
|
use Podlibre\PodcastNamespace\ReversedTaxonomy;
|
||||||
|
|
||||||
|
class PodcastImportController extends BaseController
|
||||||
|
{
|
||||||
|
protected ?Podcast $podcast;
|
||||||
|
|
||||||
|
public function _remap(string $method, string ...$params): mixed
|
||||||
|
{
|
||||||
|
if (count($params) === 0) {
|
||||||
|
return $this->{$method}();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($this->podcast = (new PodcastModel())->getPodcastById((int) $params[0])) !== null) {
|
||||||
|
return $this->{$method}();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw PageNotFoundException::forPageNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(): string
|
||||||
|
{
|
||||||
|
helper(['form', 'misc']);
|
||||||
|
|
||||||
|
$languageOptions = (new LanguageModel())->getLanguageOptions();
|
||||||
|
$categoryOptions = (new CategoryModel())->getCategoryOptions();
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'languageOptions' => $languageOptions,
|
||||||
|
'categoryOptions' => $categoryOptions,
|
||||||
|
'browserLang' => get_browser_language($this->request->getServer('HTTP_ACCEPT_LANGUAGE')),
|
||||||
|
];
|
||||||
|
|
||||||
|
return view('admin/podcast/import', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptImport(): RedirectResponse
|
||||||
|
{
|
||||||
|
helper(['media', 'misc']);
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
'imported_feed_url' => 'required|validate_url',
|
||||||
|
'season_number' => 'is_natural_no_zero|permit_empty',
|
||||||
|
'max_episodes' => 'is_natural_no_zero|permit_empty',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ini_set('user_agent', 'Castopod/' . CP_VERSION);
|
||||||
|
$feed = simplexml_load_file($this->request->getPost('imported_feed_url'));
|
||||||
|
} catch (ErrorException $errorException) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', [
|
||||||
|
$errorException->getMessage() .
|
||||||
|
': <a href="' .
|
||||||
|
$this->request->getPost('imported_feed_url') .
|
||||||
|
'" rel="noreferrer noopener" target="_blank">' .
|
||||||
|
$this->request->getPost('imported_feed_url') .
|
||||||
|
' ⎋</a>',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$nsItunes = $feed->channel[0]->children('http://www.itunes.com/dtds/podcast-1.0.dtd');
|
||||||
|
$nsPodcast = $feed->channel[0]->children(
|
||||||
|
'https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md',
|
||||||
|
);
|
||||||
|
$nsContent = $feed->channel[0]->children('http://purl.org/rss/1.0/modules/content/');
|
||||||
|
|
||||||
|
if ((string) $nsPodcast->locked === 'yes') {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', [lang('PodcastImport.lock_import')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$converter = new HtmlConverter();
|
||||||
|
|
||||||
|
$channelDescriptionHtml = (string) $feed->channel[0]->description;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (
|
||||||
|
property_exists($nsItunes, 'image') && $nsItunes->image !== null &&
|
||||||
|
$nsItunes->image->attributes()['href'] !== null
|
||||||
|
) {
|
||||||
|
$imageFile = download_file((string) $nsItunes->image->attributes()['href']);
|
||||||
|
} else {
|
||||||
|
$imageFile = download_file((string) $feed->channel[0]->image->url);
|
||||||
|
}
|
||||||
|
|
||||||
|
$location = null;
|
||||||
|
if (property_exists($nsPodcast, 'location') && $nsPodcast->location !== null) {
|
||||||
|
$location = new Location(
|
||||||
|
(string) $nsPodcast->location,
|
||||||
|
$nsPodcast->location->attributes()['geo'] === null ? null : (string) $nsPodcast->location->attributes()['geo'],
|
||||||
|
$nsPodcast->location->attributes()['osm'] === null ? null : (string) $nsPodcast->location->attributes()['osm'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (property_exists($nsPodcast, 'guid') && $nsPodcast->guid !== null) {
|
||||||
|
$guid = (string) $nsPodcast->guid;
|
||||||
|
} else {
|
||||||
|
$guid = podcast_uuid(url_to('podcast_feed', $this->request->getPost('name')));
|
||||||
|
}
|
||||||
|
|
||||||
|
$podcast = new Podcast([
|
||||||
|
'guid' => $guid,
|
||||||
|
'name' => $this->request->getPost('name'),
|
||||||
|
'imported_feed_url' => $this->request->getPost('imported_feed_url'),
|
||||||
|
'new_feed_url' => url_to('podcast_feed', $this->request->getPost('name')),
|
||||||
|
'title' => (string) $feed->channel[0]->title,
|
||||||
|
'description_markdown' => $converter->convert($channelDescriptionHtml),
|
||||||
|
'description_html' => $channelDescriptionHtml,
|
||||||
|
'image' => new Image($imageFile),
|
||||||
|
'language_code' => $this->request->getPost('language'),
|
||||||
|
'category_id' => $this->request->getPost('category'),
|
||||||
|
'parental_advisory' =>
|
||||||
|
property_exists($nsItunes, 'explicit') && $nsItunes->explicit !== null
|
||||||
|
? (in_array((string) $nsItunes->explicit, ['yes', 'true'], true)
|
||||||
|
? 'explicit'
|
||||||
|
: (in_array((string) $nsItunes->explicit, ['no', 'false'], true)
|
||||||
|
? 'clean'
|
||||||
|
: null))
|
||||||
|
: null,
|
||||||
|
'owner_name' => (string) $nsItunes->owner->name,
|
||||||
|
'owner_email' => (string) $nsItunes->owner->email,
|
||||||
|
'publisher' => (string) $nsItunes->author,
|
||||||
|
'type' => property_exists(
|
||||||
|
$nsItunes,
|
||||||
|
'type'
|
||||||
|
) && $nsItunes->type !== null ? (string) $nsItunes->type : 'episodic',
|
||||||
|
'copyright' => (string) $feed->channel[0]->copyright,
|
||||||
|
'is_blocked' =>
|
||||||
|
property_exists($nsItunes, 'block') && $nsItunes->block !== null && (string) $nsItunes->block === 'yes',
|
||||||
|
'is_completed' =>
|
||||||
|
property_exists(
|
||||||
|
$nsItunes,
|
||||||
|
'complete'
|
||||||
|
) && $nsItunes->complete !== null && (string) $nsItunes->complete === 'yes',
|
||||||
|
'location' => $location,
|
||||||
|
'created_by' => user_id(),
|
||||||
|
'updated_by' => user_id(),
|
||||||
|
]);
|
||||||
|
} catch (ErrorException $ex) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', [
|
||||||
|
$ex->getMessage() .
|
||||||
|
': <a href="' .
|
||||||
|
$this->request->getPost('imported_feed_url') .
|
||||||
|
'" rel="noreferrer noopener" target="_blank">' .
|
||||||
|
$this->request->getPost('imported_feed_url') .
|
||||||
|
' ⎋</a>',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$podcastModel = new PodcastModel();
|
||||||
|
$db = db_connect();
|
||||||
|
|
||||||
|
$db->transStart();
|
||||||
|
|
||||||
|
if (! ($newPodcastId = $podcastModel->insert($podcast, true))) {
|
||||||
|
$db->transRollback();
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $podcastModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$authorize = Services::authorization();
|
||||||
|
$podcastAdminGroup = $authorize->group('podcast_admin');
|
||||||
|
|
||||||
|
$podcastModel->addPodcastContributor(user_id(), $newPodcastId, (int) $podcastAdminGroup->id);
|
||||||
|
|
||||||
|
$podcastsPlatformsData = [];
|
||||||
|
$platformTypes = [
|
||||||
|
[
|
||||||
|
'name' => 'podcasting',
|
||||||
|
'elements' => $nsPodcast->id,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'social',
|
||||||
|
'elements' => $nsPodcast->social,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'funding',
|
||||||
|
'elements' => $nsPodcast->funding,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$platformModel = new PlatformModel();
|
||||||
|
foreach ($platformTypes as $platformType) {
|
||||||
|
foreach ($platformType['elements'] as $platform) {
|
||||||
|
$platformLabel = $platform->attributes()['platform'];
|
||||||
|
$platformSlug = slugify((string) $platformLabel);
|
||||||
|
if ($platformModel->getPlatform($platformSlug) !== null) {
|
||||||
|
$podcastsPlatformsData[] = [
|
||||||
|
'platform_slug' => $platformSlug,
|
||||||
|
'podcast_id' => $newPodcastId,
|
||||||
|
'link_url' => $platform->attributes()['url'],
|
||||||
|
'link_content' => $platform->attributes()['id'],
|
||||||
|
'is_visible' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($podcastsPlatformsData) > 1) {
|
||||||
|
$platformModel->createPodcastPlatforms($newPodcastId, $podcastsPlatformsData);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($nsPodcast->person as $podcastPerson) {
|
||||||
|
$fullName = (string) $podcastPerson;
|
||||||
|
$personModel = new PersonModel();
|
||||||
|
$newPersonId = null;
|
||||||
|
if (($newPerson = $personModel->getPerson($fullName)) !== null) {
|
||||||
|
$newPersonId = $newPerson->id;
|
||||||
|
} else {
|
||||||
|
$newPodcastPerson = new Person([
|
||||||
|
'full_name' => $fullName,
|
||||||
|
'unique_name' => slugify($fullName),
|
||||||
|
'information_url' => $podcastPerson->attributes()['href'],
|
||||||
|
'image' => new Image(download_file((string) $podcastPerson->attributes()['img'])),
|
||||||
|
'created_by' => user_id(),
|
||||||
|
'updated_by' => user_id(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (! $newPersonId = $personModel->insert($newPodcastPerson)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $personModel->errors());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: these checks should be in the taxonomy as default values
|
||||||
|
$podcastPersonGroup = $podcastPerson->attributes()['group'] ?? 'Cast';
|
||||||
|
$podcastPersonRole = $podcastPerson->attributes()['role'] ?? 'Host';
|
||||||
|
|
||||||
|
$personGroup = ReversedTaxonomy::$taxonomy[(string) $podcastPersonGroup];
|
||||||
|
|
||||||
|
$personGroupSlug = $personGroup['slug'];
|
||||||
|
$personRoleSlug = $personGroup['roles'][(string) $podcastPersonRole]['slug'];
|
||||||
|
|
||||||
|
$podcastPersonModel = new PersonModel();
|
||||||
|
if (! $podcastPersonModel->addPodcastPerson(
|
||||||
|
$newPodcastId,
|
||||||
|
$newPersonId,
|
||||||
|
$personGroupSlug,
|
||||||
|
$personRoleSlug
|
||||||
|
)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $podcastPersonModel->errors());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$numberItems = $feed->channel[0]->item->count();
|
||||||
|
|
||||||
|
$lastItem =
|
||||||
|
$this->request->getPost('max_episodes') !== '' &&
|
||||||
|
$this->request->getPost('max_episodes') < $numberItems
|
||||||
|
? (int) $this->request->getPost('max_episodes')
|
||||||
|
: $numberItems;
|
||||||
|
|
||||||
|
$slugs = [];
|
||||||
|
for ($itemNumber = 1; $itemNumber <= $lastItem; ++$itemNumber) {
|
||||||
|
$item = $feed->channel[0]->item[$numberItems - $itemNumber];
|
||||||
|
|
||||||
|
$nsItunes = $item->children('http://www.itunes.com/dtds/podcast-1.0.dtd');
|
||||||
|
$nsPodcast = $item->children(
|
||||||
|
'https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md',
|
||||||
|
);
|
||||||
|
$nsContent = $item->children('http://purl.org/rss/1.0/modules/content/');
|
||||||
|
|
||||||
|
$textToSlugify = $this->request->getPost('slug_field') === 'title'
|
||||||
|
? (string) $item->title
|
||||||
|
: basename((string) $item->link);
|
||||||
|
$slug = slugify($textToSlugify, 185);
|
||||||
|
if (in_array($slug, $slugs, true)) {
|
||||||
|
$slugNumber = 2;
|
||||||
|
while (in_array($slug . '-' . $slugNumber, $slugs, true)) {
|
||||||
|
++$slugNumber;
|
||||||
|
}
|
||||||
|
$slug = $slug . '-' . $slugNumber;
|
||||||
|
}
|
||||||
|
$slugs[] = $slug;
|
||||||
|
$itemDescriptionHtml = match ($this->request->getPost('description_field')) {
|
||||||
|
'content' => (string) $nsContent->encoded,
|
||||||
|
'summary' => (string) $nsItunes->summary,
|
||||||
|
'subtitle_summary' => $nsItunes->subtitle . '<br/>' . $nsItunes->summary,
|
||||||
|
default => (string) $item->description,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
property_exists($nsItunes, 'image') && $nsItunes->image !== null &&
|
||||||
|
$nsItunes->image->attributes()['href'] !== null
|
||||||
|
) {
|
||||||
|
$episodeImage = new Image(download_file((string) $nsItunes->image->attributes()['href']));
|
||||||
|
} else {
|
||||||
|
$episodeImage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$location = null;
|
||||||
|
if (property_exists($nsPodcast, 'location') && $nsPodcast->location !== null) {
|
||||||
|
$location = new Location(
|
||||||
|
(string) $nsPodcast->location,
|
||||||
|
$nsPodcast->location->attributes()['geo'] === null ? null : (string) $nsPodcast->location->attributes()['geo'],
|
||||||
|
$nsPodcast->location->attributes()['osm'] === null ? null : (string) $nsPodcast->location->attributes()['osm'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$newEpisode = new Episode([
|
||||||
|
'podcast_id' => $newPodcastId,
|
||||||
|
'title' => $item->title,
|
||||||
|
'slug' => $slug,
|
||||||
|
'guid' => $item->guid ?? null,
|
||||||
|
'audio_file' => download_file(
|
||||||
|
(string) $item->enclosure->attributes()['url'],
|
||||||
|
(string) $item->enclosure->attributes()['type']
|
||||||
|
),
|
||||||
|
'description_markdown' => $converter->convert($itemDescriptionHtml),
|
||||||
|
'description_html' => $itemDescriptionHtml,
|
||||||
|
'image' => $episodeImage,
|
||||||
|
'parental_advisory' =>
|
||||||
|
property_exists($nsItunes, 'explicit') && $nsItunes->explicit !== null
|
||||||
|
? (in_array((string) $nsItunes->explicit, ['yes', 'true'], true)
|
||||||
|
? 'explicit'
|
||||||
|
: (in_array((string) $nsItunes->explicit, ['no', 'false'], true)
|
||||||
|
? 'clean'
|
||||||
|
: null))
|
||||||
|
: null,
|
||||||
|
'number' =>
|
||||||
|
$this->request->getPost('force_renumber') === 'yes'
|
||||||
|
? $itemNumber
|
||||||
|
: ((string) $nsItunes->episode === '' ? null : (int) $nsItunes->episode),
|
||||||
|
'season_number' =>
|
||||||
|
$this->request->getPost('season_number') === ''
|
||||||
|
? ((string) $nsItunes->season === '' ? null : (int) $nsItunes->season)
|
||||||
|
: (int) $this->request->getPost('season_number'),
|
||||||
|
'type' => property_exists($nsItunes, 'episodeType') && $nsItunes->episodeType !== null
|
||||||
|
? (string) $nsItunes->episodeType
|
||||||
|
: 'full',
|
||||||
|
'is_blocked' => property_exists(
|
||||||
|
$nsItunes,
|
||||||
|
'block'
|
||||||
|
) && $nsItunes->block !== null && (string) $nsItunes->block === 'yes',
|
||||||
|
'location' => $location,
|
||||||
|
'created_by' => user_id(),
|
||||||
|
'updated_by' => user_id(),
|
||||||
|
'published_at' => strtotime((string) $item->pubDate),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$episodeModel = new EpisodeModel();
|
||||||
|
|
||||||
|
if (! ($newEpisodeId = $episodeModel->insert($newEpisode, true))) {
|
||||||
|
// FIXME: What shall we do?
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $episodeModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($nsPodcast->person as $episodePerson) {
|
||||||
|
$fullName = (string) $episodePerson;
|
||||||
|
$personModel = new PersonModel();
|
||||||
|
$newPersonId = null;
|
||||||
|
if (($newPerson = $personModel->getPerson($fullName)) !== null) {
|
||||||
|
$newPersonId = $newPerson->id;
|
||||||
|
} else {
|
||||||
|
$newPerson = new Person([
|
||||||
|
'full_name' => $fullName,
|
||||||
|
'unique_name' => slugify($fullName),
|
||||||
|
'information_url' => $episodePerson->attributes()['href'],
|
||||||
|
'image' => new Image(download_file((string) $episodePerson->attributes()['img'])),
|
||||||
|
'created_by' => user_id(),
|
||||||
|
'updated_by' => user_id(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (! ($newPersonId = $personModel->insert($newPerson))) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $personModel->errors());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: these checks should be in the taxonomy as default values
|
||||||
|
$episodePersonGroup = $episodePerson->attributes()['group'] ?? 'Cast';
|
||||||
|
$episodePersonRole = $episodePerson->attributes()['role'] ?? 'Host';
|
||||||
|
|
||||||
|
$personGroup = ReversedTaxonomy::$taxonomy[(string) $episodePersonGroup];
|
||||||
|
|
||||||
|
$personGroupSlug = $personGroup['slug'];
|
||||||
|
$personRoleSlug = $personGroup['roles'][(string) $episodePersonRole]['slug'];
|
||||||
|
|
||||||
|
$episodePersonModel = new PersonModel();
|
||||||
|
if (! $episodePersonModel->addEpisodePerson(
|
||||||
|
$newPodcastId,
|
||||||
|
$newEpisodeId,
|
||||||
|
$newPersonId,
|
||||||
|
$personGroupSlug,
|
||||||
|
$personRoleSlug
|
||||||
|
)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $episodePersonModel->errors());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set interact as the newly imported podcast actor
|
||||||
|
$importedPodcast = (new PodcastModel())->getPodcastById($newPodcastId);
|
||||||
|
set_interact_as_actor($importedPodcast->actor_id);
|
||||||
|
|
||||||
|
$db->transComplete();
|
||||||
|
|
||||||
|
return redirect()->route('podcast-view', [$newPodcastId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
83
app/Controllers/Admin/PodcastPersonController.php
Normal file
83
app/Controllers/Admin/PodcastPersonController.php
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Entities\Podcast;
|
||||||
|
use App\Models\PersonModel;
|
||||||
|
use App\Models\PodcastModel;
|
||||||
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||||
|
use CodeIgniter\HTTP\RedirectResponse;
|
||||||
|
|
||||||
|
class PodcastPersonController extends BaseController
|
||||||
|
{
|
||||||
|
protected Podcast $podcast;
|
||||||
|
|
||||||
|
public function _remap(string $method, string ...$params): mixed
|
||||||
|
{
|
||||||
|
if (count($params) === 0) {
|
||||||
|
throw PageNotFoundException::forPageNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
($this->podcast = (new PodcastModel())->getPodcastById((int) $params[0])) !== null
|
||||||
|
) {
|
||||||
|
unset($params[0]);
|
||||||
|
return $this->{$method}(...$params);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw PageNotFoundException::forPageNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(): string
|
||||||
|
{
|
||||||
|
helper('form');
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
'podcastPersons' => (new PersonModel())->getPodcastPersons($this->podcast->id),
|
||||||
|
'personOptions' => (new PersonModel())->getPersonOptions(),
|
||||||
|
'taxonomyOptions' => (new PersonModel())->getTaxonomyOptions(),
|
||||||
|
];
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
]);
|
||||||
|
return view('admin/podcast/persons', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptAdd(): RedirectResponse
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'persons' => 'required',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
(new PersonModel())->addPodcastPersons(
|
||||||
|
$this->podcast->id,
|
||||||
|
$this->request->getPost('persons'),
|
||||||
|
$this->request->getPost('roles') ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remove(string $personId): RedirectResponse
|
||||||
|
{
|
||||||
|
(new PersonModel())->removePersonFromPodcast($this->podcast->id, (int) $personId);
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
}
|
||||||
110
app/Controllers/Admin/PodcastPlatformController.php
Normal file
110
app/Controllers/Admin/PodcastPlatformController.php
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Entities\Podcast;
|
||||||
|
use App\Models\PlatformModel;
|
||||||
|
use App\Models\PodcastModel;
|
||||||
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||||
|
use CodeIgniter\HTTP\RedirectResponse;
|
||||||
|
use Config\Services;
|
||||||
|
|
||||||
|
class PodcastPlatformController extends BaseController
|
||||||
|
{
|
||||||
|
protected ?Podcast $podcast;
|
||||||
|
|
||||||
|
public function _remap(string $method, string ...$params): mixed
|
||||||
|
{
|
||||||
|
if (count($params) === 0) {
|
||||||
|
return $this->{$method}();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
($this->podcast = (new PodcastModel())->getPodcastById((int) $params[0])) !== null
|
||||||
|
) {
|
||||||
|
unset($params[0]);
|
||||||
|
return $this->{$method}(...$params);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw PageNotFoundException::forPageNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(): string
|
||||||
|
{
|
||||||
|
return view('admin/podcast/platforms/dashboard');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function platforms(string $platformType): string
|
||||||
|
{
|
||||||
|
helper('form');
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
'platformType' => $platformType,
|
||||||
|
'platforms' => (new PlatformModel())->getPlatformsWithLinks($this->podcast->id, $platformType),
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->podcast->title,
|
||||||
|
]);
|
||||||
|
return view('admin/podcast/platforms', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptPlatformsUpdate(string $platformType): RedirectResponse
|
||||||
|
{
|
||||||
|
$platformModel = new PlatformModel();
|
||||||
|
$validation = Services::validation();
|
||||||
|
|
||||||
|
$podcastsPlatformsData = [];
|
||||||
|
|
||||||
|
foreach (
|
||||||
|
$this->request->getPost('platforms')
|
||||||
|
as $platformSlug => $podcastPlatform
|
||||||
|
) {
|
||||||
|
$podcastPlatformUrl = $podcastPlatform['url'];
|
||||||
|
if ($podcastPlatformUrl === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (! $validation->check($podcastPlatformUrl, 'validate_url')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$podcastsPlatformsData[] = [
|
||||||
|
'platform_slug' => $platformSlug,
|
||||||
|
'podcast_id' => $this->podcast->id,
|
||||||
|
'link_url' => $podcastPlatformUrl,
|
||||||
|
'link_content' => $podcastPlatform['content'],
|
||||||
|
'is_visible' =>
|
||||||
|
array_key_exists('visible', $podcastPlatform) &&
|
||||||
|
$podcastPlatform['visible'] === 'yes',
|
||||||
|
'is_on_embeddable_player' =>
|
||||||
|
array_key_exists(
|
||||||
|
'on_embeddable_player',
|
||||||
|
$podcastPlatform,
|
||||||
|
) && $podcastPlatform['on_embeddable_player'] === 'yes',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$platformModel->savePodcastPlatforms($this->podcast->id, $platformType, $podcastsPlatformsData);
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('message', lang('Platforms.messages.updateSuccess'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removePodcastPlatform(string $platformSlug): RedirectResponse
|
||||||
|
{
|
||||||
|
(new PlatformModel())->removePodcastPlatform($this->podcast->id, $platformSlug);
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('message', lang('Platforms.messages.removeLinkSuccess'));
|
||||||
|
}
|
||||||
|
}
|
||||||
247
app/Controllers/Admin/UserController.php
Normal file
247
app/Controllers/Admin/UserController.php
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Authorization\GroupModel;
|
||||||
|
use App\Entities\User;
|
||||||
|
use App\Models\UserModel;
|
||||||
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||||
|
use CodeIgniter\HTTP\RedirectResponse;
|
||||||
|
use Config\Services;
|
||||||
|
|
||||||
|
class UserController extends BaseController
|
||||||
|
{
|
||||||
|
protected ?User $user;
|
||||||
|
|
||||||
|
public function _remap(string $method, string ...$params): mixed
|
||||||
|
{
|
||||||
|
if (count($params) === 0) {
|
||||||
|
return $this->{$method}();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->user = (new UserModel())->find($params[0])) {
|
||||||
|
return $this->{$method}();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw PageNotFoundException::forPageNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function list(): string
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'users' => (new UserModel())->findAll(),
|
||||||
|
];
|
||||||
|
|
||||||
|
return view('admin/user/list', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(): string
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'user' => $this->user,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->user->username,
|
||||||
|
]);
|
||||||
|
return view('admin/user/view', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(): string
|
||||||
|
{
|
||||||
|
helper('form');
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'roles' => (new GroupModel())->getUserRoles(),
|
||||||
|
];
|
||||||
|
|
||||||
|
return view('admin/user/create', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptCreate(): RedirectResponse
|
||||||
|
{
|
||||||
|
$userModel = new UserModel();
|
||||||
|
|
||||||
|
// Validate here first, since some things,
|
||||||
|
// like the password, can only be validated properly here.
|
||||||
|
$rules = array_merge(
|
||||||
|
$userModel->getValidationRules([
|
||||||
|
'only' => ['username'],
|
||||||
|
]),
|
||||||
|
[
|
||||||
|
'email' => 'required|valid_email|is_unique[users.email]',
|
||||||
|
'password' => 'required|strong_password',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the user
|
||||||
|
$user = new User($this->request->getPost());
|
||||||
|
|
||||||
|
// Activate user
|
||||||
|
$user->activate();
|
||||||
|
|
||||||
|
// Force user to reset his password on first connection
|
||||||
|
$user->forcePasswordReset();
|
||||||
|
|
||||||
|
if (! $userModel->insert($user)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $userModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success!
|
||||||
|
return redirect()
|
||||||
|
->route('user-list')
|
||||||
|
->with('message', lang('User.messages.createSuccess', [
|
||||||
|
'username' => $user->username,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(): string
|
||||||
|
{
|
||||||
|
helper('form');
|
||||||
|
|
||||||
|
$roles = (new GroupModel())->getUserRoles();
|
||||||
|
$roleOptions = array_reduce(
|
||||||
|
$roles,
|
||||||
|
function ($result, $role) {
|
||||||
|
$result[$role->name] = lang('User.roles.' . $role->name);
|
||||||
|
return $result;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'user' => $this->user,
|
||||||
|
'roleOptions' => $roleOptions,
|
||||||
|
];
|
||||||
|
|
||||||
|
replace_breadcrumb_params([
|
||||||
|
0 => $this->user->username,
|
||||||
|
]);
|
||||||
|
return view('admin/user/edit', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptEdit(): RedirectResponse
|
||||||
|
{
|
||||||
|
$authorize = Services::authorization();
|
||||||
|
|
||||||
|
$roles = $this->request->getPost('roles');
|
||||||
|
$authorize->setUserGroups($this->user->id, $roles ?? []);
|
||||||
|
|
||||||
|
// Success!
|
||||||
|
return redirect()
|
||||||
|
->route('user-list')
|
||||||
|
->with('message', lang('User.messages.rolesEditSuccess', [
|
||||||
|
'username' => $this->user->username,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function forcePassReset(): RedirectResponse
|
||||||
|
{
|
||||||
|
$userModel = new UserModel();
|
||||||
|
$this->user->forcePasswordReset();
|
||||||
|
|
||||||
|
if (! $userModel->update($this->user->id, $this->user)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('errors', $userModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success!
|
||||||
|
return redirect()
|
||||||
|
->route('user-list')
|
||||||
|
->with(
|
||||||
|
'message',
|
||||||
|
lang('User.messages.forcePassResetSuccess', [
|
||||||
|
'username' => $this->user->username,
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ban(): RedirectResponse
|
||||||
|
{
|
||||||
|
$authorize = Services::authorization();
|
||||||
|
if ($authorize->inGroup('superadmin', $this->user->id)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('errors', [
|
||||||
|
lang('User.messages.banSuperAdminError', [
|
||||||
|
'username' => $this->user->username,
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$userModel = new UserModel();
|
||||||
|
// TODO: add ban reason?
|
||||||
|
$this->user->ban('');
|
||||||
|
|
||||||
|
if (! $userModel->update($this->user->id, $this->user)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('errors', $userModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->route('user-list')
|
||||||
|
->with('message', lang('User.messages.banSuccess', [
|
||||||
|
'username' => $this->user->username,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unBan(): RedirectResponse
|
||||||
|
{
|
||||||
|
$userModel = new UserModel();
|
||||||
|
$this->user->unBan();
|
||||||
|
|
||||||
|
if (! $userModel->update($this->user->id, $this->user)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('errors', $userModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->route('user-list')
|
||||||
|
->with('message', lang('User.messages.unbanSuccess', [
|
||||||
|
'username' => $this->user->username,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(): RedirectResponse
|
||||||
|
{
|
||||||
|
$authorize = Services::authorization();
|
||||||
|
if ($authorize->inGroup('superadmin', $this->user->id)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('errors', [
|
||||||
|
lang('User.messages.deleteSuperAdminError', [
|
||||||
|
'username' => $this->user->username,
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
(new UserModel())->delete($this->user->id);
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('message', lang('User.messages.deleteSuccess', [
|
||||||
|
'username' => $this->user->username,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
}
|
||||||
188
app/Controllers/AuthController.php
Normal file
188
app/Controllers/AuthController.php
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Controllers;
|
||||||
|
|
||||||
|
use App\Entities\User;
|
||||||
|
use CodeIgniter\HTTP\RedirectResponse;
|
||||||
|
use Myth\Auth\Controllers\AuthController as MythAuthController;
|
||||||
|
|
||||||
|
class AuthController extends MythAuthController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* An array of helpers to be automatically loaded upon class instantiation.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $helpers = ['components'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to register a new user.
|
||||||
|
*/
|
||||||
|
public function attemptRegister(): RedirectResponse
|
||||||
|
{
|
||||||
|
// Check if registration is allowed
|
||||||
|
if (! $this->config->allowRegistration) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('error', lang('Auth.registerDisabled'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$users = model('UserModel');
|
||||||
|
|
||||||
|
// Validate here first, since some things,
|
||||||
|
// like the password, can only be validated properly here.
|
||||||
|
$rules = [
|
||||||
|
'username' =>
|
||||||
|
'required|alpha_numeric_space|min_length[3]|is_unique[users.username]',
|
||||||
|
'email' => 'required|valid_email|is_unique[users.email]',
|
||||||
|
'password' => 'required|strong_password',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', service('validation')->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the user
|
||||||
|
$allowedPostFields = array_merge(['password'], $this->config->validFields, $this->config->personalFields);
|
||||||
|
$user = new User($this->request->getPost($allowedPostFields));
|
||||||
|
|
||||||
|
$this->config->requireActivation === null
|
||||||
|
? $user->activate()
|
||||||
|
: $user->generateActivateHash();
|
||||||
|
|
||||||
|
// Ensure default group gets assigned if set
|
||||||
|
if ($this->config->defaultUserGroup !== null) {
|
||||||
|
$users = $users->withGroup($this->config->defaultUserGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $users->save($user)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $users->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->config->requireActivation !== null) {
|
||||||
|
$activator = service('activator');
|
||||||
|
$sent = $activator->send($user);
|
||||||
|
|
||||||
|
if (! $sent) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('error', $activator->error() ?? lang('Auth.unknownError'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success!
|
||||||
|
return redirect()
|
||||||
|
->route('login')
|
||||||
|
->with('message', lang('Auth.activationSuccess'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success!
|
||||||
|
return redirect()
|
||||||
|
->route('login')
|
||||||
|
->with('message', lang('Auth.registerSuccess'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the code with the email and saves the new password, if they all pass validation.
|
||||||
|
*/
|
||||||
|
public function attemptReset(): RedirectResponse
|
||||||
|
{
|
||||||
|
if ($this->config->activeResetter === null) {
|
||||||
|
return redirect()
|
||||||
|
->route('login')
|
||||||
|
->with('error', lang('Auth.forgotDisabled'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$users = model('UserModel');
|
||||||
|
|
||||||
|
// First things first - log the reset attempt.
|
||||||
|
$users->logResetAttempt(
|
||||||
|
$this->request->getPost('email'),
|
||||||
|
$this->request->getPost('token'),
|
||||||
|
$this->request->getIPAddress(),
|
||||||
|
(string) $this->request->getUserAgent(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
'token' => 'required',
|
||||||
|
'email' => 'required|valid_email',
|
||||||
|
'password' => 'required|strong_password',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $users->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $users
|
||||||
|
->where('email', $this->request->getPost('email'))
|
||||||
|
->where('reset_hash', $this->request->getPost('token'))
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($user === null) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('error', lang('Auth.forgotNoUser'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset token still valid?
|
||||||
|
if (
|
||||||
|
$user->reset_expires !== null &&
|
||||||
|
time() > $user->reset_expires->getTimestamp()
|
||||||
|
) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('error', lang('Auth.resetTokenExpired'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success! Save the new password, and cleanup the reset hash.
|
||||||
|
$user->password = $this->request->getPost('password');
|
||||||
|
$user->reset_hash = null;
|
||||||
|
$user->reset_at = date('Y-m-d H:i:s');
|
||||||
|
$user->reset_expires = null;
|
||||||
|
$user->force_pass_reset = false;
|
||||||
|
$users->save($user);
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->route('login')
|
||||||
|
->with('message', lang('Auth.resetSuccess'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptInteractAsActor(): RedirectResponse
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'actor_id' => 'required|numeric',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', service('validation')->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
helper('auth');
|
||||||
|
|
||||||
|
set_interact_as_actor((int) $this->request->getPost('actor_id'));
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,51 +7,35 @@ namespace App\Controllers;
|
||||||
use CodeIgniter\Controller;
|
use CodeIgniter\Controller;
|
||||||
use CodeIgniter\HTTP\RequestInterface;
|
use CodeIgniter\HTTP\RequestInterface;
|
||||||
use CodeIgniter\HTTP\ResponseInterface;
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
use Override;
|
|
||||||
use Psr\Log\LoggerInterface;
|
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
|
* BaseController provides a convenient place for loading components and performing functions that are needed by all
|
||||||
* your controllers.
|
* your controllers. Extend this class in any new controllers: class Home extends BaseController
|
||||||
*
|
*
|
||||||
* Extend this class in any new controllers:
|
* For security be sure to declare any new methods as protected or private.
|
||||||
* ```
|
|
||||||
* class Home extends BaseController
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* For security, be sure to declare any new methods as protected or private.
|
|
||||||
*/
|
*/
|
||||||
abstract class BaseController extends Controller
|
class BaseController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* An array of helpers to be loaded automatically upon
|
* An array of helpers to be loaded automatically upon class instantiation. These helpers will be available to all
|
||||||
* class instantiation. These helpers will be available
|
* other controllers that extend BaseController.
|
||||||
* to all other controllers that extend BaseController.
|
|
||||||
*
|
*
|
||||||
* @var list<string>
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
protected $helpers = [];
|
protected $helpers = ['auth', 'svg', 'components', 'misc'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Be sure to declare properties for any property fetch you initialized.
|
* Constructor.
|
||||||
* The creation of dynamic property is deprecated in PHP 8.2.
|
|
||||||
*/
|
*/
|
||||||
// protected $session;
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function initController(
|
public function initController(
|
||||||
RequestInterface $request,
|
RequestInterface $request,
|
||||||
ResponseInterface $response,
|
ResponseInterface $response,
|
||||||
LoggerInterface $logger,
|
LoggerInterface $logger
|
||||||
): void {
|
): void {
|
||||||
// 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
|
// Do Not Edit This Line
|
||||||
parent::initController($request, $response, $logger);
|
parent::initController($request, $response, $logger);
|
||||||
|
|
||||||
Theme::setTheme('app');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,45 +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 CodeIgniter\Controller;
|
|
||||||
use CodeIgniter\HTTP\ResponseInterface;
|
|
||||||
|
|
||||||
class ColorsController extends Controller
|
|
||||||
{
|
|
||||||
public function index(): ResponseInterface
|
|
||||||
{
|
|
||||||
$cacheName = 'colors.css';
|
|
||||||
if (
|
|
||||||
! ($colorsCssBody = cache($cacheName))
|
|
||||||
) {
|
|
||||||
$colorThemes = config('Colors')
|
|
||||||
->themes;
|
|
||||||
|
|
||||||
$colorsCssBody = '';
|
|
||||||
foreach ($colorThemes as $name => $color) {
|
|
||||||
$colorsCssBody .= ".theme-{$name} {";
|
|
||||||
foreach ($color as $variable => $value) {
|
|
||||||
$colorsCssBody .= "--color-{$variable}: {$value[0]} {$value[1]}% {$value[2]}%;";
|
|
||||||
}
|
|
||||||
|
|
||||||
$colorsCssBody .= '}';
|
|
||||||
}
|
|
||||||
|
|
||||||
cache()
|
|
||||||
->save($cacheName, $colorsCssBody, DECADE);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->response->setHeader('Content-Type', 'text/css')
|
|
||||||
->setHeader('charset', 'UTF-8')
|
|
||||||
->setBody($colorsCssBody);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @copyright 2020 Ad Aures
|
* @copyright 2020 Podlibre
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
@ -20,23 +20,17 @@ class CreditsController extends BaseController
|
||||||
{
|
{
|
||||||
$locale = service('request')
|
$locale = service('request')
|
||||||
->getLocale();
|
->getLocale();
|
||||||
|
$allPodcasts = (new PodcastModel())->findAll();
|
||||||
|
|
||||||
$cacheName = implode(
|
$cacheName = "page_credits_{$locale}";
|
||||||
'_',
|
|
||||||
array_filter(['page', 'credits', $locale, auth()->loggedIn() ? 'authenticated' : null]),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (! ($found = cache($cacheName))) {
|
if (! ($found = cache($cacheName))) {
|
||||||
$page = new Page([
|
$page = new Page([
|
||||||
'title' => lang('Person.credits', [], $locale),
|
'title' => lang('Person.credits', [], $locale),
|
||||||
'slug' => 'credits',
|
'slug' => 'credits',
|
||||||
'content_markdown' => '',
|
'content_markdown' => '',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$allPodcasts = new PodcastModel()
|
$allCredits = (new CreditModel())->findAll();
|
||||||
->findAll();
|
|
||||||
$allCredits = new CreditModel()
|
|
||||||
->findAll();
|
|
||||||
|
|
||||||
// Unlike the carpenter, we make a tree from a table:
|
// Unlike the carpenter, we make a tree from a table:
|
||||||
$personGroup = null;
|
$personGroup = null;
|
||||||
|
|
@ -50,24 +44,27 @@ class CreditsController extends BaseController
|
||||||
$personRole = $credit->person_role;
|
$personRole = $credit->person_role;
|
||||||
$credits[$personGroup] = [
|
$credits[$personGroup] = [
|
||||||
'group_label' => $credit->group_label,
|
'group_label' => $credit->group_label,
|
||||||
'persons' => [
|
'persons' => [
|
||||||
$personId => [
|
$personId => [
|
||||||
'full_name' => $credit->person->full_name,
|
'full_name' => $credit->person->full_name,
|
||||||
'thumbnail_url' => get_avatar_url($credit->person, 'thumbnail'),
|
'thumbnail_url' =>
|
||||||
'information_url' => $credit->person->information_url,
|
$credit->person->image->thumbnail_url,
|
||||||
'roles' => [
|
'information_url' =>
|
||||||
|
$credit->person->information_url,
|
||||||
|
'roles' => [
|
||||||
$personRole => [
|
$personRole => [
|
||||||
'role_label' => $credit->role_label,
|
'role_label' => $credit->role_label,
|
||||||
'is_in' => [
|
'is_in' => [
|
||||||
[
|
[
|
||||||
'link' => $credit->episode_id
|
'link' => $credit->episode_id
|
||||||
? $credit->episode->link
|
? $credit->episode->link
|
||||||
: $credit->podcast->link,
|
: $credit->podcast->link,
|
||||||
'title' => $credit->episode_id
|
'title' => $credit->episode_id
|
||||||
? (count($allPodcasts) > 1
|
? (count($allPodcasts) > 1
|
||||||
? esc($credit->podcast->title) . ' › '
|
? "{$credit->podcast->title} › "
|
||||||
: '') .
|
: '') .
|
||||||
esc($credit->episode->title) .
|
$credit->episode
|
||||||
|
->title .
|
||||||
episode_numbering(
|
episode_numbering(
|
||||||
$credit->episode
|
$credit->episode
|
||||||
->number,
|
->number,
|
||||||
|
|
@ -76,7 +73,7 @@ class CreditsController extends BaseController
|
||||||
'text-xs ml-2',
|
'text-xs ml-2',
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
: esc($credit->podcast->title),
|
: $credit->podcast->title,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
@ -88,22 +85,23 @@ class CreditsController extends BaseController
|
||||||
$personId = $credit->person_id;
|
$personId = $credit->person_id;
|
||||||
$personRole = $credit->person_role;
|
$personRole = $credit->person_role;
|
||||||
$credits[$personGroup]['persons'][$personId] = [
|
$credits[$personGroup]['persons'][$personId] = [
|
||||||
'full_name' => $credit->person->full_name,
|
'full_name' => $credit->person->full_name,
|
||||||
'thumbnail_url' => get_avatar_url($credit->person, 'thumbnail'),
|
'thumbnail_url' =>
|
||||||
|
$credit->person->image->thumbnail_url,
|
||||||
'information_url' => $credit->person->information_url,
|
'information_url' => $credit->person->information_url,
|
||||||
'roles' => [
|
'roles' => [
|
||||||
$personRole => [
|
$personRole => [
|
||||||
'role_label' => $credit->role_label,
|
'role_label' => $credit->role_label,
|
||||||
'is_in' => [
|
'is_in' => [
|
||||||
[
|
[
|
||||||
'link' => $credit->episode_id
|
'link' => $credit->episode_id
|
||||||
? $credit->episode->link
|
? $credit->episode->link
|
||||||
: $credit->podcast->link,
|
: $credit->podcast->link,
|
||||||
'title' => $credit->episode_id
|
'title' => $credit->episode_id
|
||||||
? (count($allPodcasts) > 1
|
? (count($allPodcasts) > 1
|
||||||
? esc($credit->podcast->title) . ' › '
|
? "{$credit->podcast->title} › "
|
||||||
: '') .
|
: '') .
|
||||||
esc($credit->episode->title) .
|
$credit->episode->title .
|
||||||
episode_numbering(
|
episode_numbering(
|
||||||
$credit->episode->number,
|
$credit->episode->number,
|
||||||
$credit->episode
|
$credit->episode
|
||||||
|
|
@ -111,7 +109,7 @@ class CreditsController extends BaseController
|
||||||
'text-xs ml-2',
|
'text-xs ml-2',
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
: esc($credit->podcast->title),
|
: $credit->podcast->title,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
@ -123,23 +121,23 @@ class CreditsController extends BaseController
|
||||||
$personRole
|
$personRole
|
||||||
] = [
|
] = [
|
||||||
'role_label' => $credit->role_label,
|
'role_label' => $credit->role_label,
|
||||||
'is_in' => [
|
'is_in' => [
|
||||||
[
|
[
|
||||||
'link' => $credit->episode_id
|
'link' => $credit->episode_id
|
||||||
? $credit->episode->link
|
? $credit->episode->link
|
||||||
: $credit->podcast->link,
|
: $credit->podcast->link,
|
||||||
'title' => $credit->episode_id
|
'title' => $credit->episode_id
|
||||||
? (count($allPodcasts) > 1
|
? (count($allPodcasts) > 1
|
||||||
? esc($credit->podcast->title) . ' › '
|
? "{$credit->podcast->title} › "
|
||||||
: '') .
|
: '') .
|
||||||
esc($credit->episode->title) .
|
$credit->episode->title .
|
||||||
episode_numbering(
|
episode_numbering(
|
||||||
$credit->episode->number,
|
$credit->episode->number,
|
||||||
$credit->episode->season_number,
|
$credit->episode->season_number,
|
||||||
'text-xs ml-2',
|
'text-xs ml-2',
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
: esc($credit->podcast->title),
|
: $credit->podcast->title,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
@ -152,27 +150,26 @@ class CreditsController extends BaseController
|
||||||
: $credit->podcast->link,
|
: $credit->podcast->link,
|
||||||
'title' => $credit->episode_id
|
'title' => $credit->episode_id
|
||||||
? (count($allPodcasts) > 1
|
? (count($allPodcasts) > 1
|
||||||
? esc($credit->podcast->title) . ' › '
|
? "{$credit->podcast->title} › "
|
||||||
: '') .
|
: '') .
|
||||||
esc($credit->episode->title) .
|
$credit->episode->title .
|
||||||
episode_numbering(
|
episode_numbering(
|
||||||
$credit->episode->number,
|
$credit->episode->number,
|
||||||
$credit->episode->season_number,
|
$credit->episode->season_number,
|
||||||
'text-xs ml-2',
|
'text-xs ml-2',
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
: esc($credit->podcast->title),
|
: $credit->podcast->title,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
set_page_metatags($page);
|
|
||||||
$data = [
|
$data = [
|
||||||
'page' => $page,
|
'page' => $page,
|
||||||
'credits' => $credits,
|
'credits' => $credits,
|
||||||
];
|
];
|
||||||
|
|
||||||
$found = view('pages/credits', $data);
|
$found = view('credits', $data);
|
||||||
|
|
||||||
cache()
|
cache()
|
||||||
->save($cacheName, $found, DECADE);
|
->save($cacheName, $found, DECADE);
|
||||||
|
|
|
||||||
|
|
@ -1,174 +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\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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,184 +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\Entities\Episode;
|
|
||||||
use App\Entities\EpisodeComment;
|
|
||||||
use App\Entities\Podcast;
|
|
||||||
use App\Libraries\CommentObject;
|
|
||||||
use App\Models\EpisodeCommentModel;
|
|
||||||
use App\Models\EpisodeModel;
|
|
||||||
use App\Models\PodcastModel;
|
|
||||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
|
||||||
use CodeIgniter\HTTP\RedirectResponse;
|
|
||||||
use CodeIgniter\HTTP\ResponseInterface;
|
|
||||||
use Modules\Analytics\AnalyticsTrait;
|
|
||||||
use Modules\Fediverse\Entities\Actor;
|
|
||||||
use Modules\Fediverse\Objects\OrderedCollectionObject;
|
|
||||||
use Modules\Fediverse\Objects\OrderedCollectionPage;
|
|
||||||
|
|
||||||
class EpisodeCommentController extends BaseController
|
|
||||||
{
|
|
||||||
use AnalyticsTrait;
|
|
||||||
|
|
||||||
protected Podcast $podcast;
|
|
||||||
|
|
||||||
protected Actor $actor;
|
|
||||||
|
|
||||||
protected Episode $episode;
|
|
||||||
|
|
||||||
protected EpisodeComment $comment;
|
|
||||||
|
|
||||||
public function _remap(string $method, string ...$params): mixed
|
|
||||||
{
|
|
||||||
if (count($params) < 3) {
|
|
||||||
throw PageNotFoundException::forPageNotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
! ($podcast = new PodcastModel()->getPodcastByHandle($params[0])) instanceof Podcast
|
|
||||||
) {
|
|
||||||
throw PageNotFoundException::forPageNotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->podcast = $podcast;
|
|
||||||
$this->actor = $podcast->actor;
|
|
||||||
|
|
||||||
if (
|
|
||||||
! ($episode = new EpisodeModel()->getEpisodeBySlug($params[0], $params[1])) instanceof Episode
|
|
||||||
) {
|
|
||||||
throw PageNotFoundException::forPageNotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->episode = $episode;
|
|
||||||
|
|
||||||
if (
|
|
||||||
! ($comment = new EpisodeCommentModel()->getCommentById($params[2])) instanceof EpisodeComment
|
|
||||||
) {
|
|
||||||
throw PageNotFoundException::forPageNotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->comment = $comment;
|
|
||||||
|
|
||||||
unset($params[2]);
|
|
||||||
unset($params[1]);
|
|
||||||
unset($params[0]);
|
|
||||||
|
|
||||||
return $this->{$method}(...$params);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function view(): string
|
|
||||||
{
|
|
||||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
|
||||||
|
|
||||||
$cacheName = implode(
|
|
||||||
'_',
|
|
||||||
array_filter([
|
|
||||||
'page',
|
|
||||||
"episode#{$this->episode->id}",
|
|
||||||
"comment#{$this->comment->id}",
|
|
||||||
service('request')
|
|
||||||
->getLocale(),
|
|
||||||
auth()
|
|
||||||
->loggedIn() ? 'authenticated' : null,
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (! ($cachedView = cache($cacheName))) {
|
|
||||||
set_episode_comment_metatags($this->comment);
|
|
||||||
$data = [
|
|
||||||
'podcast' => $this->podcast,
|
|
||||||
'actor' => $this->actor,
|
|
||||||
'episode' => $this->episode,
|
|
||||||
'comment' => $this->comment,
|
|
||||||
];
|
|
||||||
|
|
||||||
// if user is logged in then send to the authenticated activity view
|
|
||||||
if (auth()->loggedIn()) {
|
|
||||||
helper('form');
|
|
||||||
return view('episode/comment', $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return view('episode/comment', $data, [
|
|
||||||
'cache' => DECADE,
|
|
||||||
'cache_name' => $cacheName,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $cachedView;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function commentObject(): ResponseInterface
|
|
||||||
{
|
|
||||||
$commentObject = new CommentObject($this->comment);
|
|
||||||
|
|
||||||
return $this->response
|
|
||||||
->setContentType('application/json')
|
|
||||||
->setBody($commentObject->toJSON());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function replies(): ResponseInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* get comment replies
|
|
||||||
*/
|
|
||||||
$commentReplies = model(EpisodeCommentModel::class, false)
|
|
||||||
->where('in_reply_to_id', service('uuid')->fromString($this->comment->id)->getBytes())
|
|
||||||
->orderBy('created_at', 'ASC');
|
|
||||||
|
|
||||||
$pageNumber = (int) $this->request->getGet('page');
|
|
||||||
|
|
||||||
if ($pageNumber < 1) {
|
|
||||||
$commentReplies->paginate(12);
|
|
||||||
$pager = $commentReplies->pager;
|
|
||||||
$collection = new OrderedCollectionObject(null, $pager);
|
|
||||||
} else {
|
|
||||||
$paginatedReplies = $commentReplies->paginate(12, 'default', $pageNumber);
|
|
||||||
$pager = $commentReplies->pager;
|
|
||||||
|
|
||||||
$orderedItems = [];
|
|
||||||
foreach ($paginatedReplies as $reply) {
|
|
||||||
$replyObject = new CommentObject($reply);
|
|
||||||
$orderedItems[] = $replyObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
$collection = new OrderedCollectionPage($pager, $orderedItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->response
|
|
||||||
->setContentType('application/activity+json')
|
|
||||||
->setBody($collection->toJSON());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function likeAction(): RedirectResponse
|
|
||||||
{
|
|
||||||
if (! ($interactAsActor = interact_as_actor()) instanceof Actor) {
|
|
||||||
return redirect()->back();
|
|
||||||
}
|
|
||||||
|
|
||||||
model('LikeModel')
|
|
||||||
->toggleLike($interactAsActor, $this->comment);
|
|
||||||
|
|
||||||
return redirect()->back();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function replyAction(): RedirectResponse
|
|
||||||
{
|
|
||||||
if (! ($interactAsActor = interact_as_actor()) instanceof Actor) {
|
|
||||||
return redirect()->back();
|
|
||||||
}
|
|
||||||
|
|
||||||
model('LikeModel')
|
|
||||||
->toggleLike($interactAsActor, $this->comment);
|
|
||||||
|
|
||||||
return redirect()->back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,13 +3,16 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @copyright 2020 Ad Aures
|
* @copyright 2020 Podlibre
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
|
use ActivityPub\Objects\OrderedCollectionObject;
|
||||||
|
use ActivityPub\Objects\OrderedCollectionPage;
|
||||||
|
use Analytics\AnalyticsTrait;
|
||||||
use App\Entities\Episode;
|
use App\Entities\Episode;
|
||||||
use App\Entities\Podcast;
|
use App\Entities\Podcast;
|
||||||
use App\Libraries\NoteObject;
|
use App\Libraries\NoteObject;
|
||||||
|
|
@ -18,12 +21,9 @@ use App\Models\EpisodeModel;
|
||||||
use App\Models\PodcastModel;
|
use App\Models\PodcastModel;
|
||||||
use CodeIgniter\Database\BaseBuilder;
|
use CodeIgniter\Database\BaseBuilder;
|
||||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||||
|
use CodeIgniter\HTTP\Response;
|
||||||
use CodeIgniter\HTTP\ResponseInterface;
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
use Config\Embed;
|
use Config\Services;
|
||||||
use Modules\Analytics\AnalyticsTrait;
|
|
||||||
use Modules\Fediverse\Objects\OrderedCollectionObject;
|
|
||||||
use Modules\Fediverse\Objects\OrderedCollectionPage;
|
|
||||||
use Modules\Media\FileManagers\FileManagerInterface;
|
|
||||||
use SimpleXMLElement;
|
use SimpleXMLElement;
|
||||||
|
|
||||||
class EpisodeController extends BaseController
|
class EpisodeController extends BaseController
|
||||||
|
|
@ -41,7 +41,7 @@ class EpisodeController extends BaseController
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
! ($podcast = new PodcastModel()->getPodcastByHandle($params[0])) instanceof Podcast
|
($podcast = (new PodcastModel())->getPodcastByName($params[0])) === null
|
||||||
) {
|
) {
|
||||||
throw PageNotFoundException::forPageNotFound();
|
throw PageNotFoundException::forPageNotFound();
|
||||||
}
|
}
|
||||||
|
|
@ -49,7 +49,7 @@ class EpisodeController extends BaseController
|
||||||
$this->podcast = $podcast;
|
$this->podcast = $podcast;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
! ($episode = new EpisodeModel()->getEpisodeBySlug($params[0], $params[1])) instanceof Episode
|
($episode = (new EpisodeModel())->getEpisodeBySlug($params[0], $params[1])) === null
|
||||||
) {
|
) {
|
||||||
throw PageNotFoundException::forPageNotFound();
|
throw PageNotFoundException::forPageNotFound();
|
||||||
}
|
}
|
||||||
|
|
@ -64,41 +64,36 @@ class EpisodeController extends BaseController
|
||||||
|
|
||||||
public function index(): string
|
public function index(): string
|
||||||
{
|
{
|
||||||
$this->registerPodcastWebpageHit($this->episode->podcast_id);
|
// Prevent analytics hit when authenticated
|
||||||
|
if (! can_user_interact()) {
|
||||||
|
$this->registerPodcastWebpageHit($this->episode->podcast_id);
|
||||||
|
}
|
||||||
|
|
||||||
$cacheName = implode(
|
$locale = service('request')
|
||||||
'_',
|
->getLocale();
|
||||||
array_filter([
|
$cacheName =
|
||||||
'page',
|
"page_podcast#{$this->podcast->id}_episode#{$this->episode->id}_{$locale}" .
|
||||||
"podcast#{$this->podcast->id}",
|
(can_user_interact() ? '_authenticated' : '');
|
||||||
"episode#{$this->episode->id}",
|
|
||||||
service('request')
|
|
||||||
->getLocale(),
|
|
||||||
is_unlocked($this->podcast->handle) ? 'unlocked' : null,
|
|
||||||
auth()
|
|
||||||
->loggedIn() ? 'authenticated' : null,
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (! ($cachedView = cache($cacheName))) {
|
if (! ($cachedView = cache($cacheName))) {
|
||||||
set_episode_metatags($this->episode);
|
|
||||||
$data = [
|
$data = [
|
||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
'episode' => $this->episode,
|
'episode' => $this->episode,
|
||||||
];
|
];
|
||||||
|
|
||||||
$secondsToNextUnpublishedEpisode = new EpisodeModel()
|
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
|
||||||
->getSecondsToNextUnpublishedEpisode($this->podcast->id);
|
$this->podcast->id,
|
||||||
|
);
|
||||||
|
|
||||||
if (auth()->loggedIn()) {
|
if (can_user_interact()) {
|
||||||
helper('form');
|
helper('form');
|
||||||
|
return view('podcast/episode_authenticated', $data);
|
||||||
return view('episode/comments', $data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The page cache is set to a decade so it is deleted manually upon podcast update
|
// The page cache is set to a decade so it is deleted manually upon podcast update
|
||||||
return view('episode/comments', $data, [
|
return view('podcast/episode', $data, [
|
||||||
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
|
'cache' => $secondsToNextUnpublishedEpisode
|
||||||
|
? $secondsToNextUnpublishedEpisode
|
||||||
|
: DECADE,
|
||||||
'cache_name' => $cacheName,
|
'cache_name' => $cacheName,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
@ -106,208 +101,44 @@ class EpisodeController extends BaseController
|
||||||
return $cachedView;
|
return $cachedView;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function activity(): string
|
public function embeddablePlayer(string $theme = 'light-transparent'): string
|
||||||
{
|
{
|
||||||
$this->registerPodcastWebpageHit($this->episode->podcast_id);
|
header('Content-Security-Policy: frame-ancestors https://* http://*');
|
||||||
|
|
||||||
$cacheName = implode(
|
// Prevent analytics hit when authenticated
|
||||||
'_',
|
if (! can_user_interact()) {
|
||||||
array_filter([
|
$this->registerPodcastWebpageHit($this->episode->podcast_id);
|
||||||
'page',
|
}
|
||||||
"podcast#{$this->podcast->id}",
|
|
||||||
"episode#{$this->episode->id}",
|
$session = Services::session();
|
||||||
'activity',
|
$session->start();
|
||||||
service('request')
|
if (isset($_SERVER['HTTP_REFERER'])) {
|
||||||
->getLocale(),
|
$session->set('embeddable_player_domain', parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST));
|
||||||
is_unlocked($this->podcast->handle) ? 'unlocked' : null,
|
}
|
||||||
auth()
|
|
||||||
->loggedIn() ? 'authenticated' : null,
|
$locale = service('request')
|
||||||
]),
|
->getLocale();
|
||||||
);
|
|
||||||
|
$cacheName = "page_podcast#{$this->podcast->id}_episode#{$this->episode->id}_embeddable_player_{$theme}_{$locale}";
|
||||||
|
|
||||||
if (! ($cachedView = cache($cacheName))) {
|
if (! ($cachedView = cache($cacheName))) {
|
||||||
set_episode_metatags($this->episode);
|
$theme = EpisodeModel::$themes[$theme];
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
'episode' => $this->episode,
|
'episode' => $this->episode,
|
||||||
|
'theme' => $theme,
|
||||||
];
|
];
|
||||||
|
|
||||||
$secondsToNextUnpublishedEpisode = new EpisodeModel()
|
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
|
||||||
->getSecondsToNextUnpublishedEpisode($this->podcast->id);
|
$this->podcast->id,
|
||||||
|
);
|
||||||
if (auth()->loggedIn()) {
|
|
||||||
helper('form');
|
|
||||||
|
|
||||||
return view('episode/activity', $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The page cache is set to a decade so it is deleted manually upon podcast update
|
// The page cache is set to a decade so it is deleted manually upon podcast update
|
||||||
return view('episode/activity', $data, [
|
return view('embeddable_player', $data, [
|
||||||
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
|
'cache' => $secondsToNextUnpublishedEpisode
|
||||||
'cache_name' => $cacheName,
|
? $secondsToNextUnpublishedEpisode
|
||||||
]);
|
: DECADE,
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $cachedView;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function embed(string $theme = 'light-transparent'): string
|
|
||||||
{
|
|
||||||
header('Content-Security-Policy: frame-ancestors http://*:* https://*:*');
|
|
||||||
|
|
||||||
$this->registerPodcastWebpageHit($this->episode->podcast_id);
|
|
||||||
|
|
||||||
$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(
|
|
||||||
'_',
|
|
||||||
array_filter([
|
|
||||||
'page',
|
|
||||||
"podcast#{$this->podcast->id}",
|
|
||||||
"episode#{$this->episode->id}",
|
|
||||||
'embed',
|
|
||||||
$theme,
|
|
||||||
service('request')
|
|
||||||
->getLocale(),
|
|
||||||
is_unlocked($this->podcast->handle) ? 'unlocked' : null,
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (! ($cachedView = cache($cacheName))) {
|
|
||||||
$themeData = EpisodeModel::$themes[$theme];
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'podcast' => $this->podcast,
|
|
||||||
'episode' => $this->episode,
|
|
||||||
'theme' => $theme,
|
|
||||||
'themeData' => $themeData,
|
|
||||||
];
|
|
||||||
|
|
||||||
$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 ?: DECADE,
|
|
||||||
'cache_name' => $cacheName,
|
'cache_name' => $cacheName,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
@ -318,25 +149,24 @@ class EpisodeController extends BaseController
|
||||||
public function oembedJSON(): ResponseInterface
|
public function oembedJSON(): ResponseInterface
|
||||||
{
|
{
|
||||||
return $this->response->setJSON([
|
return $this->response->setJSON([
|
||||||
'type' => 'rich',
|
'type' => 'rich',
|
||||||
'version' => '1.0',
|
'version' => '1.0',
|
||||||
'title' => $this->episode->title,
|
'title' => $this->episode->title,
|
||||||
'provider_name' => $this->podcast->title,
|
'provider_name' => $this->podcast->title,
|
||||||
'provider_url' => $this->podcast->link,
|
'provider_url' => $this->podcast->link,
|
||||||
'author_name' => $this->podcast->title,
|
'author_name' => $this->podcast->title,
|
||||||
'author_url' => $this->podcast->link,
|
'author_url' => $this->podcast->link,
|
||||||
'html' => '<iframe src="' .
|
'html' =>
|
||||||
$this->episode->embed_url .
|
'<iframe src="' .
|
||||||
'" width="100%" height="' . config('Embed')->height . '" frameborder="0" scrolling="no"></iframe>',
|
$this->episode->embeddable_player_url .
|
||||||
'width' => config('Embed')
|
'" width="100%" height="200" frameborder="0" scrolling="no"></iframe>',
|
||||||
->width,
|
'width' => 600,
|
||||||
'height' => config('Embed')
|
'height' => 200,
|
||||||
->height,
|
'thumbnail_url' => $this->episode->image->large_url,
|
||||||
'thumbnail_url' => $this->episode->cover->og_url,
|
|
||||||
'thumbnail_width' => config('Images')
|
'thumbnail_width' => config('Images')
|
||||||
->podcastCoverSizes['og']['width'],
|
->largeSize,
|
||||||
'thumbnail_height' => config('Images')
|
'thumbnail_height' => config('Images')
|
||||||
->podcastCoverSizes['og']['height'],
|
->largeSize,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -351,27 +181,27 @@ class EpisodeController extends BaseController
|
||||||
$oembed->addChild('provider_url', $this->podcast->link);
|
$oembed->addChild('provider_url', $this->podcast->link);
|
||||||
$oembed->addChild('author_name', $this->podcast->title);
|
$oembed->addChild('author_name', $this->podcast->title);
|
||||||
$oembed->addChild('author_url', $this->podcast->link);
|
$oembed->addChild('author_url', $this->podcast->link);
|
||||||
$oembed->addChild('thumbnail', $this->episode->cover->og_url);
|
$oembed->addChild('thumbnail', $this->episode->image->large_url);
|
||||||
$oembed->addChild('thumbnail_width', (string) config('Images')->podcastCoverSizes['og']['width']);
|
$oembed->addChild('thumbnail_width', config('Images')->largeSize);
|
||||||
$oembed->addChild('thumbnail_height', (string) config('Images')->podcastCoverSizes['og']['height']);
|
$oembed->addChild('thumbnail_height', config('Images')->largeSize);
|
||||||
$oembed->addChild(
|
$oembed->addChild(
|
||||||
'html',
|
'html',
|
||||||
htmlspecialchars(
|
htmlentities(
|
||||||
'<iframe src="' .
|
'<iframe src="' .
|
||||||
$this->episode->embed_url .
|
$this->episode->embeddable_player_url .
|
||||||
'" width="100%" height="' . config(
|
'" width="100%" height="200" frameborder="0" scrolling="no"></iframe>',
|
||||||
Embed::class,
|
|
||||||
)->height . '" frameborder="0" scrolling="no"></iframe>',
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
$oembed->addChild('width', (string) config('Embed')->width);
|
$oembed->addChild('width', '600');
|
||||||
$oembed->addChild('height', (string) config('Embed')->height);
|
$oembed->addChild('height', '200');
|
||||||
|
|
||||||
// @phpstan-ignore-next-line
|
return $this->response->setXML((string) $oembed);
|
||||||
return $this->response->setXML($oembed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function episodeObject(): ResponseInterface
|
/**
|
||||||
|
* @noRector ReturnTypeDeclarationRector
|
||||||
|
*/
|
||||||
|
public function episodeObject(): Response
|
||||||
{
|
{
|
||||||
$podcastObject = new PodcastEpisode($this->episode);
|
$podcastObject = new PodcastEpisode($this->episode);
|
||||||
|
|
||||||
|
|
@ -380,16 +210,21 @@ class EpisodeController extends BaseController
|
||||||
->setBody($podcastObject->toJSON());
|
->setBody($podcastObject->toJSON());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function comments(): ResponseInterface
|
/**
|
||||||
|
* @noRector ReturnTypeDeclarationRector
|
||||||
|
*/
|
||||||
|
public function comments(): Response
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* get comments: aggregated replies from posts referring to the episode
|
* get comments: aggregated replies from posts referring to the episode
|
||||||
*/
|
*/
|
||||||
$episodeComments = model('PostModel')
|
$episodeComments = model('StatusModel')
|
||||||
->whereIn('in_reply_to_id', fn (BaseBuilder $builder): BaseBuilder => $builder->select('id')
|
->whereIn('in_reply_to_id', function (BaseBuilder $builder): BaseBuilder {
|
||||||
->from('fediverse_posts')
|
return $builder->select('id')
|
||||||
->where('episode_id', $this->episode->id))
|
->from('activitypub_statuses')
|
||||||
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
|
->where('episode_id', $this->episode->id);
|
||||||
|
})
|
||||||
|
->where('`published_at` <= NOW()', null, false)
|
||||||
->orderBy('published_at', 'ASC');
|
->orderBy('published_at', 'ASC');
|
||||||
|
|
||||||
$pageNumber = (int) $this->request->getGet('page');
|
$pageNumber = (int) $this->request->getGet('page');
|
||||||
|
|
@ -403,8 +238,10 @@ class EpisodeController extends BaseController
|
||||||
$pager = $episodeComments->pager;
|
$pager = $episodeComments->pager;
|
||||||
|
|
||||||
$orderedItems = [];
|
$orderedItems = [];
|
||||||
foreach ($paginatedComments as $comment) {
|
if ($paginatedComments !== null) {
|
||||||
$orderedItems[] = new NoteObject($comment)->toArray();
|
foreach ($paginatedComments as $comment) {
|
||||||
|
$orderedItems[] = (new NoteObject($comment))->toArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @phpstan-ignore-next-line
|
// @phpstan-ignore-next-line
|
||||||
|
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
<?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,59 +3,36 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @copyright 2022 Ad Aures
|
* @copyright 2020 Podlibre
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
use App\Entities\Podcast;
|
|
||||||
use App\Models\EpisodeModel;
|
use App\Models\EpisodeModel;
|
||||||
use App\Models\PodcastModel;
|
use App\Models\PodcastModel;
|
||||||
use CodeIgniter\Controller;
|
use CodeIgniter\Controller;
|
||||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||||
use CodeIgniter\HTTP\IncomingRequest;
|
|
||||||
use CodeIgniter\HTTP\ResponseInterface;
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Modules\PremiumPodcasts\Entities\Subscription;
|
use Opawg\UserAgentsPhp\UserAgentsRSS;
|
||||||
use Modules\PremiumPodcasts\Models\SubscriptionModel;
|
|
||||||
use Opawg\UserAgentsV2Php\UserAgentsRSS;
|
|
||||||
|
|
||||||
class FeedController extends Controller
|
class FeedController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
public function index(string $podcastName): ResponseInterface
|
||||||
* Instance of the main Request object.
|
|
||||||
*
|
|
||||||
* @var IncomingRequest
|
|
||||||
*/
|
|
||||||
protected $request;
|
|
||||||
|
|
||||||
public function index(string $podcastHandle): ResponseInterface
|
|
||||||
{
|
{
|
||||||
$podcast = new PodcastModel()
|
helper('rss');
|
||||||
->where('handle', $podcastHandle)
|
|
||||||
|
$podcast = (new PodcastModel())->where('name', $podcastName)
|
||||||
->first();
|
->first();
|
||||||
if (! $podcast instanceof Podcast) {
|
if (! $podcast) {
|
||||||
throw PageNotFoundException::forPageNotFound();
|
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;
|
$service = null;
|
||||||
try {
|
try {
|
||||||
$service = UserAgentsRSS::find(service('superglobals')->server('HTTP_USER_AGENT'));
|
$service = UserAgentsRSS::find($_SERVER['HTTP_USER_AGENT']);
|
||||||
} catch (Exception $exception) {
|
} catch (Exception $exception) {
|
||||||
// If things go wrong the show must go on and the user must be able to download the file
|
// If things go wrong the show must go on and the user must be able to download the file
|
||||||
log_message('critical', $exception->getMessage());
|
log_message('critical', $exception->getMessage());
|
||||||
|
|
@ -66,32 +43,25 @@ class FeedController extends Controller
|
||||||
$serviceSlug = $service['slug'];
|
$serviceSlug = $service['slug'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$subscription = null;
|
$cacheName =
|
||||||
$token = $this->request->getGet('token');
|
"podcast#{$podcast->id}_feed" . ($service ? "_{$serviceSlug}" : '');
|
||||||
if ($token) {
|
|
||||||
$subscription = new SubscriptionModel()
|
|
||||||
->validateSubscription($podcastHandle, $token);
|
|
||||||
}
|
|
||||||
|
|
||||||
$cacheName = implode(
|
|
||||||
'_',
|
|
||||||
array_filter([
|
|
||||||
"podcast#{$podcast->id}",
|
|
||||||
'feed',
|
|
||||||
$service ? $serviceSlug : null,
|
|
||||||
$subscription instanceof Subscription ? "subscription#{$subscription->id}" : null,
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (! ($found = cache($cacheName))) {
|
if (! ($found = cache($cacheName))) {
|
||||||
$found = get_rss_feed($podcast, $serviceSlug, $subscription, $token);
|
$found = get_rss_feed($podcast, $serviceSlug);
|
||||||
|
|
||||||
// The page cache is set to expire after next episode publication or a decade by default so it is deleted manually upon podcast update
|
// 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()
|
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
|
||||||
->getSecondsToNextUnpublishedEpisode($podcast->id);
|
$podcast->id,
|
||||||
|
);
|
||||||
|
|
||||||
cache()
|
cache()
|
||||||
->save($cacheName, $found, $secondsToNextUnpublishedEpisode ?: DECADE);
|
->save(
|
||||||
|
$cacheName,
|
||||||
|
$found,
|
||||||
|
$secondsToNextUnpublishedEpisode
|
||||||
|
? $secondsToNextUnpublishedEpisode
|
||||||
|
: DECADE,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->response->setXML($found);
|
return $this->response->setXML($found);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @copyright 2020 Ad Aures
|
* @copyright 2020 Podlibre
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
@ -11,74 +11,33 @@ declare(strict_types=1);
|
||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
use App\Models\PodcastModel;
|
use App\Models\PodcastModel;
|
||||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
|
||||||
use CodeIgniter\HTTP\RedirectResponse;
|
use CodeIgniter\HTTP\RedirectResponse;
|
||||||
use CodeIgniter\HTTP\ResponseInterface;
|
use Config\Services;
|
||||||
use Modules\Media\FileManagers\FileManagerInterface;
|
|
||||||
|
|
||||||
class HomeController extends BaseController
|
class HomeController extends BaseController
|
||||||
{
|
{
|
||||||
public function index(): RedirectResponse | string
|
public function index(): RedirectResponse | string
|
||||||
{
|
{
|
||||||
$sortOptions = ['activity', 'created_desc', 'created_asc'];
|
$db = db_connect();
|
||||||
$sortBy = in_array($this->request->getGet('sort'), $sortOptions, true) ? $this->request->getGet(
|
if ($db->getDatabase() === '' || ! $db->tableExists('podcasts')) {
|
||||||
'sort',
|
// Database connection has not been set or could not find the podcasts table
|
||||||
) : 'activity';
|
// Redirecting to install page because it is likely that Castopod Host 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);
|
||||||
|
}
|
||||||
|
|
||||||
$allPodcasts = new PodcastModel()
|
$allPodcasts = (new PodcastModel())->findAll();
|
||||||
->getAllPodcasts($sortBy);
|
|
||||||
|
|
||||||
// check if there's only one podcast to redirect user to it
|
// check if there's only one podcast to redirect user to it
|
||||||
if (count($allPodcasts) === 1) {
|
if (count($allPodcasts) === 1) {
|
||||||
return redirect()->route('podcast-activity', [$allPodcasts[0]->handle]);
|
return redirect()->route('podcast-activity', [$allPodcasts[0]->name]);
|
||||||
}
|
}
|
||||||
|
|
||||||
set_home_metatags();
|
|
||||||
// default behavior: list all podcasts on home page
|
// default behavior: list all podcasts on home page
|
||||||
$data = [
|
$data = [
|
||||||
'podcasts' => $allPodcasts,
|
'podcasts' => $allPodcasts,
|
||||||
'sortBy' => $sortBy,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return view('home', $data);
|
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!',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
364
app/Controllers/InstallController.php
Normal file
364
app/Controllers/InstallController.php
Normal file
|
|
@ -0,0 +1,364 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Controllers;
|
||||||
|
|
||||||
|
use App\Entities\User;
|
||||||
|
use App\Models\UserModel;
|
||||||
|
use CodeIgniter\Controller;
|
||||||
|
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||||
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||||
|
use CodeIgniter\HTTP\RedirectResponse;
|
||||||
|
use CodeIgniter\HTTP\RequestInterface;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
|
use Config\Database;
|
||||||
|
use Config\Services;
|
||||||
|
use Dotenv\Dotenv;
|
||||||
|
use Dotenv\Exception\ValidationException;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class InstallController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $helpers = ['form', 'components', 'svg'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public function initController(
|
||||||
|
RequestInterface $request,
|
||||||
|
ResponseInterface $response,
|
||||||
|
LoggerInterface $logger
|
||||||
|
): void {
|
||||||
|
// Do Not Edit This Line
|
||||||
|
parent::initController($request, $response, $logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Every operation goes through this method to handle the install logic.
|
||||||
|
*
|
||||||
|
* If all required actions have already been performed, the install route will show a 404 page.
|
||||||
|
*/
|
||||||
|
public function index(): string
|
||||||
|
{
|
||||||
|
if (! file_exists(ROOTPATH . '.env')) {
|
||||||
|
// create empty .env file
|
||||||
|
try {
|
||||||
|
$envFile = fopen(ROOTPATH . '.env', 'w');
|
||||||
|
fclose($envFile);
|
||||||
|
} catch (Throwable) {
|
||||||
|
// Could not create the .env file, redirect to a view with instructions on how to add it manually
|
||||||
|
return view('install/manual_config');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if .env has all required fields
|
||||||
|
$dotenv = Dotenv::createUnsafeImmutable(ROOTPATH);
|
||||||
|
$dotenv->load();
|
||||||
|
|
||||||
|
// Check if the created .env file is writable to continue install process
|
||||||
|
if (is_really_writable(ROOTPATH . '.env')) {
|
||||||
|
try {
|
||||||
|
$dotenv->required(['app.baseURL', 'app.adminGateway', 'app.authGateway']);
|
||||||
|
} catch (ValidationException) {
|
||||||
|
// form to input instance configuration
|
||||||
|
return $this->instanceConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$dotenv->required([
|
||||||
|
'database.default.hostname',
|
||||||
|
'database.default.database',
|
||||||
|
'database.default.username',
|
||||||
|
'database.default.password',
|
||||||
|
'database.default.DBPrefix',
|
||||||
|
]);
|
||||||
|
} catch (ValidationException) {
|
||||||
|
return $this->databaseConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$dotenv->required('cache.handler');
|
||||||
|
} catch (ValidationException) {
|
||||||
|
return $this->cacheConfig();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
$dotenv->required([
|
||||||
|
'app.baseURL',
|
||||||
|
'app.adminGateway',
|
||||||
|
'app.authGateway',
|
||||||
|
'database.default.hostname',
|
||||||
|
'database.default.database',
|
||||||
|
'database.default.username',
|
||||||
|
'database.default.password',
|
||||||
|
'database.default.DBPrefix',
|
||||||
|
'cache.handler',
|
||||||
|
]);
|
||||||
|
} catch (ValidationException) {
|
||||||
|
return view('install/manual_config');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = db_connect();
|
||||||
|
|
||||||
|
// Check if superadmin has been created, meaning migrations and seeds have passed
|
||||||
|
if (
|
||||||
|
$db->tableExists('users') &&
|
||||||
|
(new UserModel())->countAll() > 0
|
||||||
|
) {
|
||||||
|
// if so, show a 404 page
|
||||||
|
throw PageNotFoundException::forPageNotFound();
|
||||||
|
}
|
||||||
|
} catch (DatabaseException) {
|
||||||
|
// Could not connect to the database
|
||||||
|
// show database config view to fix value
|
||||||
|
session()
|
||||||
|
->setFlashdata('error', lang('Install.messages.databaseConnectError'));
|
||||||
|
|
||||||
|
return view('install/database_config');
|
||||||
|
}
|
||||||
|
|
||||||
|
// migrate if no user has been created
|
||||||
|
$this->migrate();
|
||||||
|
|
||||||
|
// Check if all seeds have succeeded
|
||||||
|
$this->seed();
|
||||||
|
|
||||||
|
return $this->createSuperAdmin();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function instanceConfig(): string
|
||||||
|
{
|
||||||
|
return view('install/instance_config');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptInstanceConfig(): RedirectResponse
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'hostname' => 'required|validate_url',
|
||||||
|
'media_base_url' => 'permit_empty|validate_url',
|
||||||
|
'admin_gateway' => 'required',
|
||||||
|
'auth_gateway' => 'required|differs[admin_gateway]',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->to((host_url() === null ? config('App') ->baseURL : host_url()) . config('App')->installGateway)
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$baseUrl = $this->request->getPost('hostname');
|
||||||
|
$mediaBaseUrl = $this->request->getPost('media_base_url');
|
||||||
|
self::writeEnv([
|
||||||
|
'app.baseURL' => $baseUrl,
|
||||||
|
'app.mediaBaseURL' =>
|
||||||
|
$mediaBaseUrl === '' ? $baseUrl : $mediaBaseUrl,
|
||||||
|
'app.adminGateway' => $this->request->getPost('admin_gateway'),
|
||||||
|
'app.authGateway' => $this->request->getPost('auth_gateway'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
helper('text');
|
||||||
|
|
||||||
|
// redirect to full install url with new baseUrl input
|
||||||
|
return redirect()->to(reduce_double_slashes($baseUrl . '/' . config('App')->installGateway));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function databaseConfig(): string
|
||||||
|
{
|
||||||
|
return view('install/database_config');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptDatabaseConfig(): RedirectResponse
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'db_hostname' => 'required',
|
||||||
|
'db_name' => 'required',
|
||||||
|
'db_username' => 'required',
|
||||||
|
'db_password' => 'required',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
self::writeEnv([
|
||||||
|
'database.default.hostname' => $this->request->getPost('db_hostname'),
|
||||||
|
'database.default.database' => $this->request->getPost('db_name'),
|
||||||
|
'database.default.username' => $this->request->getPost('db_username'),
|
||||||
|
'database.default.password' => $this->request->getPost('db_password'),
|
||||||
|
'database.default.DBPrefix' => $this->request->getPost('db_prefix'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cacheConfig(): string
|
||||||
|
{
|
||||||
|
return view('install/cache_config');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptCacheConfig(): RedirectResponse
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'cache_handler' => 'required',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
self::writeEnv([
|
||||||
|
'cache.handler' => $this->request->getPost('cache_handler'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs all database migrations required for instance.
|
||||||
|
*/
|
||||||
|
public function migrate(): void
|
||||||
|
{
|
||||||
|
$migrations = Services::migrations();
|
||||||
|
|
||||||
|
$migrations->setNamespace('Myth\Auth')
|
||||||
|
->latest();
|
||||||
|
$migrations->setNamespace('ActivityPub')
|
||||||
|
->latest();
|
||||||
|
$migrations->setNamespace('Analytics')
|
||||||
|
->latest();
|
||||||
|
$migrations->setNamespace(APP_NAMESPACE)
|
||||||
|
->latest();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs all database seeds required for instance.
|
||||||
|
*/
|
||||||
|
public function seed(): void
|
||||||
|
{
|
||||||
|
$seeder = Database::seeder();
|
||||||
|
|
||||||
|
// Seed database
|
||||||
|
$seeder->call('AppSeeder');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the form to create a the first superadmin user for the instance.
|
||||||
|
*/
|
||||||
|
public function createSuperAdmin(): string
|
||||||
|
{
|
||||||
|
return view('install/create_superadmin');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the first superadmin user or redirects back to form if any error.
|
||||||
|
*
|
||||||
|
* After creation, user is redirected to login page to input its credentials.
|
||||||
|
*/
|
||||||
|
public function attemptCreateSuperAdmin(): RedirectResponse
|
||||||
|
{
|
||||||
|
$userModel = new UserModel();
|
||||||
|
|
||||||
|
// Validate here first, since some things,
|
||||||
|
// like the password, can only be validated properly here.
|
||||||
|
$rules = array_merge(
|
||||||
|
$userModel->getValidationRules([
|
||||||
|
'only' => ['username'],
|
||||||
|
]),
|
||||||
|
[
|
||||||
|
'email' => 'required|valid_email|is_unique[users.email]',
|
||||||
|
'password' => 'required|strong_password',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the user
|
||||||
|
$user = new User($this->request->getPost());
|
||||||
|
|
||||||
|
// Activate user
|
||||||
|
$user->activate();
|
||||||
|
|
||||||
|
$db = db_connect();
|
||||||
|
|
||||||
|
$db->transStart();
|
||||||
|
if (! ($userId = $userModel->insert($user, true))) {
|
||||||
|
$db->transRollback();
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $userModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
// add newly created user to superadmin group
|
||||||
|
$authorization = Services::authorization();
|
||||||
|
$authorization->addUserToGroup($userId, 'superadmin');
|
||||||
|
|
||||||
|
$db->transComplete();
|
||||||
|
|
||||||
|
// Success!
|
||||||
|
// set redirect_url session as admin area to go to after login
|
||||||
|
session()
|
||||||
|
->set('redirect_url', route_to('admin'));
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->route('login')
|
||||||
|
->with('message', lang('Install.messages.createSuperAdminSuccess'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* writes config values in .env file overwrites any existing key and appends new ones
|
||||||
|
*
|
||||||
|
* @param array<string, string> $configData key/value config pairs
|
||||||
|
*/
|
||||||
|
public static function writeEnv(array $configData): void
|
||||||
|
{
|
||||||
|
$envData = file(ROOTPATH . '.env'); // reads an array of lines
|
||||||
|
|
||||||
|
foreach ($configData as $key => $value) {
|
||||||
|
$replaced = false;
|
||||||
|
$keyVal = $key . '="' . $value . '"' . PHP_EOL;
|
||||||
|
$envData = array_map(
|
||||||
|
function ($line) use ($key, $keyVal, &$replaced) {
|
||||||
|
if (str_starts_with($line, $key)) {
|
||||||
|
$replaced = true;
|
||||||
|
return $keyVal;
|
||||||
|
}
|
||||||
|
return $line;
|
||||||
|
},
|
||||||
|
$envData
|
||||||
|
);
|
||||||
|
|
||||||
|
if (! $replaced) {
|
||||||
|
$envData[] = $keyVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file_put_contents(ROOTPATH . '.env', implode('', $envData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,72 +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\EpisodeModel;
|
|
||||||
use CodeIgniter\HTTP\ResponseInterface;
|
|
||||||
|
|
||||||
class MapController extends BaseController
|
|
||||||
{
|
|
||||||
public function index(): string
|
|
||||||
{
|
|
||||||
$cacheName = implode(
|
|
||||||
'_',
|
|
||||||
array_filter([
|
|
||||||
'page',
|
|
||||||
'map',
|
|
||||||
service('request')
|
|
||||||
->getLocale(),
|
|
||||||
auth()
|
|
||||||
->loggedIn() ? 'authenticated' : null,
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (! ($found = cache($cacheName))) {
|
|
||||||
return view('pages/map', [], [
|
|
||||||
'cache' => DECADE,
|
|
||||||
'cache_name' => $cacheName,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $found;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getEpisodesMarkers(): ResponseInterface
|
|
||||||
{
|
|
||||||
$cacheName = 'episodes_markers';
|
|
||||||
if (! ($found = cache($cacheName))) {
|
|
||||||
$episodes = new EpisodeModel()
|
|
||||||
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
|
|
||||||
->where('location_geo is not')
|
|
||||||
->findAll();
|
|
||||||
$found = [];
|
|
||||||
foreach ($episodes as $episode) {
|
|
||||||
$found[] = [
|
|
||||||
'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,
|
|
||||||
'podcast_title' => esc($episode->podcast->title),
|
|
||||||
'episode_title' => esc($episode->title),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// The page cache is set to a decade so it is deleted manually upon episode update
|
|
||||||
cache()
|
|
||||||
->save($cacheName, $found, DECADE);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->response->setJSON($found);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
60
app/Controllers/MapMarkerController.php
Normal file
60
app/Controllers/MapMarkerController.php
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Controllers;
|
||||||
|
|
||||||
|
use App\Models\EpisodeModel;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
|
|
||||||
|
class MapMarkerController extends BaseController
|
||||||
|
{
|
||||||
|
public function index(): string
|
||||||
|
{
|
||||||
|
$locale = service('request')
|
||||||
|
->getLocale();
|
||||||
|
$cacheName = "page_map_{$locale}";
|
||||||
|
if (! ($found = cache($cacheName))) {
|
||||||
|
$found = view('map', [], [
|
||||||
|
'cache' => DECADE,
|
||||||
|
'cache_name' => $cacheName,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return $found;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEpisodesMarkers(): ResponseInterface
|
||||||
|
{
|
||||||
|
$cacheName = 'episodes_markers';
|
||||||
|
if (! ($found = cache($cacheName))) {
|
||||||
|
$episodes = (new EpisodeModel())
|
||||||
|
->where('`published_at` <= NOW()', null, false)
|
||||||
|
->where('location_geo is not', null)
|
||||||
|
->findAll();
|
||||||
|
$found = [];
|
||||||
|
foreach ($episodes as $episode) {
|
||||||
|
$found[] = [
|
||||||
|
'latitude' => $episode->location->latitude,
|
||||||
|
'longitude' => $episode->location->longitude,
|
||||||
|
'location_name' => $episode->location->name,
|
||||||
|
'location_url' => $episode->location->url,
|
||||||
|
'episode_link' => $episode->link,
|
||||||
|
'podcast_link' => $episode->podcast->link,
|
||||||
|
'image_path' => $episode->image->thumbnail_url,
|
||||||
|
'podcast_title' => $episode->podcast->title,
|
||||||
|
'episode_title' => $episode->title,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// The page cache is set to a decade so it is deleted manually upon episode update
|
||||||
|
cache()
|
||||||
|
->save($cacheName, $found, DECADE);
|
||||||
|
}
|
||||||
|
return $this->response->setJSON($found);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @copyright 2020 Ad Aures
|
* @copyright 2020 Podlibre
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
@ -20,13 +20,13 @@ class PageController extends BaseController
|
||||||
|
|
||||||
public function _remap(string $method, string ...$params): mixed
|
public function _remap(string $method, string ...$params): mixed
|
||||||
{
|
{
|
||||||
if ($params === []) {
|
if (count($params) === 0) {
|
||||||
throw PageNotFoundException::forPageNotFound();
|
throw PageNotFoundException::forPageNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
$page = new PageModel()
|
if (
|
||||||
->where('slug', $params[0])->first();
|
($page = (new PageModel())->where('slug', $params[0])->first()) === null
|
||||||
if (! $page instanceof Page) {
|
) {
|
||||||
throw PageNotFoundException::forPageNotFound();
|
throw PageNotFoundException::forPageNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,25 +37,13 @@ class PageController extends BaseController
|
||||||
|
|
||||||
public function index(): string
|
public function index(): string
|
||||||
{
|
{
|
||||||
$cacheName = implode(
|
$cacheName = "page-{$this->page->slug}";
|
||||||
'_',
|
|
||||||
array_filter([
|
|
||||||
'page',
|
|
||||||
$this->page->slug,
|
|
||||||
service('request')
|
|
||||||
->getLocale(),
|
|
||||||
auth()
|
|
||||||
->loggedIn() ? 'authenticated' : null,
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (! ($found = cache($cacheName))) {
|
if (! ($found = cache($cacheName))) {
|
||||||
set_page_metatags($this->page);
|
|
||||||
$data = [
|
$data = [
|
||||||
'page' => $this->page,
|
'page' => $this->page,
|
||||||
];
|
];
|
||||||
|
|
||||||
$found = view('pages/page', $data);
|
$found = view('page', $data);
|
||||||
|
|
||||||
// The page cache is set to a decade so it is deleted manually upon page update
|
// The page cache is set to a decade so it is deleted manually upon page update
|
||||||
cache()
|
cache()
|
||||||
|
|
|
||||||
28
app/Controllers/PlatformController.php
Normal file
28
app/Controllers/PlatformController.php
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,24 +3,25 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @copyright 2020 Ad Aures
|
* @copyright 2020 Podlibre
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
|
use ActivityPub\Objects\OrderedCollectionObject;
|
||||||
|
use ActivityPub\Objects\OrderedCollectionPage;
|
||||||
|
use Analytics\AnalyticsTrait;
|
||||||
use App\Entities\Podcast;
|
use App\Entities\Podcast;
|
||||||
use App\Libraries\PodcastActor;
|
use App\Libraries\PodcastActor;
|
||||||
use App\Libraries\PodcastEpisode;
|
use App\Libraries\PodcastEpisode;
|
||||||
use App\Models\EpisodeModel;
|
use App\Models\EpisodeModel;
|
||||||
use App\Models\PodcastModel;
|
use App\Models\PodcastModel;
|
||||||
use App\Models\PostModel;
|
use App\Models\StatusModel;
|
||||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||||
use CodeIgniter\HTTP\ResponseInterface;
|
use CodeIgniter\HTTP\RedirectResponse;
|
||||||
use Modules\Analytics\AnalyticsTrait;
|
use CodeIgniter\HTTP\Response;
|
||||||
use Modules\Fediverse\Objects\OrderedCollectionObject;
|
|
||||||
use Modules\Fediverse\Objects\OrderedCollectionPage;
|
|
||||||
|
|
||||||
class PodcastController extends BaseController
|
class PodcastController extends BaseController
|
||||||
{
|
{
|
||||||
|
|
@ -30,12 +31,12 @@ class PodcastController extends BaseController
|
||||||
|
|
||||||
public function _remap(string $method, string ...$params): mixed
|
public function _remap(string $method, string ...$params): mixed
|
||||||
{
|
{
|
||||||
if ($params === []) {
|
if (count($params) === 0) {
|
||||||
throw PageNotFoundException::forPageNotFound();
|
throw PageNotFoundException::forPageNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
! ($podcast = new PodcastModel()->getPodcastByHandle($params[0])) instanceof Podcast
|
($podcast = (new PodcastModel())->getPodcastByName($params[0])) === null
|
||||||
) {
|
) {
|
||||||
throw PageNotFoundException::forPageNotFound();
|
throw PageNotFoundException::forPageNotFound();
|
||||||
}
|
}
|
||||||
|
|
@ -47,7 +48,7 @@ class PodcastController extends BaseController
|
||||||
return $this->{$method}(...$params);
|
return $this->{$method}(...$params);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function podcastActor(): ResponseInterface
|
public function podcastActor(): RedirectResponse
|
||||||
{
|
{
|
||||||
$podcastActor = new PodcastActor($this->podcast);
|
$podcastActor = new PodcastActor($this->podcast);
|
||||||
|
|
||||||
|
|
@ -58,7 +59,10 @@ class PodcastController extends BaseController
|
||||||
|
|
||||||
public function activity(): string
|
public function activity(): string
|
||||||
{
|
{
|
||||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
// Prevent analytics hit when authenticated
|
||||||
|
if (! can_user_interact()) {
|
||||||
|
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||||
|
}
|
||||||
|
|
||||||
$cacheName = implode(
|
$cacheName = implode(
|
||||||
'_',
|
'_',
|
||||||
|
|
@ -68,79 +72,30 @@ class PodcastController extends BaseController
|
||||||
'activity',
|
'activity',
|
||||||
service('request')
|
service('request')
|
||||||
->getLocale(),
|
->getLocale(),
|
||||||
is_unlocked($this->podcast->handle) ? 'unlocked' : null,
|
can_user_interact() ? '_authenticated' : null,
|
||||||
auth()
|
|
||||||
->loggedIn() ? 'authenticated' : null,
|
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (! ($cachedView = cache($cacheName))) {
|
if (! ($cachedView = cache($cacheName))) {
|
||||||
set_podcast_metatags($this->podcast, 'activity');
|
|
||||||
$data = [
|
$data = [
|
||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
'posts' => new PostModel()
|
'statuses' => (new StatusModel())->getActorPublishedStatuses($this->podcast->actor_id),
|
||||||
->getActorPublishedPosts($this->podcast->actor_id),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// if user is logged in then send to the authenticated activity view
|
// if user is logged in then send to the authenticated activity view
|
||||||
if (auth()->loggedIn()) {
|
if (can_user_interact()) {
|
||||||
helper('form');
|
helper('form');
|
||||||
|
return view('podcast/activity_authenticated', $data);
|
||||||
return view('podcast/activity', $data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$secondsToNextUnpublishedEpisode = new EpisodeModel()
|
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
|
||||||
->getSecondsToNextUnpublishedEpisode($this->podcast->id);
|
$this->podcast->id,
|
||||||
|
);
|
||||||
|
|
||||||
return view('podcast/activity', $data, [
|
return view('podcast/activity', $data, [
|
||||||
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
|
'cache' => $secondsToNextUnpublishedEpisode
|
||||||
'cache_name' => $cacheName,
|
? $secondsToNextUnpublishedEpisode
|
||||||
]);
|
: DECADE,
|
||||||
}
|
|
||||||
|
|
||||||
return $cachedView;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function about(): string
|
|
||||||
{
|
|
||||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
|
||||||
|
|
||||||
$cacheName = implode(
|
|
||||||
'_',
|
|
||||||
array_filter([
|
|
||||||
'page',
|
|
||||||
"podcast#{$this->podcast->id}",
|
|
||||||
'about',
|
|
||||||
service('request')
|
|
||||||
->getLocale(),
|
|
||||||
is_unlocked($this->podcast->handle) ? 'unlocked' : null,
|
|
||||||
auth()
|
|
||||||
->loggedIn() ? 'authenticated' : null,
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (! ($cachedView = cache($cacheName))) {
|
|
||||||
$stats = new EpisodeModel()
|
|
||||||
->getPodcastStats($this->podcast->id);
|
|
||||||
|
|
||||||
set_podcast_metatags($this->podcast, 'about');
|
|
||||||
$data = [
|
|
||||||
'podcast' => $this->podcast,
|
|
||||||
'stats' => $stats,
|
|
||||||
];
|
|
||||||
|
|
||||||
// // if user is logged in then send to the authenticated activity view
|
|
||||||
if (auth()->loggedIn()) {
|
|
||||||
helper('form');
|
|
||||||
|
|
||||||
return view('podcast/about', $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
$secondsToNextUnpublishedEpisode = new EpisodeModel()
|
|
||||||
->getSecondsToNextUnpublishedEpisode($this->podcast->id);
|
|
||||||
|
|
||||||
return view('podcast/about', $data, [
|
|
||||||
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
|
|
||||||
'cache_name' => $cacheName,
|
'cache_name' => $cacheName,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
@ -150,14 +105,16 @@ class PodcastController extends BaseController
|
||||||
|
|
||||||
public function episodes(): string
|
public function episodes(): string
|
||||||
{
|
{
|
||||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
// Prevent analytics hit when authenticated
|
||||||
|
if (! can_user_interact()) {
|
||||||
|
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||||
|
}
|
||||||
|
|
||||||
$yearQuery = $this->request->getGet('year');
|
$yearQuery = $this->request->getGet('year');
|
||||||
$seasonQuery = $this->request->getGet('season');
|
$seasonQuery = $this->request->getGet('season');
|
||||||
|
|
||||||
if (! $yearQuery && ! $seasonQuery) {
|
if (! $yearQuery && ! $seasonQuery) {
|
||||||
$defaultQuery = new PodcastModel()
|
$defaultQuery = (new PodcastModel())->getDefaultQuery($this->podcast->id);
|
||||||
->getDefaultQuery($this->podcast->id);
|
|
||||||
if ($defaultQuery) {
|
if ($defaultQuery) {
|
||||||
if ($defaultQuery['type'] === 'season') {
|
if ($defaultQuery['type'] === 'season') {
|
||||||
$seasonQuery = $defaultQuery['data']['season_number'];
|
$seasonQuery = $defaultQuery['data']['season_number'];
|
||||||
|
|
@ -177,9 +134,7 @@ class PodcastController extends BaseController
|
||||||
$seasonQuery ? 'season' . $seasonQuery : null,
|
$seasonQuery ? 'season' . $seasonQuery : null,
|
||||||
service('request')
|
service('request')
|
||||||
->getLocale(),
|
->getLocale(),
|
||||||
is_unlocked($this->podcast->handle) ? 'unlocked' : null,
|
can_user_interact() ? '_authenticated' : null,
|
||||||
auth()
|
|
||||||
->loggedIn() ? 'authenticated' : null,
|
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -195,17 +150,18 @@ class PodcastController extends BaseController
|
||||||
$isActive = $yearQuery === $year['year'];
|
$isActive = $yearQuery === $year['year'];
|
||||||
if ($isActive) {
|
if ($isActive) {
|
||||||
$activeQuery = [
|
$activeQuery = [
|
||||||
'type' => 'year',
|
'type' => 'year',
|
||||||
'value' => $year['year'],
|
'value' => $year['year'],
|
||||||
'label' => $year['year'],
|
'label' => $year['year'],
|
||||||
'number_of_episodes' => $year['number_of_episodes'],
|
'number_of_episodes' => $year['number_of_episodes'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$episodesNavigation[] = [
|
$episodesNavigation[] = [
|
||||||
'label' => $year['year'],
|
'label' => $year['year'],
|
||||||
'number_of_episodes' => $year['number_of_episodes'],
|
'number_of_episodes' => $year['number_of_episodes'],
|
||||||
'route' => route_to('podcast-episodes', $this->podcast->handle) .
|
'route' =>
|
||||||
|
route_to('podcast-episodes', $this->podcast->name) .
|
||||||
'?year=' .
|
'?year=' .
|
||||||
$year['year'],
|
$year['year'],
|
||||||
'is_active' => $isActive,
|
'is_active' => $isActive,
|
||||||
|
|
@ -216,7 +172,7 @@ class PodcastController extends BaseController
|
||||||
$isActive = $seasonQuery === $season['season_number'];
|
$isActive = $seasonQuery === $season['season_number'];
|
||||||
if ($isActive) {
|
if ($isActive) {
|
||||||
$activeQuery = [
|
$activeQuery = [
|
||||||
'type' => 'season',
|
'type' => 'season',
|
||||||
'value' => $season['season_number'],
|
'value' => $season['season_number'],
|
||||||
'label' => lang('Podcast.season', [
|
'label' => lang('Podcast.season', [
|
||||||
'seasonNumber' => $season['season_number'],
|
'seasonNumber' => $season['season_number'],
|
||||||
|
|
@ -230,30 +186,38 @@ class PodcastController extends BaseController
|
||||||
'seasonNumber' => $season['season_number'],
|
'seasonNumber' => $season['season_number'],
|
||||||
]),
|
]),
|
||||||
'number_of_episodes' => $season['number_of_episodes'],
|
'number_of_episodes' => $season['number_of_episodes'],
|
||||||
'route' => route_to('podcast-episodes', $this->podcast->handle) .
|
'route' =>
|
||||||
|
route_to('podcast-episodes', $this->podcast->name) .
|
||||||
'?season=' .
|
'?season=' .
|
||||||
$season['season_number'],
|
$season['season_number'],
|
||||||
'is_active' => $isActive,
|
'is_active' => $isActive,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
set_podcast_metatags($this->podcast, 'episodes');
|
|
||||||
$data = [
|
$data = [
|
||||||
'podcast' => $this->podcast,
|
'podcast' => $this->podcast,
|
||||||
'episodesNav' => $episodesNavigation,
|
'episodesNav' => $episodesNavigation,
|
||||||
'activeQuery' => $activeQuery,
|
'activeQuery' => $activeQuery,
|
||||||
'episodes' => new EpisodeModel()
|
'episodes' => (new EpisodeModel())->getPodcastEpisodes(
|
||||||
->getPodcastEpisodes($this->podcast->id, $this->podcast->type, $yearQuery, $seasonQuery),
|
$this->podcast->id,
|
||||||
|
$this->podcast->type,
|
||||||
|
$yearQuery,
|
||||||
|
$seasonQuery,
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (auth()->loggedIn()) {
|
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
|
||||||
return view('podcast/episodes', $data);
|
$this->podcast->id,
|
||||||
}
|
);
|
||||||
|
|
||||||
$secondsToNextUnpublishedEpisode = new EpisodeModel()
|
// if user is logged in then send to the authenticated episodes view
|
||||||
->getSecondsToNextUnpublishedEpisode($this->podcast->id);
|
if (can_user_interact()) {
|
||||||
|
return view('podcast/episodes_authenticated', $data);
|
||||||
|
}
|
||||||
return view('podcast/episodes', $data, [
|
return view('podcast/episodes', $data, [
|
||||||
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
|
'cache' => $secondsToNextUnpublishedEpisode
|
||||||
|
? $secondsToNextUnpublishedEpisode
|
||||||
|
: DECADE,
|
||||||
'cache_name' => $cacheName,
|
'cache_name' => $cacheName,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
@ -261,16 +225,19 @@ class PodcastController extends BaseController
|
||||||
return $cachedView;
|
return $cachedView;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function episodeCollection(): ResponseInterface
|
/**
|
||||||
|
* @noRector ReturnTypeDeclarationRector
|
||||||
|
*/
|
||||||
|
public function episodeCollection(): Response
|
||||||
{
|
{
|
||||||
if ($this->podcast->type === 'serial') {
|
if ($this->podcast->type === 'serial') {
|
||||||
// podcast is serial
|
// podcast is serial
|
||||||
$episodes = model('EpisodeModel')
|
$episodes = model('EpisodeModel')
|
||||||
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
|
->where('`published_at` <= NOW()', null, false)
|
||||||
->orderBy('season_number DESC, number ASC');
|
->orderBy('season_number DESC, number ASC');
|
||||||
} else {
|
} else {
|
||||||
$episodes = model('EpisodeModel')
|
$episodes = model('EpisodeModel')
|
||||||
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
|
->where('`published_at` <= NOW()', null, false)
|
||||||
->orderBy('published_at', 'DESC');
|
->orderBy('published_at', 'DESC');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -285,8 +252,10 @@ class PodcastController extends BaseController
|
||||||
$pager = $episodes->pager;
|
$pager = $episodes->pager;
|
||||||
|
|
||||||
$orderedItems = [];
|
$orderedItems = [];
|
||||||
foreach ($paginatedEpisodes as $episode) {
|
if ($paginatedEpisodes !== null) {
|
||||||
$orderedItems[] = new PodcastEpisode($episode)->toArray();
|
foreach ($paginatedEpisodes as $episode) {
|
||||||
|
$orderedItems[] = (new PodcastEpisode($episode))->toArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @phpstan-ignore-next-line
|
// @phpstan-ignore-next-line
|
||||||
|
|
@ -297,12 +266,4 @@ class PodcastController extends BaseController
|
||||||
->setContentType('application/activity+json')
|
->setContentType('application/activity+json')
|
||||||
->setBody($collection->toJSON());
|
->setBody($collection->toJSON());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function links(): string
|
|
||||||
{
|
|
||||||
set_podcast_metatags($this->podcast, 'links');
|
|
||||||
return view('podcast/links', [
|
|
||||||
'podcast' => $this->podcast,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,275 +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\Entities\Actor;
|
|
||||||
use App\Entities\Podcast;
|
|
||||||
use App\Entities\Post as CastopodPost;
|
|
||||||
use App\Models\EpisodeModel;
|
|
||||||
use App\Models\PodcastModel;
|
|
||||||
use App\Models\PostModel;
|
|
||||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
|
||||||
use CodeIgniter\HTTP\RedirectResponse;
|
|
||||||
use CodeIgniter\HTTP\URI;
|
|
||||||
use CodeIgniter\I18n\Time;
|
|
||||||
use Modules\Analytics\AnalyticsTrait;
|
|
||||||
use Modules\Fediverse\Controllers\PostController as FediversePostController;
|
|
||||||
use Override;
|
|
||||||
|
|
||||||
class PostController extends FediversePostController
|
|
||||||
{
|
|
||||||
use AnalyticsTrait;
|
|
||||||
|
|
||||||
protected Podcast $podcast;
|
|
||||||
|
|
||||||
protected Actor $actor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var CastopodPost
|
|
||||||
*/
|
|
||||||
protected $post;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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])) instanceof Podcast
|
|
||||||
) {
|
|
||||||
throw PageNotFoundException::forPageNotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->podcast = $podcast;
|
|
||||||
$this->actor = $this->podcast->actor;
|
|
||||||
|
|
||||||
if (count($params) <= 1) {
|
|
||||||
unset($params[0]);
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
|
||||||
|
|
||||||
$cacheName = implode(
|
|
||||||
'_',
|
|
||||||
array_filter([
|
|
||||||
'page',
|
|
||||||
"post#{$this->post->id}",
|
|
||||||
service('request')
|
|
||||||
->getLocale(),
|
|
||||||
auth()
|
|
||||||
->loggedIn() ? 'authenticated' : null,
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (! ($cachedView = cache($cacheName))) {
|
|
||||||
set_post_metatags($this->post);
|
|
||||||
$data = [
|
|
||||||
'post' => $this->post,
|
|
||||||
'podcast' => $this->podcast,
|
|
||||||
];
|
|
||||||
|
|
||||||
// if user is logged in then send to the authenticated activity view
|
|
||||||
if (auth()->loggedIn()) {
|
|
||||||
helper('form');
|
|
||||||
return view('post/post', $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return view('post/post', $data, [
|
|
||||||
'cache' => DECADE,
|
|
||||||
'cache_name' => $cacheName,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $cachedView;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function createAction(): RedirectResponse
|
|
||||||
{
|
|
||||||
$rules = [
|
|
||||||
'message' => 'required|max_length[500]',
|
|
||||||
'episode_url' => 'valid_url_strict|permit_empty',
|
|
||||||
];
|
|
||||||
|
|
||||||
if (! $this->validate($rules)) {
|
|
||||||
return redirect()
|
|
||||||
->back()
|
|
||||||
->withInput()
|
|
||||||
->with('errors', $this->validator->getErrors());
|
|
||||||
}
|
|
||||||
|
|
||||||
$validData = $this->validator->getValidated();
|
|
||||||
|
|
||||||
$message = $validData['message'];
|
|
||||||
|
|
||||||
$newPost = new CastopodPost([
|
|
||||||
'actor_id' => interact_as_actor_id(),
|
|
||||||
'published_at' => Time::now(),
|
|
||||||
'created_by' => user_id(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// get episode if episodeUrl has been set
|
|
||||||
$episodeUri = $validData['episode_url'];
|
|
||||||
if (
|
|
||||||
$episodeUri &&
|
|
||||||
($params = extract_params_from_episode_uri(new URI($episodeUri))) &&
|
|
||||||
($episode = new EpisodeModel()->getEpisodeBySlug($params['podcastHandle'], $params['episodeSlug']))
|
|
||||||
) {
|
|
||||||
$newPost->episode_id = $episode->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
$newPost->message = $message;
|
|
||||||
|
|
||||||
$postModel = new PostModel();
|
|
||||||
if (
|
|
||||||
! $postModel
|
|
||||||
->addPost($newPost, ! (bool) $newPost->episode_id, true)
|
|
||||||
) {
|
|
||||||
return redirect()
|
|
||||||
->back()
|
|
||||||
->withInput()
|
|
||||||
->with('errors', $postModel->errors());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Post has been successfully created
|
|
||||||
return redirect()->back();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function replyAction(): RedirectResponse
|
|
||||||
{
|
|
||||||
$rules = [
|
|
||||||
'message' => 'required|max_length[500]',
|
|
||||||
];
|
|
||||||
|
|
||||||
if (! $this->validate($rules)) {
|
|
||||||
return redirect()
|
|
||||||
->back()
|
|
||||||
->withInput()
|
|
||||||
->with('errors', $this->validator->getErrors());
|
|
||||||
}
|
|
||||||
|
|
||||||
$validData = $this->validator->getValidated();
|
|
||||||
|
|
||||||
$newPost = new CastopodPost([
|
|
||||||
'actor_id' => interact_as_actor_id(),
|
|
||||||
'in_reply_to_id' => $this->post->id,
|
|
||||||
'message' => $validData['message'],
|
|
||||||
'is_private' => $this->post->is_private,
|
|
||||||
'published_at' => Time::now(),
|
|
||||||
'created_by' => user_id(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($this->post->episode_id !== null) {
|
|
||||||
$newPost->episode_id = $this->post->episode_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
$postModel = new PostModel();
|
|
||||||
if (! $postModel->addReply($newPost)) {
|
|
||||||
return redirect()
|
|
||||||
->back()
|
|
||||||
->withInput()
|
|
||||||
->with('errors', $postModel->errors());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reply post without preview card has been successfully created
|
|
||||||
return redirect()->back();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function favouriteAction(): RedirectResponse
|
|
||||||
{
|
|
||||||
model('FavouriteModel')->toggleFavourite(interact_as_actor(), $this->post);
|
|
||||||
|
|
||||||
return redirect()->back();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function reblogAction(): RedirectResponse
|
|
||||||
{
|
|
||||||
new PostModel()
|
|
||||||
->toggleReblog(interact_as_actor(), $this->post);
|
|
||||||
|
|
||||||
return redirect()->back();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function action(): RedirectResponse
|
|
||||||
{
|
|
||||||
$rules = [
|
|
||||||
'action' => 'required|in_list[favourite,reblog,reply]',
|
|
||||||
];
|
|
||||||
|
|
||||||
if (! $this->validate($rules)) {
|
|
||||||
return redirect()
|
|
||||||
->back()
|
|
||||||
->withInput()
|
|
||||||
->with('errors', $this->validator->getErrors());
|
|
||||||
}
|
|
||||||
|
|
||||||
$validData = $this->validator->getValidated();
|
|
||||||
|
|
||||||
$action = $validData['action'];
|
|
||||||
return match ($action) {
|
|
||||||
'favourite' => $this->favouriteAction(),
|
|
||||||
'reblog' => $this->reblogAction(),
|
|
||||||
'reply' => $this->replyAction(),
|
|
||||||
default => redirect()
|
|
||||||
->back()
|
|
||||||
->withInput()
|
|
||||||
->with('errors', 'error'),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public function remoteActionView(string $action): string
|
|
||||||
{
|
|
||||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
|
||||||
|
|
||||||
set_remote_actions_metatags($this->post, $action);
|
|
||||||
$data = [
|
|
||||||
'podcast' => $this->podcast,
|
|
||||||
'actor' => $this->actor,
|
|
||||||
'post' => $this->post,
|
|
||||||
'action' => $action,
|
|
||||||
];
|
|
||||||
|
|
||||||
helper('form');
|
|
||||||
|
|
||||||
// NO VIEW CACHING: form has a CSRF token which should change on each request
|
|
||||||
return view('post/remote_action', $data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
254
app/Controllers/StatusController.php
Normal file
254
app/Controllers/StatusController.php
Normal file
|
|
@ -0,0 +1,254 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Controllers;
|
||||||
|
|
||||||
|
use ActivityPub\Controllers\StatusController as ActivityPubStatusController;
|
||||||
|
use ActivityPub\Entities\Status as ActivityPubStatus;
|
||||||
|
use Analytics\AnalyticsTrait;
|
||||||
|
use App\Entities\Actor;
|
||||||
|
use App\Entities\Podcast;
|
||||||
|
use App\Entities\Status as CastopodStatus;
|
||||||
|
use App\Models\EpisodeModel;
|
||||||
|
use App\Models\PodcastModel;
|
||||||
|
use App\Models\StatusModel;
|
||||||
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||||
|
use CodeIgniter\HTTP\RedirectResponse;
|
||||||
|
use CodeIgniter\HTTP\URI;
|
||||||
|
use CodeIgniter\I18n\Time;
|
||||||
|
|
||||||
|
class StatusController extends ActivityPubStatusController
|
||||||
|
{
|
||||||
|
use AnalyticsTrait;
|
||||||
|
|
||||||
|
protected Podcast $podcast;
|
||||||
|
|
||||||
|
protected Actor $actor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $helpers = ['auth', 'activitypub', 'svg', 'components', 'misc'];
|
||||||
|
|
||||||
|
public function _remap(string $method, string ...$params): mixed
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
($podcast = (new PodcastModel())->getPodcastByName($params[0],)) === null
|
||||||
|
) {
|
||||||
|
throw PageNotFoundException::forPageNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->podcast = $podcast;
|
||||||
|
$this->actor = $this->podcast->actor;
|
||||||
|
|
||||||
|
if (
|
||||||
|
count($params) > 1 &&
|
||||||
|
($status = (new StatusModel())->getStatusById($params[1])) !== null
|
||||||
|
) {
|
||||||
|
$this->status = $status;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
$cacheName = implode(
|
||||||
|
'_',
|
||||||
|
array_filter([
|
||||||
|
'page',
|
||||||
|
"status#{$this->status->id}",
|
||||||
|
service('request')
|
||||||
|
->getLocale(),
|
||||||
|
can_user_interact() ? '_authenticated' : null,
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (! ($cachedView = cache($cacheName))) {
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
'actor' => $this->actor,
|
||||||
|
'status' => $this->status,
|
||||||
|
];
|
||||||
|
|
||||||
|
// if user is logged in then send to the authenticated activity view
|
||||||
|
if (can_user_interact()) {
|
||||||
|
helper('form');
|
||||||
|
return view('podcast/status_authenticated', $data);
|
||||||
|
}
|
||||||
|
return view('podcast/status', $data, [
|
||||||
|
'cache' => DECADE,
|
||||||
|
'cache_name' => $cacheName,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $cachedView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptCreate(): RedirectResponse
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'message' => 'required|max_length[500]',
|
||||||
|
'episode_url' => 'valid_url|permit_empty',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = $this->request->getPost('message');
|
||||||
|
|
||||||
|
$newStatus = new CastopodStatus([
|
||||||
|
'actor_id' => interact_as_actor_id(),
|
||||||
|
'published_at' => Time::now(),
|
||||||
|
'created_by' => user_id(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// get episode if episodeUrl has been set
|
||||||
|
$episodeUri = $this->request->getPost('episode_url');
|
||||||
|
if (
|
||||||
|
$episodeUri &&
|
||||||
|
($params = extract_params_from_episode_uri(new URI($episodeUri))) &&
|
||||||
|
($episode = (new EpisodeModel())->getEpisodeBySlug($params['podcastName'], $params['episodeSlug']))
|
||||||
|
) {
|
||||||
|
$newStatus->episode_id = $episode->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$newStatus->message = $message;
|
||||||
|
|
||||||
|
$statusModel = new StatusModel();
|
||||||
|
if (
|
||||||
|
! $statusModel
|
||||||
|
->addStatus($newStatus, ! (bool) $newStatus->episode_id, true)
|
||||||
|
) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $statusModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status has been successfully created
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptReply(): RedirectResponse
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'message' => 'required|max_length[500]',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$newStatus = new ActivityPubStatus([
|
||||||
|
'actor_id' => interact_as_actor_id(),
|
||||||
|
'in_reply_to_id' => $this->status->id,
|
||||||
|
'message' => $this->request->getPost('message'),
|
||||||
|
'published_at' => Time::now(),
|
||||||
|
'created_by' => user_id(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$statusModel = new StatusModel();
|
||||||
|
if (! $statusModel->addReply($newStatus)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $statusModel->errors());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reply status without preview card has been successfully created
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptFavourite(): RedirectResponse
|
||||||
|
{
|
||||||
|
model('FavouriteModel')->toggleFavourite(interact_as_actor(), $this->status);
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptReblog(): RedirectResponse
|
||||||
|
{
|
||||||
|
(new StatusModel())->toggleReblog(interact_as_actor(), $this->status);
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptAction(): RedirectResponse
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'action' => 'required|in_list[favourite,reblog,reply]',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$action = $this->request->getPost('action');
|
||||||
|
return match ($action) {
|
||||||
|
'favourite' => $this->attemptFavourite(),
|
||||||
|
'reblog' => $this->attemptReblog(),
|
||||||
|
'reply' => $this->attemptReply(),
|
||||||
|
default => redirect()
|
||||||
|
->back()
|
||||||
|
->withInput()
|
||||||
|
->with('errors', 'error'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remoteAction(string $action): string
|
||||||
|
{
|
||||||
|
// Prevent analytics hit when authenticated
|
||||||
|
if (! can_user_interact()) {
|
||||||
|
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$cacheName = implode(
|
||||||
|
'_',
|
||||||
|
array_filter(['page', "status#{$this->status->id}", "remote_{$action}", service('request') ->getLocale()]),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (! ($cachedView = cache($cacheName))) {
|
||||||
|
$data = [
|
||||||
|
'podcast' => $this->podcast,
|
||||||
|
'actor' => $this->actor,
|
||||||
|
'status' => $this->status,
|
||||||
|
'action' => $action,
|
||||||
|
];
|
||||||
|
|
||||||
|
helper('form');
|
||||||
|
|
||||||
|
return view('podcast/status_remote_action', $data, [
|
||||||
|
'cache' => DECADE,
|
||||||
|
'cache_name' => $cacheName,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) $cachedView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,117 +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\Entities\Podcast;
|
|
||||||
use App\Models\PodcastModel;
|
|
||||||
use CodeIgniter\Controller;
|
|
||||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
|
||||||
use CodeIgniter\HTTP\ResponseInterface;
|
|
||||||
|
|
||||||
class WebmanifestController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var array<string, array<string, string>>
|
|
||||||
*/
|
|
||||||
final public const array THEME_COLORS = [
|
|
||||||
'pine' => [
|
|
||||||
'theme' => '#009486',
|
|
||||||
'background' => '#F0F9F8',
|
|
||||||
],
|
|
||||||
'lake' => [
|
|
||||||
'theme' => '#00ACE0',
|
|
||||||
'background' => '#F0F7F9',
|
|
||||||
],
|
|
||||||
'jacaranda' => [
|
|
||||||
'theme' => '#562CDD',
|
|
||||||
'background' => '#F2F0F9',
|
|
||||||
],
|
|
||||||
'crimson' => [
|
|
||||||
'theme' => '#F24562',
|
|
||||||
'background' => '#F9F0F2',
|
|
||||||
],
|
|
||||||
'amber' => [
|
|
||||||
'theme' => '#FF6224',
|
|
||||||
'background' => '#F9F3F0',
|
|
||||||
],
|
|
||||||
'onyx' => [
|
|
||||||
'theme' => '#040406',
|
|
||||||
'background' => '#F3F3F7',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
public function index(): ResponseInterface
|
|
||||||
{
|
|
||||||
helper('misc');
|
|
||||||
|
|
||||||
$webmanifest = [
|
|
||||||
'name' => esc(service('settings') ->get('App.siteName')),
|
|
||||||
'description' => esc(service('settings') ->get('App.siteDescription')),
|
|
||||||
'lang' => service('request')
|
|
||||||
->getLocale(),
|
|
||||||
'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' => [
|
|
||||||
[
|
|
||||||
'src' => get_site_icon_url('192'),
|
|
||||||
'type' => 'image/png',
|
|
||||||
'sizes' => '192x192',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'src' => get_site_icon_url('512'),
|
|
||||||
'type' => 'image/png',
|
|
||||||
'sizes' => '512x512',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
return $this->response->setJSON($webmanifest);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function podcastManifest(string $podcastHandle): ResponseInterface
|
|
||||||
{
|
|
||||||
if (
|
|
||||||
! ($podcast = new PodcastModel()->getPodcastByHandle($podcastHandle)) instanceof Podcast
|
|
||||||
) {
|
|
||||||
throw PageNotFoundException::forPageNotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
$webmanifest = [
|
|
||||||
'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' => [
|
|
||||||
[
|
|
||||||
'src' => $podcast->cover->webmanifest192_url,
|
|
||||||
'type' => $podcast->cover->webmanifest192_mimetype,
|
|
||||||
'sizes' => '192x192',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'src' => $podcast->cover->webmanifest512_url,
|
|
||||||
'type' => $podcast->cover->webmanifest512_mimetype,
|
|
||||||
'sizes' => '512x512',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
return $this->response->setJSON($webmanifest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AddAddPodcastsPlatforms Creates podcasts_platforms table in database
|
||||||
|
*
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Analytics\Database\Migrations;
|
||||||
|
|
||||||
|
use CodeIgniter\Database\Migration;
|
||||||
|
|
||||||
|
class AddPodcastsPlatforms extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$this->forge->addField([
|
||||||
|
'podcast_id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'platform_slug' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 32,
|
||||||
|
],
|
||||||
|
'link_url' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 512,
|
||||||
|
],
|
||||||
|
'link_content' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 128,
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'is_visible' => [
|
||||||
|
'type' => 'TINYINT',
|
||||||
|
'constraint' => 1,
|
||||||
|
'default' => 0,
|
||||||
|
],
|
||||||
|
'is_on_embeddable_player' => [
|
||||||
|
'type' => 'TINYINT',
|
||||||
|
'constraint' => 1,
|
||||||
|
'default' => 0,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->addPrimaryKey(['podcast_id', 'platform_slug']);
|
||||||
|
$this->forge->createTable('podcasts_platforms');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$this->forge->dropTable('podcasts_platforms');
|
||||||
|
}
|
||||||
|
}
|
||||||
54
app/Database/Migrations/2020-05-29-152000_add_categories.php
Normal file
54
app/Database/Migrations/2020-05-29-152000_add_categories.php
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AddCategories Creates categories table in database
|
||||||
|
*
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @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 AddCategories extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$this->forge->addField([
|
||||||
|
'id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'parent_id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'code' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 32,
|
||||||
|
],
|
||||||
|
'apple_category' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 32,
|
||||||
|
],
|
||||||
|
'google_category' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 32,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$this->forge->addPrimaryKey('id');
|
||||||
|
$this->forge->addUniqueKey('code');
|
||||||
|
$this->forge->addForeignKey('parent_id', 'categories', 'id');
|
||||||
|
$this->forge->createTable('categories');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$this->forge->dropTable('categories');
|
||||||
|
}
|
||||||
|
}
|
||||||
40
app/Database/Migrations/2020-05-30-101000_add_languages.php
Normal file
40
app/Database/Migrations/2020-05-30-101000_add_languages.php
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AddLanguages Creates languages table in database
|
||||||
|
*
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @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 AddLanguages extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$this->forge->addField([
|
||||||
|
'code' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'comment' => 'ISO 639-1 language code',
|
||||||
|
'constraint' => 2,
|
||||||
|
],
|
||||||
|
'native_name' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 128,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$this->forge->addPrimaryKey('code');
|
||||||
|
$this->forge->createTable('languages');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$this->forge->dropTable('languages');
|
||||||
|
}
|
||||||
|
}
|
||||||
211
app/Database/Migrations/2020-05-30-101500_add_podcasts.php
Normal file
211
app/Database/Migrations/2020-05-30-101500_add_podcasts.php
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AddPodcasts Creates podcasts table in database
|
||||||
|
*
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @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 AddPodcasts extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$this->forge->addField([
|
||||||
|
'id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
'auto_increment' => true,
|
||||||
|
],
|
||||||
|
'guid' => [
|
||||||
|
'type' => 'CHAR',
|
||||||
|
'constraint' => 36,
|
||||||
|
],
|
||||||
|
'actor_id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'name' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 32,
|
||||||
|
],
|
||||||
|
'title' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 128,
|
||||||
|
],
|
||||||
|
'description_markdown' => [
|
||||||
|
'type' => 'TEXT',
|
||||||
|
],
|
||||||
|
'description_html' => [
|
||||||
|
'type' => 'TEXT',
|
||||||
|
],
|
||||||
|
'image_path' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 255,
|
||||||
|
],
|
||||||
|
// constraint is 13 because the longest safe mimetype for images is image/svg+xml,
|
||||||
|
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#image_types
|
||||||
|
'image_mimetype' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 13,
|
||||||
|
],
|
||||||
|
'language_code' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 2,
|
||||||
|
],
|
||||||
|
'category_id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
'default' => 0,
|
||||||
|
],
|
||||||
|
'parental_advisory' => [
|
||||||
|
'type' => 'ENUM',
|
||||||
|
'constraint' => ['clean', 'explicit'],
|
||||||
|
'null' => true,
|
||||||
|
'default' => null,
|
||||||
|
],
|
||||||
|
'owner_name' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 128,
|
||||||
|
],
|
||||||
|
'owner_email' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 255,
|
||||||
|
],
|
||||||
|
'publisher' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 128,
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'type' => [
|
||||||
|
'type' => 'ENUM',
|
||||||
|
'constraint' => ['episodic', 'serial'],
|
||||||
|
'default' => 'episodic',
|
||||||
|
],
|
||||||
|
'copyright' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 128,
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'episode_description_footer_markdown' => [
|
||||||
|
'type' => 'TEXT',
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'episode_description_footer_html' => [
|
||||||
|
'type' => 'TEXT',
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'is_blocked' => [
|
||||||
|
'type' => 'TINYINT',
|
||||||
|
'constraint' => 1,
|
||||||
|
'default' => 0,
|
||||||
|
],
|
||||||
|
'is_completed' => [
|
||||||
|
'type' => 'TINYINT',
|
||||||
|
'constraint' => 1,
|
||||||
|
'default' => 0,
|
||||||
|
],
|
||||||
|
'is_locked' => [
|
||||||
|
'type' => 'TINYINT',
|
||||||
|
'constraint' => 1,
|
||||||
|
'default' => 1,
|
||||||
|
],
|
||||||
|
'imported_feed_url' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 512,
|
||||||
|
'comment' =>
|
||||||
|
'The RSS feed URL if this podcast was imported, NULL otherwise.',
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'new_feed_url' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 512,
|
||||||
|
'comment' =>
|
||||||
|
'The RSS new feed URL if this podcast is moving out, NULL otherwise.',
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'payment_pointer' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 128,
|
||||||
|
'comment' => 'Wallet address for Web Monetization payments',
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'location_name' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 128,
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'location_geo' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 32,
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'location_osm' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 12,
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'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,
|
||||||
|
],
|
||||||
|
'created_by' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'updated_by' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'created_at' => [
|
||||||
|
'type' => 'DATETIME',
|
||||||
|
],
|
||||||
|
'updated_at' => [
|
||||||
|
'type' => 'DATETIME',
|
||||||
|
],
|
||||||
|
'deleted_at' => [
|
||||||
|
'type' => 'DATETIME',
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->addPrimaryKey('id');
|
||||||
|
// TODO: remove name in favor of username from actor
|
||||||
|
$this->forge->addUniqueKey('name');
|
||||||
|
$this->forge->addUniqueKey('guid');
|
||||||
|
$this->forge->addUniqueKey('actor_id');
|
||||||
|
$this->forge->addForeignKey('actor_id', 'activitypub_actors', 'id', '', 'CASCADE');
|
||||||
|
$this->forge->addForeignKey('category_id', 'categories', 'id');
|
||||||
|
$this->forge->addForeignKey('language_code', 'languages', 'code');
|
||||||
|
$this->forge->addForeignKey('created_by', 'users', 'id');
|
||||||
|
$this->forge->addForeignKey('updated_by', 'users', 'id');
|
||||||
|
$this->forge->createTable('podcasts');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$this->forge->dropTable('podcasts');
|
||||||
|
}
|
||||||
|
}
|
||||||
200
app/Database/Migrations/2020-06-05-170000_add_episodes.php
Normal file
200
app/Database/Migrations/2020-06-05-170000_add_episodes.php
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AddEpisodes Creates episodes table in database
|
||||||
|
*
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @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 AddEpisodes extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$this->forge->addField([
|
||||||
|
'id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
'auto_increment' => true,
|
||||||
|
],
|
||||||
|
'podcast_id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'guid' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 255,
|
||||||
|
],
|
||||||
|
'title' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 128,
|
||||||
|
],
|
||||||
|
'slug' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 191,
|
||||||
|
],
|
||||||
|
'audio_file_path' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 255,
|
||||||
|
],
|
||||||
|
'audio_file_duration' => [
|
||||||
|
// exact value for duration with max 99999,999 ~ 27.7 hours
|
||||||
|
'type' => 'DECIMAL(8,3)',
|
||||||
|
'unsigned' => true,
|
||||||
|
'comment' => 'Playtime in seconds',
|
||||||
|
],
|
||||||
|
'audio_file_mimetype' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 255,
|
||||||
|
],
|
||||||
|
'audio_file_size' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
'comment' => 'File size in bytes',
|
||||||
|
],
|
||||||
|
'audio_file_header_size' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
'comment' => 'Header size in bytes',
|
||||||
|
],
|
||||||
|
'description_markdown' => [
|
||||||
|
'type' => 'TEXT',
|
||||||
|
],
|
||||||
|
'description_html' => [
|
||||||
|
'type' => 'TEXT',
|
||||||
|
],
|
||||||
|
'image_path' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 255,
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
// constraint is 13 because the longest safe mimetype for images is image/svg+xml,
|
||||||
|
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#image_types
|
||||||
|
'image_mimetype' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 13,
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'transcript_file_path' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 255,
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'transcript_file_remote_url' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 512,
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'chapters_file_path' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 255,
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'chapters_file_remote_url' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 512,
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'parental_advisory' => [
|
||||||
|
'type' => 'ENUM',
|
||||||
|
'constraint' => ['clean', 'explicit'],
|
||||||
|
'null' => true,
|
||||||
|
'default' => null,
|
||||||
|
],
|
||||||
|
'number' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'season_number' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'type' => [
|
||||||
|
'type' => 'ENUM',
|
||||||
|
'constraint' => ['trailer', 'full', 'bonus'],
|
||||||
|
'default' => 'full',
|
||||||
|
],
|
||||||
|
'is_blocked' => [
|
||||||
|
'type' => 'TINYINT',
|
||||||
|
'constraint' => 1,
|
||||||
|
'default' => 0,
|
||||||
|
],
|
||||||
|
'location_name' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 128,
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'location_geo' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 32,
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'location_osm' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 12,
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'custom_rss' => [
|
||||||
|
'type' => 'JSON',
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'favourites_total' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
'default' => 0,
|
||||||
|
],
|
||||||
|
'reblogs_total' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
'default' => 0,
|
||||||
|
],
|
||||||
|
'statuses_total' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
'default' => 0,
|
||||||
|
],
|
||||||
|
'created_by' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'updated_by' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'published_at' => [
|
||||||
|
'type' => 'DATETIME',
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'created_at' => [
|
||||||
|
'type' => 'DATETIME',
|
||||||
|
],
|
||||||
|
'updated_at' => [
|
||||||
|
'type' => 'DATETIME',
|
||||||
|
],
|
||||||
|
'deleted_at' => [
|
||||||
|
'type' => 'DATETIME',
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$this->forge->addPrimaryKey('id');
|
||||||
|
$this->forge->addUniqueKey(['podcast_id', 'slug']);
|
||||||
|
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE');
|
||||||
|
$this->forge->addForeignKey('created_by', 'users', 'id');
|
||||||
|
$this->forge->addForeignKey('updated_by', 'users', 'id');
|
||||||
|
$this->forge->createTable('episodes');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$this->forge->dropTable('episodes');
|
||||||
|
}
|
||||||
|
}
|
||||||
81
app/Database/Migrations/2020-06-05-180000_add_soundbites.php
Normal file
81
app/Database/Migrations/2020-06-05-180000_add_soundbites.php
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AddSoundbites Creates soundbites table in database
|
||||||
|
*
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @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 AddSoundbites extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$this->forge->addField([
|
||||||
|
'id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
'auto_increment' => true,
|
||||||
|
],
|
||||||
|
'podcast_id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'episode_id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'start_time' => [
|
||||||
|
'type' => 'DECIMAL(8,3)',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'duration' => [
|
||||||
|
// soundbite duration cannot be higher than 9999,999 seconds ~ 2.77 hours
|
||||||
|
'type' => 'DECIMAL(7,3)',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'label' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 128,
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'created_by' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'updated_by' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'created_at' => [
|
||||||
|
'type' => 'DATETIME',
|
||||||
|
],
|
||||||
|
'updated_at' => [
|
||||||
|
'type' => 'DATETIME',
|
||||||
|
],
|
||||||
|
'deleted_at' => [
|
||||||
|
'type' => 'DATETIME',
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$this->forge->addKey('id', true);
|
||||||
|
$this->forge->addUniqueKey(['episode_id', 'start_time', 'duration']);
|
||||||
|
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE');
|
||||||
|
$this->forge->addForeignKey('episode_id', 'episodes', 'id', '', 'CASCADE');
|
||||||
|
$this->forge->addForeignKey('created_by', 'users', 'id');
|
||||||
|
$this->forge->addForeignKey('updated_by', 'users', 'id');
|
||||||
|
$this->forge->createTable('soundbites');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$this->forge->dropTable('soundbites');
|
||||||
|
}
|
||||||
|
}
|
||||||
55
app/Database/Migrations/2020-06-05-190000_add_platforms.php
Normal file
55
app/Database/Migrations/2020-06-05-190000_add_platforms.php
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AddPlatforms Creates platforms table in database
|
||||||
|
*
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @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 AddPlatforms extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$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,
|
||||||
|
'default' => null,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$this->forge->addField('`created_at` timestamp NOT NULL DEFAULT NOW()');
|
||||||
|
$this->forge->addField('`updated_at` timestamp NOT NULL DEFAULT NOW() ON UPDATE NOW()');
|
||||||
|
$this->forge->addPrimaryKey('slug');
|
||||||
|
$this->forge->createTable('platforms');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$this->forge->dropTable('platforms');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AddPodcastUsers Creates podcast_users table in database
|
||||||
|
*
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @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 AddPodcastsUsers extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$this->forge->addField([
|
||||||
|
'podcast_id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'user_id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'group_id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$this->forge->addPrimaryKey(['user_id', 'podcast_id']);
|
||||||
|
$this->forge->addForeignKey('user_id', 'users', 'id', '', 'CASCADE');
|
||||||
|
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE');
|
||||||
|
$this->forge->addForeignKey('group_id', 'auth_groups', 'id', '', 'CASCADE');
|
||||||
|
$this->forge->createTable('podcasts_users');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$this->forge->dropTable('podcasts_users');
|
||||||
|
}
|
||||||
|
}
|
||||||
61
app/Database/Migrations/2020-08-17-150000_add_pages.php
Normal file
61
app/Database/Migrations/2020-08-17-150000_add_pages.php
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AddPages Creates pages table in database
|
||||||
|
*
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @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 AddPages extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$this->forge->addField([
|
||||||
|
'id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
'auto_increment' => true,
|
||||||
|
],
|
||||||
|
'title' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 255,
|
||||||
|
],
|
||||||
|
'slug' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 191,
|
||||||
|
'unique' => true,
|
||||||
|
],
|
||||||
|
'content_markdown' => [
|
||||||
|
'type' => 'TEXT',
|
||||||
|
],
|
||||||
|
'content_html' => [
|
||||||
|
'type' => 'TEXT',
|
||||||
|
],
|
||||||
|
'created_at' => [
|
||||||
|
'type' => 'DATETIME',
|
||||||
|
],
|
||||||
|
'updated_at' => [
|
||||||
|
'type' => 'DATETIME',
|
||||||
|
],
|
||||||
|
'deleted_at' => [
|
||||||
|
'type' => 'DATETIME',
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$this->forge->addPrimaryKey('id');
|
||||||
|
$this->forge->createTable('pages');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$this->forge->dropTable('pages');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AddPodcastsCategories Creates podcasts_categories table in database
|
||||||
|
*
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @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 AddPodcastsCategories extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$this->forge->addField([
|
||||||
|
'podcast_id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'category_id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$this->forge->addPrimaryKey(['podcast_id', 'category_id']);
|
||||||
|
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE');
|
||||||
|
$this->forge->addForeignKey('category_id', 'categories', 'id', '', 'CASCADE');
|
||||||
|
$this->forge->createTable('podcasts_categories');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$this->forge->dropTable('podcasts_categories');
|
||||||
|
}
|
||||||
|
}
|
||||||
81
app/Database/Migrations/2020-12-25-120000_add_persons.php
Normal file
81
app/Database/Migrations/2020-12-25-120000_add_persons.php
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Persons Creates persons table in database
|
||||||
|
*
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @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 AddPersons extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$this->forge->addField([
|
||||||
|
'id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
'auto_increment' => true,
|
||||||
|
],
|
||||||
|
'full_name' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 192,
|
||||||
|
'comment' => 'This is the full name or alias of the person.',
|
||||||
|
],
|
||||||
|
'unique_name' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 192,
|
||||||
|
'comment' => 'This is the slug name or alias of the person.',
|
||||||
|
'unique' => true,
|
||||||
|
],
|
||||||
|
'information_url' => [
|
||||||
|
'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,
|
||||||
|
],
|
||||||
|
'image_path' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 255,
|
||||||
|
],
|
||||||
|
// constraint is 13 because the longest safe mimetype for images is image/svg+xml,
|
||||||
|
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#image_types
|
||||||
|
'image_mimetype' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 13,
|
||||||
|
],
|
||||||
|
'created_by' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'updated_by' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'created_at' => [
|
||||||
|
'type' => 'DATETIME',
|
||||||
|
],
|
||||||
|
'updated_at' => [
|
||||||
|
'type' => 'DATETIME',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->addKey('id', true);
|
||||||
|
$this->forge->addForeignKey('created_by', 'users', 'id');
|
||||||
|
$this->forge->addForeignKey('updated_by', 'users', 'id');
|
||||||
|
$this->forge->createTable('persons');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$this->forge->dropTable('persons');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AddPodcastsPersons Creates podcasts_persons table in database
|
||||||
|
*
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @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 AddPodcastsPersons extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$this->forge->addField([
|
||||||
|
'id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
'auto_increment' => true,
|
||||||
|
],
|
||||||
|
'podcast_id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'person_id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'person_group' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 32,
|
||||||
|
],
|
||||||
|
'person_role' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 32,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$this->forge->addKey('id', true);
|
||||||
|
$this->forge->addUniqueKey(['podcast_id', 'person_id', 'person_group', 'person_role']);
|
||||||
|
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE');
|
||||||
|
$this->forge->addForeignKey('person_id', 'persons', 'id', '', 'CASCADE');
|
||||||
|
$this->forge->createTable('podcasts_persons');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$this->forge->dropTable('podcasts_persons');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AddEpisodesPersons Creates episodes_persons table in database
|
||||||
|
*
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @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 AddEpisodesPersons extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$this->forge->addField([
|
||||||
|
'id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
'auto_increment' => true,
|
||||||
|
],
|
||||||
|
'podcast_id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'episode_id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'person_id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'person_group' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 32,
|
||||||
|
],
|
||||||
|
'person_role' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 32,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$this->forge->addPrimaryKey('id');
|
||||||
|
$this->forge->addUniqueKey(['podcast_id', 'episode_id', 'person_id', 'person_group', 'person_role']);
|
||||||
|
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE');
|
||||||
|
$this->forge->addForeignKey('episode_id', 'episodes', 'id', '', 'CASCADE');
|
||||||
|
$this->forge->addForeignKey('person_id', 'persons', 'id', '', 'CASCADE');
|
||||||
|
$this->forge->createTable('episodes_persons');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$this->forge->dropTable('episodes_persons');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AddCreditView Creates Credit View in database
|
||||||
|
*
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @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 AddCreditView extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
// Creates View for credit UNION query
|
||||||
|
$viewName = $this->db->prefixTable('credits');
|
||||||
|
$personsTable = $this->db->prefixTable('persons');
|
||||||
|
$podcastPersonsTable = $this->db->prefixTable('podcasts_persons');
|
||||||
|
$episodePersonsTable = $this->db->prefixTable('episodes_persons');
|
||||||
|
$episodesTable = $this->db->prefixTable('episodes');
|
||||||
|
$createQuery = <<<CODE_SAMPLE
|
||||||
|
CREATE VIEW `{$viewName}` AS
|
||||||
|
SELECT `person_group`, `person_id`, `full_name`, `person_role`, `podcast_id`, NULL AS `episode_id` FROM `{$podcastPersonsTable}`
|
||||||
|
INNER JOIN `{$personsTable}`
|
||||||
|
ON (`person_id`=`{$personsTable}`.`id`)
|
||||||
|
UNION
|
||||||
|
SELECT `person_group`, `person_id`, `full_name`, `person_role`, {$episodePersonsTable}.`podcast_id`, `episode_id` FROM `{$episodePersonsTable}`
|
||||||
|
INNER JOIN `{$personsTable}`
|
||||||
|
ON (`person_id`=`{$personsTable}`.`id`)
|
||||||
|
INNER JOIN `{$episodesTable}`
|
||||||
|
ON (`episode_id`=`{$episodesTable}`.`id`)
|
||||||
|
WHERE `{$episodesTable}`.published_at <= NOW()
|
||||||
|
ORDER BY `person_group`, `full_name`, `person_role`, `podcast_id`, `episode_id`;
|
||||||
|
CODE_SAMPLE;
|
||||||
|
$this->db->query($createQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$viewName = $this->db->prefixTable('credits');
|
||||||
|
$this->db->query("DROP VIEW IF EXISTS `{$viewName}`");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AddEpisodeIdToStatuses Adds episode_id field to activitypub_statuses table in database
|
||||||
|
*
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @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 AddEpisodeIdToStatuses extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$prefix = $this->db->getPrefix();
|
||||||
|
|
||||||
|
$createQuery = <<<CODE_SAMPLE
|
||||||
|
ALTER TABLE {$prefix}activitypub_statuses
|
||||||
|
ADD COLUMN `episode_id` INT UNSIGNED NULL AFTER `replies_count`,
|
||||||
|
ADD FOREIGN KEY {$prefix}activitypub_statuses_episode_id_foreign(episode_id) REFERENCES {$prefix}episodes(id) ON DELETE CASCADE;
|
||||||
|
CODE_SAMPLE;
|
||||||
|
$this->db->query($createQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$this->forge->dropForeignKey('activitypub_statuses', 'activitypub_statuses_episode_id_foreign');
|
||||||
|
$this->forge->dropColumn('activitypub_statuses', 'episode_id');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AddCreatedByToStatuses Adds created_by field to activitypub_statuses table in database
|
||||||
|
*
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @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 AddCreatedByToStatuses extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$prefix = $this->db->getPrefix();
|
||||||
|
|
||||||
|
$createQuery = <<<CODE_SAMPLE
|
||||||
|
ALTER TABLE {$prefix}activitypub_statuses
|
||||||
|
ADD COLUMN `created_by` INT UNSIGNED AFTER `episode_id`,
|
||||||
|
ADD FOREIGN KEY {$prefix}activitypub_statuses_created_by_foreign(created_by) REFERENCES {$prefix}users(id) ON DELETE CASCADE;
|
||||||
|
CODE_SAMPLE;
|
||||||
|
$this->db->query($createQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$this->forge->dropForeignKey('activitypub_statuses', 'activitypub_statuses_created_by_foreign');
|
||||||
|
$this->forge->dropColumn('activitypub_statuses', 'created_by');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class AddCategories Creates categories 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 AddCategories extends BaseMigration
|
|
||||||
{
|
|
||||||
#[Override]
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$this->forge->addField([
|
|
||||||
'id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'parent_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'code' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 32,
|
|
||||||
],
|
|
||||||
'apple_category' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 32,
|
|
||||||
],
|
|
||||||
'google_category' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 32,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
$this->forge->addPrimaryKey('id');
|
|
||||||
$this->forge->addUniqueKey('code');
|
|
||||||
$this->forge->addForeignKey('parent_id', 'categories', 'id');
|
|
||||||
$this->forge->createTable('categories');
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
$this->forge->dropTable('categories');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class AddLanguages Creates languages 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 AddLanguages extends BaseMigration
|
|
||||||
{
|
|
||||||
#[Override]
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$this->forge->addField([
|
|
||||||
'code' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'comment' => 'ISO 639-1 language code',
|
|
||||||
'constraint' => 2,
|
|
||||||
],
|
|
||||||
'native_name' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 128,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
$this->forge->addPrimaryKey('code');
|
|
||||||
$this->forge->createTable('languages');
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
$this->forge->dropTable('languages');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,216 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class AddPodcasts Creates podcasts 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 AddPodcasts extends BaseMigration
|
|
||||||
{
|
|
||||||
#[Override]
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$this->forge->addField([
|
|
||||||
'id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
'auto_increment' => true,
|
|
||||||
],
|
|
||||||
'guid' => [
|
|
||||||
'type' => 'CHAR',
|
|
||||||
'constraint' => 36,
|
|
||||||
],
|
|
||||||
'actor_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'handle' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 32,
|
|
||||||
],
|
|
||||||
'title' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 128,
|
|
||||||
],
|
|
||||||
'description_markdown' => [
|
|
||||||
'type' => 'TEXT',
|
|
||||||
],
|
|
||||||
'description_html' => [
|
|
||||||
'type' => 'TEXT',
|
|
||||||
],
|
|
||||||
'cover_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'banner_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'language_code' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 2,
|
|
||||||
],
|
|
||||||
'category_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
'default' => 0,
|
|
||||||
],
|
|
||||||
'parental_advisory' => [
|
|
||||||
'type' => 'ENUM',
|
|
||||||
'constraint' => ['clean', 'explicit'],
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'owner_name' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 128,
|
|
||||||
],
|
|
||||||
'owner_email' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 255,
|
|
||||||
],
|
|
||||||
'publisher' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 128,
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'type' => [
|
|
||||||
'type' => 'ENUM',
|
|
||||||
'constraint' => ['episodic', 'serial'],
|
|
||||||
'default' => 'episodic',
|
|
||||||
],
|
|
||||||
'copyright' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 128,
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'episode_description_footer_markdown' => [
|
|
||||||
'type' => 'TEXT',
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'episode_description_footer_html' => [
|
|
||||||
'type' => 'TEXT',
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'is_blocked' => [
|
|
||||||
'type' => 'TINYINT',
|
|
||||||
'constraint' => 1,
|
|
||||||
'default' => 0,
|
|
||||||
],
|
|
||||||
'is_completed' => [
|
|
||||||
'type' => 'TINYINT',
|
|
||||||
'constraint' => 1,
|
|
||||||
'default' => 0,
|
|
||||||
],
|
|
||||||
'is_locked' => [
|
|
||||||
'type' => 'TINYINT',
|
|
||||||
'constraint' => 1,
|
|
||||||
'default' => 1,
|
|
||||||
],
|
|
||||||
'imported_feed_url' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 512,
|
|
||||||
'comment' => 'The RSS feed URL if this podcast was imported, NULL otherwise.',
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'new_feed_url' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 512,
|
|
||||||
'comment' => 'The RSS new feed URL if this podcast is moving out, NULL otherwise.',
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'payment_pointer' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 128,
|
|
||||||
'comment' => 'Wallet address for Web Monetization payments',
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'location_name' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 128,
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'location_geo' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 32,
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'location_osm' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 12,
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'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,
|
|
||||||
],
|
|
||||||
'is_premium_by_default' => [
|
|
||||||
'type' => 'TINYINT',
|
|
||||||
'constraint' => 1,
|
|
||||||
'default' => 0,
|
|
||||||
],
|
|
||||||
'created_by' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'updated_by' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'published_at' => [
|
|
||||||
'type' => 'DATETIME',
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'created_at' => [
|
|
||||||
'type' => 'DATETIME',
|
|
||||||
],
|
|
||||||
'updated_at' => [
|
|
||||||
'type' => 'DATETIME',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->forge->addPrimaryKey('id');
|
|
||||||
// TODO: remove name in favor of username from actor
|
|
||||||
$this->forge->addUniqueKey('handle');
|
|
||||||
$this->forge->addUniqueKey('guid');
|
|
||||||
$this->forge->addUniqueKey('actor_id');
|
|
||||||
$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');
|
|
||||||
$this->forge->addForeignKey('language_code', 'languages', 'code');
|
|
||||||
$this->forge->addForeignKey('created_by', 'users', 'id');
|
|
||||||
$this->forge->addForeignKey('updated_by', 'users', 'id');
|
|
||||||
$this->forge->createTable('podcasts');
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
$this->forge->dropTable('podcasts');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,182 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class AddEpisodes Creates episodes 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 AddEpisodes extends BaseMigration
|
|
||||||
{
|
|
||||||
#[Override]
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$this->forge->addField([
|
|
||||||
'id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
'auto_increment' => true,
|
|
||||||
],
|
|
||||||
'podcast_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'guid' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 255,
|
|
||||||
],
|
|
||||||
'title' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 128,
|
|
||||||
],
|
|
||||||
'slug' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 128,
|
|
||||||
],
|
|
||||||
'audio_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'description_markdown' => [
|
|
||||||
'type' => 'TEXT',
|
|
||||||
],
|
|
||||||
'description_html' => [
|
|
||||||
'type' => 'TEXT',
|
|
||||||
],
|
|
||||||
'cover_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'transcript_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'transcript_remote_url' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 512,
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'chapters_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'chapters_remote_url' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 512,
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'parental_advisory' => [
|
|
||||||
'type' => 'ENUM',
|
|
||||||
'constraint' => ['clean', 'explicit'],
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'number' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'season_number' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'type' => [
|
|
||||||
'type' => 'ENUM',
|
|
||||||
'constraint' => ['trailer', 'full', 'bonus'],
|
|
||||||
'default' => 'full',
|
|
||||||
],
|
|
||||||
'is_blocked' => [
|
|
||||||
'type' => 'TINYINT',
|
|
||||||
'constraint' => 1,
|
|
||||||
'default' => 0,
|
|
||||||
],
|
|
||||||
'location_name' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 128,
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'location_geo' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 32,
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'location_osm' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 12,
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'custom_rss' => [
|
|
||||||
'type' => 'JSON',
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'posts_count' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
'default' => 0,
|
|
||||||
],
|
|
||||||
'comments_count' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
'default' => 0,
|
|
||||||
],
|
|
||||||
'is_premium' => [
|
|
||||||
'type' => 'TINYINT',
|
|
||||||
'constraint' => 1,
|
|
||||||
'default' => 0,
|
|
||||||
],
|
|
||||||
'created_by' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'updated_by' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'published_at' => [
|
|
||||||
'type' => 'DATETIME',
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'created_at' => [
|
|
||||||
'type' => 'DATETIME',
|
|
||||||
],
|
|
||||||
'updated_at' => [
|
|
||||||
'type' => 'DATETIME',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
$this->forge->addPrimaryKey('id');
|
|
||||||
$this->forge->addUniqueKey(['podcast_id', 'slug']);
|
|
||||||
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE');
|
|
||||||
$this->forge->addForeignKey('audio_id', 'media', 'id');
|
|
||||||
$this->forge->addForeignKey('cover_id', 'media', 'id', '', 'SET NULL');
|
|
||||||
$this->forge->addForeignKey('transcript_id', 'media', 'id', '', 'SET NULL');
|
|
||||||
$this->forge->addForeignKey('chapters_id', 'media', 'id', '', 'SET NULL');
|
|
||||||
$this->forge->addForeignKey('created_by', 'users', 'id');
|
|
||||||
$this->forge->addForeignKey('updated_by', 'users', 'id');
|
|
||||||
$this->forge->createTable('episodes');
|
|
||||||
|
|
||||||
// Add Full-Text Search index on title and description_markdown
|
|
||||||
$prefix = $this->db->getPrefix();
|
|
||||||
$createQuery = <<<SQL
|
|
||||||
ALTER TABLE {$prefix}episodes
|
|
||||||
ADD FULLTEXT title (title, description_markdown);
|
|
||||||
SQL;
|
|
||||||
$this->db->query($createQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
$this->forge->dropTable('episodes');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class AddPlatforms Creates 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\Migrations;
|
|
||||||
|
|
||||||
use Override;
|
|
||||||
|
|
||||||
class AddPlatforms extends BaseMigration
|
|
||||||
{
|
|
||||||
#[Override]
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$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');
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
$this->forge->dropTable('platforms');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class AddAddPodcastsPlatforms Creates podcasts_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\Migrations;
|
|
||||||
|
|
||||||
use Override;
|
|
||||||
|
|
||||||
class AddPodcastsPlatforms extends BaseMigration
|
|
||||||
{
|
|
||||||
#[Override]
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$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');
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
$this->forge->dropTable('podcasts_platforms');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class AddEpisodeComments creates episode_comments table in database
|
|
||||||
*
|
|
||||||
* @copyright 2021 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 AddEpisodeComments extends BaseMigration
|
|
||||||
{
|
|
||||||
#[Override]
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$this->forge->addField([
|
|
||||||
'id' => [
|
|
||||||
'type' => 'BINARY',
|
|
||||||
'constraint' => 16,
|
|
||||||
],
|
|
||||||
'uri' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 255,
|
|
||||||
],
|
|
||||||
'episode_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'actor_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'in_reply_to_id' => [
|
|
||||||
'type' => 'BINARY',
|
|
||||||
'constraint' => 16,
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'message' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 5000,
|
|
||||||
],
|
|
||||||
'message_html' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 6000,
|
|
||||||
],
|
|
||||||
'likes_count' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'replies_count' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'created_at' => [
|
|
||||||
'type' => 'DATETIME',
|
|
||||||
],
|
|
||||||
'created_by' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->forge->addPrimaryKey('id');
|
|
||||||
$this->forge->addForeignKey('episode_id', 'episodes', '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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class AddLikes Creates likes table in database
|
|
||||||
*
|
|
||||||
* @copyright 2021 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 AddLikes extends BaseMigration
|
|
||||||
{
|
|
||||||
#[Override]
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$this->forge->addField([
|
|
||||||
'actor_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'comment_id' => [
|
|
||||||
'type' => 'BINARY',
|
|
||||||
'constraint' => 16,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->forge->addField('`created_at` timestamp NOT NULL DEFAULT current_timestamp()');
|
|
||||||
$this->forge->addPrimaryKey(['actor_id', 'comment_id']);
|
|
||||||
$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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class AddPages Creates pages 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 AddPages extends BaseMigration
|
|
||||||
{
|
|
||||||
#[Override]
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$this->forge->addField([
|
|
||||||
'id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
'auto_increment' => true,
|
|
||||||
],
|
|
||||||
'title' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 255,
|
|
||||||
],
|
|
||||||
'slug' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 128,
|
|
||||||
'unique' => true,
|
|
||||||
],
|
|
||||||
'content_markdown' => [
|
|
||||||
'type' => 'TEXT',
|
|
||||||
],
|
|
||||||
'content_html' => [
|
|
||||||
'type' => 'TEXT',
|
|
||||||
],
|
|
||||||
'created_at' => [
|
|
||||||
'type' => 'DATETIME',
|
|
||||||
],
|
|
||||||
'updated_at' => [
|
|
||||||
'type' => 'DATETIME',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
$this->forge->addPrimaryKey('id');
|
|
||||||
$this->forge->createTable('pages');
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
$this->forge->dropTable('pages');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class AddPodcastsCategories Creates podcasts_categories 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 AddPodcastsCategories extends BaseMigration
|
|
||||||
{
|
|
||||||
#[Override]
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$this->forge->addField([
|
|
||||||
'podcast_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'category_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
$this->forge->addPrimaryKey(['podcast_id', 'category_id']);
|
|
||||||
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE');
|
|
||||||
$this->forge->addForeignKey('category_id', 'categories', 'id', '', 'CASCADE');
|
|
||||||
$this->forge->createTable('podcasts_categories');
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
$this->forge->dropTable('podcasts_categories');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,105 +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\Database\Migrations;
|
|
||||||
|
|
||||||
use Override;
|
|
||||||
|
|
||||||
class AddClips extends BaseMigration
|
|
||||||
{
|
|
||||||
#[Override]
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$this->forge->addField([
|
|
||||||
'id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
'auto_increment' => true,
|
|
||||||
],
|
|
||||||
'podcast_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'episode_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'start_time' => [
|
|
||||||
'type' => 'DECIMAL(8,3)',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'duration' => [
|
|
||||||
// clip duration cannot be higher than 9999,999 seconds ~ 2.77 hours
|
|
||||||
'type' => 'DECIMAL(7,3)',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'title' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 128,
|
|
||||||
],
|
|
||||||
'type' => [
|
|
||||||
'type' => 'ENUM',
|
|
||||||
'constraint' => ['audio', 'video'],
|
|
||||||
],
|
|
||||||
'media_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'metadata' => [
|
|
||||||
'type' => 'JSON',
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'status' => [
|
|
||||||
'type' => 'ENUM',
|
|
||||||
'constraint' => ['queued', 'pending', 'running', 'passed', 'failed'],
|
|
||||||
],
|
|
||||||
'logs' => [
|
|
||||||
'type' => 'TEXT',
|
|
||||||
],
|
|
||||||
'created_by' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'updated_by' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'job_started_at' => [
|
|
||||||
'type' => 'DATETIME',
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'job_ended_at' => [
|
|
||||||
'type' => 'DATETIME',
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'created_at' => [
|
|
||||||
'type' => 'DATETIME',
|
|
||||||
],
|
|
||||||
'updated_at' => [
|
|
||||||
'type' => 'DATETIME',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->forge->addKey('id', true);
|
|
||||||
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE');
|
|
||||||
$this->forge->addForeignKey('episode_id', 'episodes', 'id', '', 'CASCADE');
|
|
||||||
$this->forge->addForeignKey('media_id', 'media', 'id', '', 'CASCADE');
|
|
||||||
$this->forge->addForeignKey('created_by', 'users', 'id');
|
|
||||||
$this->forge->addForeignKey('updated_by', 'users', 'id');
|
|
||||||
$this->forge->createTable('clips');
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
$this->forge->dropTable('clips');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class Persons Creates persons 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 AddPersons extends BaseMigration
|
|
||||||
{
|
|
||||||
#[Override]
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$this->forge->addField([
|
|
||||||
'id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
'auto_increment' => true,
|
|
||||||
],
|
|
||||||
'full_name' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 192,
|
|
||||||
'comment' => 'This is the full name or alias of the person.',
|
|
||||||
],
|
|
||||||
'unique_name' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 192,
|
|
||||||
'comment' => 'This is the slug name or alias of the person.',
|
|
||||||
'unique' => true,
|
|
||||||
],
|
|
||||||
'information_url' => [
|
|
||||||
'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,
|
|
||||||
],
|
|
||||||
'avatar_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
'null' => true,
|
|
||||||
],
|
|
||||||
'created_by' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'updated_by' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'created_at' => [
|
|
||||||
'type' => 'DATETIME',
|
|
||||||
],
|
|
||||||
'updated_at' => [
|
|
||||||
'type' => 'DATETIME',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->forge->addKey('id', true);
|
|
||||||
$this->forge->addForeignKey('avatar_id', 'media', 'id', '', 'SET NULL');
|
|
||||||
$this->forge->addForeignKey('created_by', 'users', 'id');
|
|
||||||
$this->forge->addForeignKey('updated_by', 'users', 'id');
|
|
||||||
$this->forge->createTable('persons');
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
$this->forge->dropTable('persons');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class AddPodcastsPersons Creates podcasts_persons 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 AddPodcastsPersons extends BaseMigration
|
|
||||||
{
|
|
||||||
#[Override]
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$this->forge->addField([
|
|
||||||
'id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
'auto_increment' => true,
|
|
||||||
],
|
|
||||||
'podcast_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'person_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'person_group' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 32,
|
|
||||||
],
|
|
||||||
'person_role' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 32,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
$this->forge->addKey('id', true);
|
|
||||||
$this->forge->addUniqueKey(['podcast_id', 'person_id', 'person_group', 'person_role']);
|
|
||||||
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE');
|
|
||||||
$this->forge->addForeignKey('person_id', 'persons', 'id', '', 'CASCADE');
|
|
||||||
$this->forge->createTable('podcasts_persons');
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
$this->forge->dropTable('podcasts_persons');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class AddEpisodesPersons Creates episodes_persons 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 AddEpisodesPersons extends BaseMigration
|
|
||||||
{
|
|
||||||
#[Override]
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$this->forge->addField([
|
|
||||||
'id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
'auto_increment' => true,
|
|
||||||
],
|
|
||||||
'podcast_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'episode_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'person_id' => [
|
|
||||||
'type' => 'INT',
|
|
||||||
'unsigned' => true,
|
|
||||||
],
|
|
||||||
'person_group' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 32,
|
|
||||||
],
|
|
||||||
'person_role' => [
|
|
||||||
'type' => 'VARCHAR',
|
|
||||||
'constraint' => 32,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
$this->forge->addPrimaryKey('id');
|
|
||||||
$this->forge->addUniqueKey(['podcast_id', 'episode_id', 'person_id', 'person_group', 'person_role']);
|
|
||||||
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE');
|
|
||||||
$this->forge->addForeignKey('episode_id', 'episodes', 'id', '', 'CASCADE');
|
|
||||||
$this->forge->addForeignKey('person_id', 'persons', 'id', '', 'CASCADE');
|
|
||||||
$this->forge->createTable('episodes_persons');
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
$this->forge->dropTable('episodes_persons');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +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\Database\Migrations;
|
|
||||||
|
|
||||||
use Override;
|
|
||||||
|
|
||||||
class AddCreditsView extends BaseMigration
|
|
||||||
{
|
|
||||||
#[Override]
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
// Creates View for credit UNION query
|
|
||||||
$viewName = $this->db->prefixTable('credits');
|
|
||||||
$personsTable = $this->db->prefixTable('persons');
|
|
||||||
$podcastPersonsTable = $this->db->prefixTable('podcasts_persons');
|
|
||||||
$episodePersonsTable = $this->db->prefixTable('episodes_persons');
|
|
||||||
$episodesTable = $this->db->prefixTable('episodes');
|
|
||||||
$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}`
|
|
||||||
ON (`person_id`=`{$personsTable}`.`id`)
|
|
||||||
UNION
|
|
||||||
SELECT `person_group`, `person_id`, `full_name`, `person_role`, {$episodePersonsTable}.`podcast_id`, `episode_id` FROM `{$episodePersonsTable}`
|
|
||||||
INNER JOIN `{$personsTable}`
|
|
||||||
ON (`person_id`=`{$personsTable}`.`id`)
|
|
||||||
INNER JOIN `{$episodesTable}`
|
|
||||||
ON (`episode_id`=`{$episodesTable}`.`id`)
|
|
||||||
WHERE `{$episodesTable}`.published_at <= UTC_TIMESTAMP()
|
|
||||||
ORDER BY `person_group`, `full_name`, `person_role`, `podcast_id`, `episode_id`;
|
|
||||||
SQL;
|
|
||||||
$this->db->query($createQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
$viewName = $this->db->prefixTable('credits');
|
|
||||||
$this->db->query("DROP VIEW IF EXISTS `{$viewName}`");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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 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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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 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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
<?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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
<?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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
<?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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
<?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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
<?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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,157 +0,0 @@
|
||||||
<?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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
<?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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
<?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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
<?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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
<?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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +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\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
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||||
/**
|
/**
|
||||||
* Class AppSeeder Calls all required seeders for castopod to work properly
|
* Class AppSeeder Calls all required seeders for castopod to work properly
|
||||||
*
|
*
|
||||||
* @copyright 2020 Ad Aures
|
* @copyright 2020 Podlibre
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
@ -13,14 +13,14 @@ declare(strict_types=1);
|
||||||
namespace App\Database\Seeds;
|
namespace App\Database\Seeds;
|
||||||
|
|
||||||
use CodeIgniter\Database\Seeder;
|
use CodeIgniter\Database\Seeder;
|
||||||
use Override;
|
|
||||||
|
|
||||||
class AppSeeder extends Seeder
|
class AppSeeder extends Seeder
|
||||||
{
|
{
|
||||||
#[Override]
|
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
|
$this->call('AuthSeeder');
|
||||||
$this->call('CategorySeeder');
|
$this->call('CategorySeeder');
|
||||||
$this->call('LanguageSeeder');
|
$this->call('LanguageSeeder');
|
||||||
|
$this->call('PlatformSeeder');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
314
app/Database/Seeds/AuthSeeder.php
Normal file
314
app/Database/Seeds/AuthSeeder.php
Normal file
|
|
@ -0,0 +1,314 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class PermissionSeeder Inserts permissions
|
||||||
|
*
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @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|array>[]>
|
||||||
|
*/
|
||||||
|
protected array $permissions = [
|
||||||
|
'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 a podcast without removing it from database',
|
||||||
|
'has_permission' => ['superadmin'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'delete_permanently',
|
||||||
|
'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_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 / unpublish episodes & statuses of a podcast',
|
||||||
|
'has_permission' => ['podcast_admin'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'interact_as',
|
||||||
|
'description' =>
|
||||||
|
'Interact as the podcast to favourite / share or reply to statuses.',
|
||||||
|
'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 an episode of a podcast without removing it from the database',
|
||||||
|
'has_permission' => ['podcast_admin'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'delete_permanently',
|
||||||
|
'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 an activitypub actors from interacting with the instance.',
|
||||||
|
'has_permission' => ['superadmin'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'block_domains',
|
||||||
|
'description' =>
|
||||||
|
'Block an activitypub 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,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->db
|
||||||
|
->table('auth_permissions')
|
||||||
|
->ignore(true)
|
||||||
|
->insertBatch($dataPermissions);
|
||||||
|
$this->db
|
||||||
|
->table('auth_groups')
|
||||||
|
->ignore(true)
|
||||||
|
->insertBatch($dataGroups);
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||||
/**
|
/**
|
||||||
* Class CategorySeeder Inserts values in categories table in database
|
* Class CategorySeeder Inserts values in categories table in database
|
||||||
*
|
*
|
||||||
* @copyright 2020 Ad Aures
|
* @copyright 2020 Podlibre
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
@ -13,791 +13,787 @@ declare(strict_types=1);
|
||||||
namespace App\Database\Seeds;
|
namespace App\Database\Seeds;
|
||||||
|
|
||||||
use CodeIgniter\Database\Seeder;
|
use CodeIgniter\Database\Seeder;
|
||||||
use Override;
|
|
||||||
|
|
||||||
class CategorySeeder extends Seeder
|
class CategorySeeder extends Seeder
|
||||||
{
|
{
|
||||||
#[Override]
|
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
$data = [
|
$data = [
|
||||||
[
|
[
|
||||||
'id' => 1,
|
'id' => 1,
|
||||||
'parent_id' => null,
|
'parent_id' => null,
|
||||||
'code' => 'arts',
|
'code' => 'arts',
|
||||||
'apple_category' => 'Arts',
|
'apple_category' => 'Arts',
|
||||||
'google_category' => 'Arts',
|
'google_category' => 'Arts',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 2,
|
'id' => 2,
|
||||||
'parent_id' => null,
|
'parent_id' => null,
|
||||||
'code' => 'business',
|
'code' => 'business',
|
||||||
'apple_category' => 'Business',
|
'apple_category' => 'Business',
|
||||||
'google_category' => 'Business',
|
'google_category' => 'Business',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 3,
|
'id' => 3,
|
||||||
'parent_id' => null,
|
'parent_id' => null,
|
||||||
'code' => 'comedy',
|
'code' => 'comedy',
|
||||||
'apple_category' => 'Comedy',
|
'apple_category' => 'Comedy',
|
||||||
'google_category' => 'Comedy',
|
'google_category' => 'Comedy',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 4,
|
'id' => 4,
|
||||||
'parent_id' => null,
|
'parent_id' => null,
|
||||||
'code' => 'education',
|
'code' => 'education',
|
||||||
'apple_category' => 'Education',
|
'apple_category' => 'Education',
|
||||||
'google_category' => 'Education',
|
'google_category' => 'Education',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 5,
|
'id' => 5,
|
||||||
'parent_id' => null,
|
'parent_id' => null,
|
||||||
'code' => 'fiction',
|
'code' => 'fiction',
|
||||||
'apple_category' => 'Fiction',
|
'apple_category' => 'Fiction',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 6,
|
'id' => 6,
|
||||||
'parent_id' => null,
|
'parent_id' => null,
|
||||||
'code' => 'government',
|
'code' => 'government',
|
||||||
'apple_category' => 'Government',
|
'apple_category' => 'Government',
|
||||||
'google_category' => 'Government & Organizations',
|
'google_category' => 'Government & Organizations',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 7,
|
'id' => 7,
|
||||||
'parent_id' => null,
|
'parent_id' => null,
|
||||||
'code' => 'health_and_fitness',
|
'code' => 'health_and_fitness',
|
||||||
'apple_category' => 'Health & Fitness',
|
'apple_category' => 'Health & Fitness',
|
||||||
'google_category' => 'Health',
|
'google_category' => 'Health',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 8,
|
'id' => 8,
|
||||||
'parent_id' => null,
|
'parent_id' => null,
|
||||||
'code' => 'history',
|
'code' => 'history',
|
||||||
'apple_category' => 'History',
|
'apple_category' => 'History',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 9,
|
'id' => 9,
|
||||||
'parent_id' => null,
|
'parent_id' => null,
|
||||||
'code' => 'kids_and_family',
|
'code' => 'kids_and_family',
|
||||||
'apple_category' => 'Kids & Family',
|
'apple_category' => 'Kids & Family',
|
||||||
'google_category' => 'Kids & Family',
|
'google_category' => 'Kids & Family',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 10,
|
'id' => 10,
|
||||||
'parent_id' => null,
|
'parent_id' => null,
|
||||||
'code' => 'leisure',
|
'code' => 'leisure',
|
||||||
'apple_category' => 'Leisure',
|
'apple_category' => 'Leisure',
|
||||||
'google_category' => 'Games & Hobbies',
|
'google_category' => 'Games & Hobbies',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 11,
|
'id' => 11,
|
||||||
'parent_id' => null,
|
'parent_id' => null,
|
||||||
'code' => 'music',
|
'code' => 'music',
|
||||||
'apple_category' => 'Music',
|
'apple_category' => 'Music',
|
||||||
'google_category' => 'Music',
|
'google_category' => 'Music',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 12,
|
'id' => 12,
|
||||||
'parent_id' => null,
|
'parent_id' => null,
|
||||||
'code' => 'news',
|
'code' => 'news',
|
||||||
'apple_category' => 'News',
|
'apple_category' => 'News',
|
||||||
'google_category' => 'News & Politics',
|
'google_category' => 'News & Politics',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 13,
|
'id' => 13,
|
||||||
'parent_id' => null,
|
'parent_id' => null,
|
||||||
'code' => 'religion_and_spirituality',
|
'code' => 'religion_and_spirituality',
|
||||||
'apple_category' => 'Religion & Spirituality',
|
'apple_category' => 'Religion & Spirituality',
|
||||||
'google_category' => 'Religion & Spirituality',
|
'google_category' => 'Religion & Spirituality',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 14,
|
'id' => 14,
|
||||||
'parent_id' => null,
|
'parent_id' => null,
|
||||||
'code' => 'science',
|
'code' => 'science',
|
||||||
'apple_category' => 'Science',
|
'apple_category' => 'Science',
|
||||||
'google_category' => 'Science & Medicine',
|
'google_category' => 'Science & Medicine',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 15,
|
'id' => 15,
|
||||||
'parent_id' => null,
|
'parent_id' => null,
|
||||||
'code' => 'society_and_culture',
|
'code' => 'society_and_culture',
|
||||||
'apple_category' => 'Society & Culture',
|
'apple_category' => 'Society & Culture',
|
||||||
'google_category' => 'Society & Culture',
|
'google_category' => 'Society & Culture',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 16,
|
'id' => 16,
|
||||||
'parent_id' => null,
|
'parent_id' => null,
|
||||||
'code' => 'sports',
|
'code' => 'sports',
|
||||||
'apple_category' => 'Sports',
|
'apple_category' => 'Sports',
|
||||||
'google_category' => 'Sports & Recreation',
|
'google_category' => 'Sports & Recreation',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 17,
|
'id' => 17,
|
||||||
'parent_id' => null,
|
'parent_id' => null,
|
||||||
'code' => 'technology',
|
'code' => 'technology',
|
||||||
'apple_category' => 'Technology',
|
'apple_category' => 'Technology',
|
||||||
'google_category' => 'Technology',
|
'google_category' => 'Technology',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 18,
|
'id' => 18,
|
||||||
'parent_id' => null,
|
'parent_id' => null,
|
||||||
'code' => 'true_crime',
|
'code' => 'true_crime',
|
||||||
'apple_category' => 'True Crime',
|
'apple_category' => 'True Crime',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 19,
|
'id' => 19,
|
||||||
'parent_id' => null,
|
'parent_id' => null,
|
||||||
'code' => 'tv_and_film',
|
'code' => 'tv_and_film',
|
||||||
'apple_category' => 'TV & Film',
|
'apple_category' => 'TV & Film',
|
||||||
'google_category' => 'TV & Film',
|
'google_category' => 'TV & Film',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 20,
|
'id' => 20,
|
||||||
'parent_id' => 1,
|
'parent_id' => 1,
|
||||||
'code' => 'books',
|
'code' => 'books',
|
||||||
'apple_category' => 'Books',
|
'apple_category' => 'Books',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 21,
|
'id' => 21,
|
||||||
'parent_id' => 1,
|
'parent_id' => 1,
|
||||||
'code' => 'design',
|
'code' => 'design',
|
||||||
'apple_category' => 'Design',
|
'apple_category' => 'Design',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 22,
|
'id' => 22,
|
||||||
'parent_id' => 1,
|
'parent_id' => 1,
|
||||||
'code' => 'fashion_and_beauty',
|
'code' => 'fashion_and_beauty',
|
||||||
'apple_category' => 'Fashion & Beauty',
|
'apple_category' => 'Fashion & Beauty',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 23,
|
'id' => 23,
|
||||||
'parent_id' => 1,
|
'parent_id' => 1,
|
||||||
'code' => 'food',
|
'code' => 'food',
|
||||||
'apple_category' => 'Food',
|
'apple_category' => 'Food',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 24,
|
'id' => 24,
|
||||||
'parent_id' => 1,
|
'parent_id' => 1,
|
||||||
'code' => 'performing_arts',
|
'code' => 'performing_arts',
|
||||||
'apple_category' => 'Performing Arts',
|
'apple_category' => 'Performing Arts',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 25,
|
'id' => 25,
|
||||||
'parent_id' => 1,
|
'parent_id' => 1,
|
||||||
'code' => 'visual_arts',
|
'code' => 'visual_arts',
|
||||||
'apple_category' => 'Visual Arts',
|
'apple_category' => 'Visual Arts',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 26,
|
'id' => 26,
|
||||||
'parent_id' => 2,
|
'parent_id' => 2,
|
||||||
'code' => 'careers',
|
'code' => 'careers',
|
||||||
'apple_category' => 'Careers',
|
'apple_category' => 'Careers',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 27,
|
'id' => 27,
|
||||||
'parent_id' => 2,
|
'parent_id' => 2,
|
||||||
'code' => 'entrepreneurship',
|
'code' => 'entrepreneurship',
|
||||||
'apple_category' => 'Entrepreneurship',
|
'apple_category' => 'Entrepreneurship',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 28,
|
'id' => 28,
|
||||||
'parent_id' => 2,
|
'parent_id' => 2,
|
||||||
'code' => 'investing',
|
'code' => 'investing',
|
||||||
'apple_category' => 'Investing',
|
'apple_category' => 'Investing',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 29,
|
'id' => 29,
|
||||||
'parent_id' => 2,
|
'parent_id' => 2,
|
||||||
'code' => 'management',
|
'code' => 'management',
|
||||||
'apple_category' => 'Management',
|
'apple_category' => 'Management',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 30,
|
'id' => 30,
|
||||||
'parent_id' => 2,
|
'parent_id' => 2,
|
||||||
'code' => 'marketing',
|
'code' => 'marketing',
|
||||||
'apple_category' => 'Marketing',
|
'apple_category' => 'Marketing',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 31,
|
'id' => 31,
|
||||||
'parent_id' => 2,
|
'parent_id' => 2,
|
||||||
'code' => 'non_profit',
|
'code' => 'non_profit',
|
||||||
'apple_category' => 'Non-Profit',
|
'apple_category' => 'Non-Profit',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 32,
|
'id' => 32,
|
||||||
'parent_id' => 3,
|
'parent_id' => 3,
|
||||||
'code' => 'comedy_interviews',
|
'code' => 'comedy_interviews',
|
||||||
'apple_category' => 'Comedy Interviews',
|
'apple_category' => 'Comedy Interviews',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 33,
|
'id' => 33,
|
||||||
'parent_id' => 3,
|
'parent_id' => 3,
|
||||||
'code' => 'improv',
|
'code' => 'improv',
|
||||||
'apple_category' => 'Improv',
|
'apple_category' => 'Improv',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 34,
|
'id' => 34,
|
||||||
'parent_id' => 3,
|
'parent_id' => 3,
|
||||||
'code' => 'stand_up',
|
'code' => 'stand_up',
|
||||||
'apple_category' => 'Stand-Up',
|
'apple_category' => 'Stand-Up',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 35,
|
'id' => 35,
|
||||||
'parent_id' => 4,
|
'parent_id' => 4,
|
||||||
'code' => 'courses',
|
'code' => 'courses',
|
||||||
'apple_category' => 'Courses',
|
'apple_category' => 'Courses',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 36,
|
'id' => 36,
|
||||||
'parent_id' => 4,
|
'parent_id' => 4,
|
||||||
'code' => 'how_to',
|
'code' => 'how_to',
|
||||||
'apple_category' => 'How To',
|
'apple_category' => 'How To',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 37,
|
'id' => 37,
|
||||||
'parent_id' => 4,
|
'parent_id' => 4,
|
||||||
'code' => 'language_learning',
|
'code' => 'language_learning',
|
||||||
'apple_category' => 'Language Learning',
|
'apple_category' => 'Language Learning',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 38,
|
'id' => 38,
|
||||||
'parent_id' => 4,
|
'parent_id' => 4,
|
||||||
'code' => 'self_improvement',
|
'code' => 'self_improvement',
|
||||||
'apple_category' => 'Self-Improvement',
|
'apple_category' => 'Self-Improvement',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 39,
|
'id' => 39,
|
||||||
'parent_id' => 5,
|
'parent_id' => 5,
|
||||||
'code' => 'comedy_fiction',
|
'code' => 'comedy_fiction',
|
||||||
'apple_category' => 'Comedy Fiction',
|
'apple_category' => 'Comedy Fiction',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 40,
|
'id' => 40,
|
||||||
'parent_id' => 5,
|
'parent_id' => 5,
|
||||||
'code' => 'drama',
|
'code' => 'drama',
|
||||||
'apple_category' => 'Drama',
|
'apple_category' => 'Drama',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 41,
|
'id' => 41,
|
||||||
'parent_id' => 5,
|
'parent_id' => 5,
|
||||||
'code' => 'science_fiction',
|
'code' => 'science_fiction',
|
||||||
'apple_category' => 'Science Fiction',
|
'apple_category' => 'Science Fiction',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 42,
|
'id' => 42,
|
||||||
'parent_id' => 7,
|
'parent_id' => 7,
|
||||||
'code' => 'alternative_health',
|
'code' => 'alternative_health',
|
||||||
'apple_category' => 'Alternative Health',
|
'apple_category' => 'Alternative Health',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 43,
|
'id' => 43,
|
||||||
'parent_id' => 7,
|
'parent_id' => 7,
|
||||||
'code' => 'fitness',
|
'code' => 'fitness',
|
||||||
'apple_category' => 'Fitness',
|
'apple_category' => 'Fitness',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 44,
|
'id' => 44,
|
||||||
'parent_id' => 7,
|
'parent_id' => 7,
|
||||||
'code' => 'medicine',
|
'code' => 'medicine',
|
||||||
'apple_category' => 'Medicine',
|
'apple_category' => 'Medicine',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 45,
|
'id' => 45,
|
||||||
'parent_id' => 7,
|
'parent_id' => 7,
|
||||||
'code' => 'mental_health',
|
'code' => 'mental_health',
|
||||||
'apple_category' => 'Mental Health',
|
'apple_category' => 'Mental Health',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 46,
|
'id' => 46,
|
||||||
'parent_id' => 7,
|
'parent_id' => 7,
|
||||||
'code' => 'nutrition',
|
'code' => 'nutrition',
|
||||||
'apple_category' => 'Nutrition',
|
'apple_category' => 'Nutrition',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 47,
|
'id' => 47,
|
||||||
'parent_id' => 7,
|
'parent_id' => 7,
|
||||||
'code' => 'sexuality',
|
'code' => 'sexuality',
|
||||||
'apple_category' => 'Sexuality',
|
'apple_category' => 'Sexuality',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 48,
|
'id' => 48,
|
||||||
'parent_id' => 9,
|
'parent_id' => 9,
|
||||||
'code' => 'education_for_kids',
|
'code' => 'education_for_kids',
|
||||||
'apple_category' => 'Education for Kids',
|
'apple_category' => 'Education for Kids',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 49,
|
'id' => 49,
|
||||||
'parent_id' => 9,
|
'parent_id' => 9,
|
||||||
'code' => 'parenting',
|
'code' => 'parenting',
|
||||||
'apple_category' => 'Parenting',
|
'apple_category' => 'Parenting',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 50,
|
'id' => 50,
|
||||||
'parent_id' => 9,
|
'parent_id' => 9,
|
||||||
'code' => 'pets_and_animals',
|
'code' => 'pets_and_animals',
|
||||||
'apple_category' => 'Pets & Animals',
|
'apple_category' => 'Pets & Animals',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 51,
|
'id' => 51,
|
||||||
'parent_id' => 9,
|
'parent_id' => 9,
|
||||||
'code' => 'stories_for_kids',
|
'code' => 'stories_for_kids',
|
||||||
'apple_category' => 'Stories for Kids',
|
'apple_category' => 'Stories for Kids',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 52,
|
'id' => 52,
|
||||||
'parent_id' => 10,
|
'parent_id' => 10,
|
||||||
'code' => 'animation_and_manga',
|
'code' => 'animation_and_manga',
|
||||||
'apple_category' => 'Animation & Manga',
|
'apple_category' => 'Animation & Manga',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 53,
|
'id' => 53,
|
||||||
'parent_id' => 10,
|
'parent_id' => 10,
|
||||||
'code' => 'automotive',
|
'code' => 'automotive',
|
||||||
'apple_category' => 'Automotive',
|
'apple_category' => 'Automotive',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 54,
|
'id' => 54,
|
||||||
'parent_id' => 10,
|
'parent_id' => 10,
|
||||||
'code' => 'aviation',
|
'code' => 'aviation',
|
||||||
'apple_category' => 'Aviation',
|
'apple_category' => 'Aviation',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 55,
|
'id' => 55,
|
||||||
'parent_id' => 10,
|
'parent_id' => 10,
|
||||||
'code' => 'crafts',
|
'code' => 'crafts',
|
||||||
'apple_category' => 'Crafts',
|
'apple_category' => 'Crafts',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 56,
|
'id' => 56,
|
||||||
'parent_id' => 10,
|
'parent_id' => 10,
|
||||||
'code' => 'games',
|
'code' => 'games',
|
||||||
'apple_category' => 'Games',
|
'apple_category' => 'Games',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 57,
|
'id' => 57,
|
||||||
'parent_id' => 10,
|
'parent_id' => 10,
|
||||||
'code' => 'hobbies',
|
'code' => 'hobbies',
|
||||||
'apple_category' => 'Hobbies',
|
'apple_category' => 'Hobbies',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 58,
|
'id' => 58,
|
||||||
'parent_id' => 10,
|
'parent_id' => 10,
|
||||||
'code' => 'home_and_garden',
|
'code' => 'home_and_garden',
|
||||||
'apple_category' => 'Home & Garden',
|
'apple_category' => 'Home & Garden',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 59,
|
'id' => 59,
|
||||||
'parent_id' => 10,
|
'parent_id' => 10,
|
||||||
'code' => 'video_games',
|
'code' => 'video_games',
|
||||||
'apple_category' => 'Video Games',
|
'apple_category' => 'Video Games',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 60,
|
'id' => 60,
|
||||||
'parent_id' => 11,
|
'parent_id' => 11,
|
||||||
'code' => 'music_commentary',
|
'code' => 'music_commentary',
|
||||||
'apple_category' => 'Music Commentary',
|
'apple_category' => 'Music Commentary',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 61,
|
'id' => 61,
|
||||||
'parent_id' => 11,
|
'parent_id' => 11,
|
||||||
'code' => 'music_history',
|
'code' => 'music_history',
|
||||||
'apple_category' => 'Music History',
|
'apple_category' => 'Music History',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 62,
|
'id' => 62,
|
||||||
'parent_id' => 11,
|
'parent_id' => 11,
|
||||||
'code' => 'music_interviews',
|
'code' => 'music_interviews',
|
||||||
'apple_category' => 'Music Interviews',
|
'apple_category' => 'Music Interviews',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 63,
|
'id' => 63,
|
||||||
'parent_id' => 12,
|
'parent_id' => 12,
|
||||||
'code' => 'business_news',
|
'code' => 'business_news',
|
||||||
'apple_category' => 'Business News',
|
'apple_category' => 'Business News',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 64,
|
'id' => 64,
|
||||||
'parent_id' => 12,
|
'parent_id' => 12,
|
||||||
'code' => 'daily_news',
|
'code' => 'daily_news',
|
||||||
'apple_category' => 'Daily News',
|
'apple_category' => 'Daily News',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 65,
|
'id' => 65,
|
||||||
'parent_id' => 12,
|
'parent_id' => 12,
|
||||||
'code' => 'entertainment_news',
|
'code' => 'entertainment_news',
|
||||||
'apple_category' => 'Entertainment News',
|
'apple_category' => 'Entertainment News',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 66,
|
'id' => 66,
|
||||||
'parent_id' => 12,
|
'parent_id' => 12,
|
||||||
'code' => 'news_commentary',
|
'code' => 'news_commentary',
|
||||||
'apple_category' => 'News Commentary',
|
'apple_category' => 'News Commentary',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 67,
|
'id' => 67,
|
||||||
'parent_id' => 12,
|
'parent_id' => 12,
|
||||||
'code' => 'politics',
|
'code' => 'politics',
|
||||||
'apple_category' => 'Politics',
|
'apple_category' => 'Politics',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 68,
|
'id' => 68,
|
||||||
'parent_id' => 12,
|
'parent_id' => 12,
|
||||||
'code' => 'sports_news',
|
'code' => 'sports_news',
|
||||||
'apple_category' => 'Sports News',
|
'apple_category' => 'Sports News',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 69,
|
'id' => 69,
|
||||||
'parent_id' => 12,
|
'parent_id' => 12,
|
||||||
'code' => 'tech_news',
|
'code' => 'tech_news',
|
||||||
'apple_category' => 'Tech News',
|
'apple_category' => 'Tech News',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 70,
|
'id' => 70,
|
||||||
'parent_id' => 13,
|
'parent_id' => 13,
|
||||||
'code' => 'buddhism',
|
'code' => 'buddhism',
|
||||||
'apple_category' => 'Buddhism',
|
'apple_category' => 'Buddhism',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 71,
|
'id' => 71,
|
||||||
'parent_id' => 13,
|
'parent_id' => 13,
|
||||||
'code' => 'christianity',
|
'code' => 'christianity',
|
||||||
'apple_category' => 'Christianity',
|
'apple_category' => 'Christianity',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 72,
|
'id' => 72,
|
||||||
'parent_id' => 13,
|
'parent_id' => 13,
|
||||||
'code' => 'hinduism',
|
'code' => 'hinduism',
|
||||||
'apple_category' => 'Hinduism',
|
'apple_category' => 'Hinduism',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 73,
|
'id' => 73,
|
||||||
'parent_id' => 13,
|
'parent_id' => 13,
|
||||||
'code' => 'islam',
|
'code' => 'islam',
|
||||||
'apple_category' => 'Islam',
|
'apple_category' => 'Islam',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 74,
|
'id' => 74,
|
||||||
'parent_id' => 13,
|
'parent_id' => 13,
|
||||||
'code' => 'judaism',
|
'code' => 'judaism',
|
||||||
'apple_category' => 'Judaism',
|
'apple_category' => 'Judaism',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 75,
|
'id' => 75,
|
||||||
'parent_id' => 13,
|
'parent_id' => 13,
|
||||||
'code' => 'religion',
|
'code' => 'religion',
|
||||||
'apple_category' => 'Religion',
|
'apple_category' => 'Religion',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 76,
|
'id' => 76,
|
||||||
'parent_id' => 13,
|
'parent_id' => 13,
|
||||||
'code' => 'spirituality',
|
'code' => 'spirituality',
|
||||||
'apple_category' => 'Spirituality',
|
'apple_category' => 'Spirituality',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 77,
|
'id' => 77,
|
||||||
'parent_id' => 14,
|
'parent_id' => 14,
|
||||||
'code' => 'astronomy',
|
'code' => 'astronomy',
|
||||||
'apple_category' => 'Astronomy',
|
'apple_category' => 'Astronomy',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 78,
|
'id' => 78,
|
||||||
'parent_id' => 14,
|
'parent_id' => 14,
|
||||||
'code' => 'chemistry',
|
'code' => 'chemistry',
|
||||||
'apple_category' => 'Chemistry',
|
'apple_category' => 'Chemistry',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 79,
|
'id' => 79,
|
||||||
'parent_id' => 14,
|
'parent_id' => 14,
|
||||||
'code' => 'earth_sciences',
|
'code' => 'earth_sciences',
|
||||||
'apple_category' => 'Earth Sciences',
|
'apple_category' => 'Earth Sciences',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 80,
|
'id' => 80,
|
||||||
'parent_id' => 14,
|
'parent_id' => 14,
|
||||||
'code' => 'life_sciences',
|
'code' => 'life_sciences',
|
||||||
'apple_category' => 'Life Sciences',
|
'apple_category' => 'Life Sciences',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 81,
|
'id' => 81,
|
||||||
'parent_id' => 14,
|
'parent_id' => 14,
|
||||||
'code' => 'mathematics',
|
'code' => 'mathematics',
|
||||||
'apple_category' => 'Mathematics',
|
'apple_category' => 'Mathematics',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 82,
|
'id' => 82,
|
||||||
'parent_id' => 14,
|
'parent_id' => 14,
|
||||||
'code' => 'natural_sciences',
|
'code' => 'natural_sciences',
|
||||||
'apple_category' => 'Natural Sciences',
|
'apple_category' => 'Natural Sciences',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 83,
|
'id' => 83,
|
||||||
'parent_id' => 14,
|
'parent_id' => 14,
|
||||||
'code' => 'nature',
|
'code' => 'nature',
|
||||||
'apple_category' => 'Nature',
|
'apple_category' => 'Nature',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 84,
|
'id' => 84,
|
||||||
'parent_id' => 14,
|
'parent_id' => 14,
|
||||||
'code' => 'physics',
|
'code' => 'physics',
|
||||||
'apple_category' => 'Physics',
|
'apple_category' => 'Physics',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 85,
|
'id' => 85,
|
||||||
'parent_id' => 14,
|
'parent_id' => 14,
|
||||||
'code' => 'social_sciences',
|
'code' => 'social_sciences',
|
||||||
'apple_category' => 'Social Sciences',
|
'apple_category' => 'Social Sciences',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 86,
|
'id' => 86,
|
||||||
'parent_id' => 15,
|
'parent_id' => 15,
|
||||||
'code' => 'documentary',
|
'code' => 'documentary',
|
||||||
'apple_category' => 'Documentary',
|
'apple_category' => 'Documentary',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 87,
|
'id' => 87,
|
||||||
'parent_id' => 15,
|
'parent_id' => 15,
|
||||||
'code' => 'personal_journals',
|
'code' => 'personal_journals',
|
||||||
'apple_category' => 'Personal Journals',
|
'apple_category' => 'Personal Journals',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 88,
|
'id' => 88,
|
||||||
'parent_id' => 15,
|
'parent_id' => 15,
|
||||||
'code' => 'philosophy',
|
'code' => 'philosophy',
|
||||||
'apple_category' => 'Philosophy',
|
'apple_category' => 'Philosophy',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 89,
|
'id' => 89,
|
||||||
'parent_id' => 15,
|
'parent_id' => 15,
|
||||||
'code' => 'places_and_travel',
|
'code' => 'places_and_travel',
|
||||||
'apple_category' => 'Places & Travel',
|
'apple_category' => 'Places & Travel',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 90,
|
'id' => 90,
|
||||||
'parent_id' => 15,
|
'parent_id' => 15,
|
||||||
'code' => 'relationships',
|
'code' => 'relationships',
|
||||||
'apple_category' => 'Relationships',
|
'apple_category' => 'Relationships',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 91,
|
'id' => 91,
|
||||||
'parent_id' => 16,
|
'parent_id' => 16,
|
||||||
'code' => 'baseball',
|
'code' => 'baseball',
|
||||||
'apple_category' => 'Baseball',
|
'apple_category' => 'Baseball',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 92,
|
'id' => 92,
|
||||||
'parent_id' => 16,
|
'parent_id' => 16,
|
||||||
'code' => 'basketball',
|
'code' => 'basketball',
|
||||||
'apple_category' => 'Basketball',
|
'apple_category' => 'Basketball',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 93,
|
'id' => 93,
|
||||||
'parent_id' => 16,
|
'parent_id' => 16,
|
||||||
'code' => 'cricket',
|
'code' => 'cricket',
|
||||||
'apple_category' => 'Cricket',
|
'apple_category' => 'Cricket',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 94,
|
'id' => 94,
|
||||||
'parent_id' => 16,
|
'parent_id' => 16,
|
||||||
'code' => 'fantasy_sports',
|
'code' => 'fantasy_sports',
|
||||||
'apple_category' => 'Fantasy Sports',
|
'apple_category' => 'Fantasy Sports',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 95,
|
'id' => 95,
|
||||||
'parent_id' => 16,
|
'parent_id' => 16,
|
||||||
'code' => 'football',
|
'code' => 'football',
|
||||||
'apple_category' => 'Football',
|
'apple_category' => 'Football',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 96,
|
'id' => 96,
|
||||||
'parent_id' => 16,
|
'parent_id' => 16,
|
||||||
'code' => 'golf',
|
'code' => 'golf',
|
||||||
'apple_category' => 'Golf',
|
'apple_category' => 'Golf',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 97,
|
'id' => 97,
|
||||||
'parent_id' => 16,
|
'parent_id' => 16,
|
||||||
'code' => 'hockey',
|
'code' => 'hockey',
|
||||||
'apple_category' => 'Hockey',
|
'apple_category' => 'Hockey',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 98,
|
'id' => 98,
|
||||||
'parent_id' => 16,
|
'parent_id' => 16,
|
||||||
'code' => 'rugby',
|
'code' => 'rugby',
|
||||||
'apple_category' => 'Rugby',
|
'apple_category' => 'Rugby',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 99,
|
'id' => 99,
|
||||||
'parent_id' => 16,
|
'parent_id' => 16,
|
||||||
'code' => 'running',
|
'code' => 'running',
|
||||||
'apple_category' => 'Running',
|
'apple_category' => 'Running',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 100,
|
'id' => 100,
|
||||||
'parent_id' => 16,
|
'parent_id' => 16,
|
||||||
'code' => 'soccer',
|
'code' => 'soccer',
|
||||||
'apple_category' => 'Soccer',
|
'apple_category' => 'Soccer',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 101,
|
'id' => 101,
|
||||||
'parent_id' => 16,
|
'parent_id' => 16,
|
||||||
'code' => 'swimming',
|
'code' => 'swimming',
|
||||||
'apple_category' => 'Swimming',
|
'apple_category' => 'Swimming',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 102,
|
'id' => 102,
|
||||||
'parent_id' => 16,
|
'parent_id' => 16,
|
||||||
'code' => 'tennis',
|
'code' => 'tennis',
|
||||||
'apple_category' => 'Tennis',
|
'apple_category' => 'Tennis',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 103,
|
'id' => 103,
|
||||||
'parent_id' => 16,
|
'parent_id' => 16,
|
||||||
'code' => 'volleyball',
|
'code' => 'volleyball',
|
||||||
'apple_category' => 'Volleyball',
|
'apple_category' => 'Volleyball',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 104,
|
'id' => 104,
|
||||||
'parent_id' => 16,
|
'parent_id' => 16,
|
||||||
'code' => 'wilderness',
|
'code' => 'wilderness',
|
||||||
'apple_category' => 'Wilderness',
|
'apple_category' => 'Wilderness',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 105,
|
'id' => 105,
|
||||||
'parent_id' => 16,
|
'parent_id' => 16,
|
||||||
'code' => 'wrestling',
|
'code' => 'wrestling',
|
||||||
'apple_category' => 'Wrestling',
|
'apple_category' => 'Wrestling',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 106,
|
'id' => 106,
|
||||||
'parent_id' => 19,
|
'parent_id' => 19,
|
||||||
'code' => 'after_shows',
|
'code' => 'after_shows',
|
||||||
'apple_category' => 'After Shows',
|
'apple_category' => 'After Shows',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 107,
|
'id' => 107,
|
||||||
'parent_id' => 19,
|
'parent_id' => 19,
|
||||||
'code' => 'film_history',
|
'code' => 'film_history',
|
||||||
'apple_category' => 'Film History',
|
'apple_category' => 'Film History',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 108,
|
'id' => 108,
|
||||||
'parent_id' => 19,
|
'parent_id' => 19,
|
||||||
'code' => 'film_interviews',
|
'code' => 'film_interviews',
|
||||||
'apple_category' => 'Film Interviews',
|
'apple_category' => 'Film Interviews',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 109,
|
'id' => 109,
|
||||||
'parent_id' => 19,
|
'parent_id' => 19,
|
||||||
'code' => 'film_reviews',
|
'code' => 'film_reviews',
|
||||||
'apple_category' => 'Film Reviews',
|
'apple_category' => 'Film Reviews',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 110,
|
'id' => 110,
|
||||||
'parent_id' => 19,
|
'parent_id' => 19,
|
||||||
'code' => 'tv_reviews',
|
'code' => 'tv_reviews',
|
||||||
'apple_category' => 'TV Reviews',
|
'apple_category' => 'TV Reviews',
|
||||||
'google_category' => '',
|
'google_category' => '',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($data as $categoryLine) {
|
$this->db
|
||||||
$this->db
|
->table('categories')
|
||||||
->table('categories')
|
->ignore(true)
|
||||||
->ignore(true)
|
->insertBatch($data);
|
||||||
->insert($categoryLine);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
<?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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +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;
|
|
||||||
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'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,26 +5,22 @@ declare(strict_types=1);
|
||||||
/**
|
/**
|
||||||
* Class FakePodcastsAnalyticsSeeder Inserts Fake Analytics in the database
|
* Class FakePodcastsAnalyticsSeeder Inserts Fake Analytics in the database
|
||||||
*
|
*
|
||||||
* @copyright 2020 Ad Aures
|
* @copyright 2020 Podlibre
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace App\Database\Seeds;
|
namespace App\Database\Seeds;
|
||||||
|
|
||||||
use App\Entities\Episode;
|
|
||||||
use App\Entities\Podcast;
|
|
||||||
use App\Models\EpisodeModel;
|
use App\Models\EpisodeModel;
|
||||||
use App\Models\PodcastModel;
|
use App\Models\PodcastModel;
|
||||||
use CodeIgniter\Database\Seeder;
|
use CodeIgniter\Database\Seeder;
|
||||||
use Exception;
|
|
||||||
use GeoIp2\Database\Reader;
|
use GeoIp2\Database\Reader;
|
||||||
|
|
||||||
use GeoIp2\Exception\AddressNotFoundException;
|
use GeoIp2\Exception\AddressNotFoundException;
|
||||||
use Override;
|
|
||||||
|
|
||||||
class FakePodcastsAnalyticsSeeder extends Seeder
|
class FakePodcastsAnalyticsSeeder extends Seeder
|
||||||
{
|
{
|
||||||
#[Override]
|
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
$jsonUserAgents = json_decode(
|
$jsonUserAgents = json_decode(
|
||||||
|
|
@ -43,165 +39,163 @@ class FakePodcastsAnalyticsSeeder extends Seeder
|
||||||
JSON_THROW_ON_ERROR,
|
JSON_THROW_ON_ERROR,
|
||||||
);
|
);
|
||||||
|
|
||||||
$podcast = new PodcastModel()
|
$podcast = (new PodcastModel())->first();
|
||||||
->first();
|
|
||||||
|
|
||||||
if (! $podcast instanceof Podcast) {
|
if ($podcast !== null) {
|
||||||
throw new Exception("COULD NOT POPULATE DATABASE:\n\tCreate a podcast with episodes first.\n");
|
$firstEpisode = (new EpisodeModel())
|
||||||
}
|
->selectMin('published_at')
|
||||||
|
->first();
|
||||||
|
|
||||||
$firstEpisode = new EpisodeModel()
|
for (
|
||||||
->selectMin('published_at')
|
$date = strtotime((string) $firstEpisode->published_at);
|
||||||
->first();
|
$date < strtotime('now');
|
||||||
|
$date = strtotime(date('Y-m-d', $date) . ' +1 day')
|
||||||
|
) {
|
||||||
|
$analyticsPodcasts = [];
|
||||||
|
$analyticsPodcastsByHour = [];
|
||||||
|
$analyticsPodcastsByCountry = [];
|
||||||
|
$analyticsPodcastsByEpisode = [];
|
||||||
|
$analyticsPodcastsByPlayer = [];
|
||||||
|
$analyticsPodcastsByRegion = [];
|
||||||
|
|
||||||
if (! $firstEpisode instanceof Episode) {
|
$episodes = (new EpisodeModel())
|
||||||
throw new Exception("COULD NOT POPULATE DATABASE:\n\tCreate an episode first.");
|
->where('podcast_id', $podcast->id)
|
||||||
}
|
->where('`published_at` <= NOW()', null, false)
|
||||||
|
->findAll();
|
||||||
|
foreach ($episodes as $episode) {
|
||||||
|
$age = floor(($date - strtotime((string) $episode->published_at)) / 86400);
|
||||||
|
$probability1 = floor(exp(3 - $age / 40)) + 1;
|
||||||
|
|
||||||
for (
|
for (
|
||||||
$date = strtotime((string) $firstEpisode->published_at);
|
$lineNumber = 0;
|
||||||
$date < strtotime('now');
|
$lineNumber < rand(1, (int) $probability1);
|
||||||
$date = strtotime(date('Y-m-d', $date) . ' +1 day')
|
++$lineNumber
|
||||||
) {
|
) {
|
||||||
$analyticsPodcasts = [];
|
$probability2 = floor(exp(6 - $age / 20)) + 10;
|
||||||
$analyticsPodcastsByHour = [];
|
|
||||||
$analyticsPodcastsByCountry = [];
|
|
||||||
$analyticsPodcastsByEpisode = [];
|
|
||||||
$analyticsPodcastsByPlayer = [];
|
|
||||||
$analyticsPodcastsByRegion = [];
|
|
||||||
|
|
||||||
$episodes = new EpisodeModel()
|
$player =
|
||||||
->where('podcast_id', $podcast->id)
|
$jsonUserAgents[
|
||||||
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
|
rand(1, count($jsonUserAgents) - 1)
|
||||||
->findAll();
|
];
|
||||||
foreach ($episodes as $episode) {
|
$service =
|
||||||
$age = floor(($date - strtotime((string) $episode->published_at)) / 86400);
|
$jsonRSSUserAgents[
|
||||||
$probability1 = floor(exp(3 - $age / 40)) + 1;
|
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;
|
||||||
|
|
||||||
for (
|
$fakeIp =
|
||||||
$lineNumber = 0;
|
rand(0, 255) .
|
||||||
$lineNumber < random_int(1, (int) $probability1);
|
'.' .
|
||||||
++$lineNumber
|
rand(0, 255) .
|
||||||
) {
|
'.' .
|
||||||
$probability2 = floor(exp(6 - $age / 20)) + 10;
|
rand(0, 255) .
|
||||||
|
'.' .
|
||||||
|
rand(0, 255);
|
||||||
|
|
||||||
$player =
|
$cityReader = new Reader(WRITEPATH . 'uploads/GeoLite2-City/GeoLite2-City.mmdb');
|
||||||
$jsonUserAgents[
|
|
||||||
random_int(1, count($jsonUserAgents) - 1)
|
$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,
|
||||||
];
|
];
|
||||||
$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.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$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,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
$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);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
$this->db
|
echo "COULD NOT POPULATE DATABASE:\n\tCreate a podcast with episodes first.\n";
|
||||||
->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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,20 +5,17 @@ declare(strict_types=1);
|
||||||
/**
|
/**
|
||||||
* Class FakeWebsiteAnalyticsSeeder Inserts Fake Analytics in the database
|
* Class FakeWebsiteAnalyticsSeeder Inserts Fake Analytics in the database
|
||||||
*
|
*
|
||||||
* @copyright 2020 Ad Aures
|
* @copyright 2020 Podlibre
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace App\Database\Seeds;
|
namespace App\Database\Seeds;
|
||||||
|
|
||||||
use App\Entities\Episode;
|
|
||||||
use App\Entities\Podcast;
|
|
||||||
use App\Models\EpisodeModel;
|
use App\Models\EpisodeModel;
|
||||||
use App\Models\PodcastModel;
|
use App\Models\PodcastModel;
|
||||||
|
|
||||||
use CodeIgniter\Database\Seeder;
|
use CodeIgniter\Database\Seeder;
|
||||||
use Exception;
|
|
||||||
use Override;
|
|
||||||
|
|
||||||
class FakeWebsiteAnalyticsSeeder extends Seeder
|
class FakeWebsiteAnalyticsSeeder extends Seeder
|
||||||
{
|
{
|
||||||
|
|
@ -182,96 +179,90 @@ class FakeWebsiteAnalyticsSeeder extends Seeder
|
||||||
'WOSBrowser',
|
'WOSBrowser',
|
||||||
];
|
];
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
$podcast = new PodcastModel()
|
$podcast = (new PodcastModel())->first();
|
||||||
->first();
|
|
||||||
|
|
||||||
if (! $podcast instanceof Podcast) {
|
if ($podcast) {
|
||||||
throw new Exception("COULD NOT POPULATE DATABASE:\n\tCreate a podcast with episodes first.\n");
|
$firstEpisode = (new EpisodeModel())
|
||||||
}
|
->selectMin('published_at')
|
||||||
|
->first();
|
||||||
|
|
||||||
$firstEpisode = new EpisodeModel()
|
for (
|
||||||
->selectMin('published_at')
|
$date = strtotime((string) $firstEpisode->published_at);
|
||||||
->first();
|
$date < strtotime('now');
|
||||||
|
$date = strtotime(date('Y-m-d', $date) . ' +1 day')
|
||||||
|
) {
|
||||||
|
$websiteByBrowser = [];
|
||||||
|
$websiteByEntryPage = [];
|
||||||
|
$websiteByReferer = [];
|
||||||
|
|
||||||
if (! $firstEpisode instanceof Episode) {
|
$episodes = (new EpisodeModel())
|
||||||
throw new Exception("COULD NOT POPULATE DATABASE:\n\tCreate an episode first.");
|
->where('podcast_id', $podcast->id)
|
||||||
}
|
->where('`published_at` <= NOW()', null, false)
|
||||||
|
->findAll();
|
||||||
|
foreach ($episodes as $episode) {
|
||||||
|
$age = floor(($date - strtotime((string) $episode->published_at)) / 86400);
|
||||||
|
$probability1 = (int) floor(exp(3 - $age / 40)) + 1;
|
||||||
|
|
||||||
for (
|
for (
|
||||||
$date = strtotime((string) $firstEpisode->published_at);
|
$lineNumber = 0;
|
||||||
$date < strtotime('now');
|
$lineNumber < rand(1, $probability1);
|
||||||
$date = strtotime(date('Y-m-d', $date) . ' +1 day')
|
++$lineNumber
|
||||||
) {
|
) {
|
||||||
$websiteByBrowser = [];
|
$probability2 = (int) floor(exp(6 - $age / 20)) + 10;
|
||||||
$websiteByEntryPage = [];
|
|
||||||
$websiteByReferer = [];
|
|
||||||
|
|
||||||
$episodes = new EpisodeModel()
|
$domain =
|
||||||
->where('podcast_id', $podcast->id)
|
$this->domains[rand(0, count($this->domains) - 1)];
|
||||||
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
|
$keyword =
|
||||||
->findAll();
|
$this->keywords[
|
||||||
foreach ($episodes as $episode) {
|
rand(0, count($this->keywords) - 1)
|
||||||
$age = floor(($date - strtotime((string) $episode->published_at)) / 86400);
|
];
|
||||||
$probability1 = (int) floor(exp(3 - $age / 40)) + 1;
|
$browser =
|
||||||
|
$this->browsers[
|
||||||
|
rand(0, count($this->browsers) - 1)
|
||||||
|
];
|
||||||
|
|
||||||
for (
|
$hits = rand(0, $probability2);
|
||||||
$lineNumber = 0;
|
|
||||||
$lineNumber < random_int(1, $probability1);
|
|
||||||
++$lineNumber
|
|
||||||
) {
|
|
||||||
$probability2 = (int) floor(exp(6 - $age / 20)) + 10;
|
|
||||||
|
|
||||||
$domain =
|
$websiteByBrowser[] = [
|
||||||
$this->domains[random_int(0, count($this->domains) - 1)];
|
'podcast_id' => $podcast->id,
|
||||||
$keyword =
|
'date' => date('Y-m-d', $date),
|
||||||
$this->keywords[
|
'browser' => $browser,
|
||||||
random_int(0, count($this->keywords) - 1)
|
'hits' => $hits,
|
||||||
];
|
];
|
||||||
$browser =
|
$websiteByEntryPage[] = [
|
||||||
$this->browsers[
|
'podcast_id' => $podcast->id,
|
||||||
random_int(0, count($this->browsers) - 1)
|
'date' => date('Y-m-d', $date),
|
||||||
|
'entry_page_url' => $episode->link,
|
||||||
|
'hits' => $hits,
|
||||||
];
|
];
|
||||||
|
$websiteByReferer[] = [
|
||||||
$hits = random_int(0, $probability2);
|
'podcast_id' => $podcast->id,
|
||||||
|
'date' => date('Y-m-d', $date),
|
||||||
$websiteByBrowser[] = [
|
'referer_url' =>
|
||||||
'podcast_id' => $podcast->id,
|
'http://' . $domain . '/?q=' . $keyword,
|
||||||
'date' => date('Y-m-d', $date),
|
'domain' => $domain,
|
||||||
'browser' => $browser,
|
'keywords' => $keyword,
|
||||||
'hits' => $hits,
|
'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 {
|
||||||
$this->db
|
echo "COULD NOT POPULATE DATABASE:\n\tCreate a podcast with episodes first.\n";
|
||||||
->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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||||
/**
|
/**
|
||||||
* Class LanguageSeeder Inserts values in languages table in database
|
* Class LanguageSeeder Inserts values in languages table in database
|
||||||
*
|
*
|
||||||
* @copyright 2020 Ad Aures
|
* @copyright 2020 Podlibre
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
@ -18,757 +18,754 @@ declare(strict_types=1);
|
||||||
namespace App\Database\Seeds;
|
namespace App\Database\Seeds;
|
||||||
|
|
||||||
use CodeIgniter\Database\Seeder;
|
use CodeIgniter\Database\Seeder;
|
||||||
use Override;
|
|
||||||
|
|
||||||
class LanguageSeeder extends Seeder
|
class LanguageSeeder extends Seeder
|
||||||
{
|
{
|
||||||
#[Override]
|
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
$data = [
|
$data = [
|
||||||
[
|
[
|
||||||
'code' => 'aa',
|
'code' => 'aa',
|
||||||
'native_name' => 'Afaraf',
|
'native_name' => 'Afaraf',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ab',
|
'code' => 'ab',
|
||||||
'native_name' => 'аҧсуа бызшәа, аҧсшәа',
|
'native_name' => 'аҧсуа бызшәа, аҧсшәа',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ae',
|
'code' => 'ae',
|
||||||
'native_name' => 'Avesta',
|
'native_name' => 'avesta',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'af',
|
'code' => 'af',
|
||||||
'native_name' => 'Afrikaans',
|
'native_name' => 'Afrikaans',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ak',
|
'code' => 'ak',
|
||||||
'native_name' => 'Akan',
|
'native_name' => 'Akan',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'am',
|
'code' => 'am',
|
||||||
'native_name' => 'አማርኛ',
|
'native_name' => 'አማርኛ',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'an',
|
'code' => 'an',
|
||||||
'native_name' => 'Aragonés',
|
'native_name' => 'aragonés',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ar',
|
'code' => 'ar',
|
||||||
'native_name' => 'العربية',
|
'native_name' => 'العربية',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'as',
|
'code' => 'as',
|
||||||
'native_name' => 'অসমীয়া',
|
'native_name' => 'অসমীয়া',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'av',
|
'code' => 'av',
|
||||||
'native_name' => 'авар мацӀ, магӀарул мацӀ',
|
'native_name' => 'авар мацӀ, магӀарул мацӀ',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ay',
|
'code' => 'ay',
|
||||||
'native_name' => 'Aymar aru',
|
'native_name' => 'aymar aru',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'az',
|
'code' => 'az',
|
||||||
'native_name' => 'azərbaycan dili',
|
'native_name' => 'azərbaycan dili',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ba',
|
'code' => 'ba',
|
||||||
'native_name' => 'башҡорт теле',
|
'native_name' => 'башҡорт теле',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'be',
|
'code' => 'be',
|
||||||
'native_name' => 'беларуская мова',
|
'native_name' => 'беларуская мова',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'bg',
|
'code' => 'bg',
|
||||||
'native_name' => 'български език',
|
'native_name' => 'български език',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'bh',
|
'code' => 'bh',
|
||||||
'native_name' => 'भोजपुरी',
|
'native_name' => 'भोजपुरी',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'bi',
|
'code' => 'bi',
|
||||||
'native_name' => 'Bislama',
|
'native_name' => 'Bislama',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'bm',
|
'code' => 'bm',
|
||||||
'native_name' => 'Bamanankan',
|
'native_name' => 'bamanankan',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'bn',
|
'code' => 'bn',
|
||||||
'native_name' => 'বাংলা',
|
'native_name' => 'বাংলা',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'bo',
|
'code' => 'bo',
|
||||||
'native_name' => 'བོད་ཡིག',
|
'native_name' => 'བོད་ཡིག',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'br',
|
'code' => 'br',
|
||||||
'native_name' => 'Brezhoneg',
|
'native_name' => 'brezhoneg',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'bs',
|
'code' => 'bs',
|
||||||
'native_name' => 'Bosanski jezik',
|
'native_name' => 'bosanski jezik',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ca',
|
'code' => 'ca',
|
||||||
'native_name' => 'Català, valencià',
|
'native_name' => 'català, valencià',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ce',
|
'code' => 'ce',
|
||||||
'native_name' => 'нохчийн мотт',
|
'native_name' => 'нохчийн мотт',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ch',
|
'code' => 'ch',
|
||||||
'native_name' => 'Chamoru',
|
'native_name' => 'Chamoru',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'co',
|
'code' => 'co',
|
||||||
'native_name' => 'Corsu, lingua corsa',
|
'native_name' => 'corsu, lingua corsa',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'cr',
|
'code' => 'cr',
|
||||||
'native_name' => 'ᓀᐦᐃᔭᐍᐏᐣ',
|
'native_name' => 'ᓀᐦᐃᔭᐍᐏᐣ',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'cs',
|
'code' => 'cs',
|
||||||
'native_name' => 'čeština, český jazyk',
|
'native_name' => 'čeština, český jazyk',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'cu',
|
'code' => 'cu',
|
||||||
'native_name' => 'ѩзыкъ словѣньскъ',
|
'native_name' => 'ѩзыкъ словѣньскъ',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'cv',
|
'code' => 'cv',
|
||||||
'native_name' => 'чӑваш чӗлхи',
|
'native_name' => 'чӑваш чӗлхи',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'cy',
|
'code' => 'cy',
|
||||||
'native_name' => 'Cymraeg',
|
'native_name' => 'Cymraeg',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'da',
|
'code' => 'da',
|
||||||
'native_name' => 'Dansk',
|
'native_name' => 'dansk',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'de',
|
'code' => 'de',
|
||||||
'native_name' => 'Deutsch',
|
'native_name' => 'Deutsch',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'dv',
|
'code' => 'dv',
|
||||||
'native_name' => 'ދިވެހި',
|
'native_name' => 'ދިވެހި',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'dz',
|
'code' => 'dz',
|
||||||
'native_name' => 'རྫོང་ཁ',
|
'native_name' => 'རྫོང་ཁ',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ee',
|
'code' => 'ee',
|
||||||
'native_name' => 'Eʋegbe',
|
'native_name' => 'Eʋegbe',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'el',
|
'code' => 'el',
|
||||||
'native_name' => 'ελληνικά',
|
'native_name' => 'ελληνικά',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'en',
|
'code' => 'en',
|
||||||
'native_name' => 'English',
|
'native_name' => 'English',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'eo',
|
'code' => 'eo',
|
||||||
'native_name' => 'Esperanto',
|
'native_name' => 'Esperanto',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'es',
|
'code' => 'es',
|
||||||
'native_name' => 'Español',
|
'native_name' => 'Español',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'et',
|
'code' => 'et',
|
||||||
'native_name' => 'eesti, eesti keel',
|
'native_name' => 'eesti, eesti keel',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'eu',
|
'code' => 'eu',
|
||||||
'native_name' => 'Euskara, euskera',
|
'native_name' => 'euskara, euskera',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'fa',
|
'code' => 'fa',
|
||||||
'native_name' => 'فارسی',
|
'native_name' => 'فارسی',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ff',
|
'code' => 'ff',
|
||||||
'native_name' => 'Fulfulde, Pulaar, Pular',
|
'native_name' => 'Fulfulde, Pulaar, Pular',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'fi',
|
'code' => 'fi',
|
||||||
'native_name' => 'Suomi, suomen kieli',
|
'native_name' => 'suomi, suomen kieli',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'fj',
|
'code' => 'fj',
|
||||||
'native_name' => 'Vosa Vakaviti',
|
'native_name' => 'vosa Vakaviti',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'fo',
|
'code' => 'fo',
|
||||||
'native_name' => 'Føroyskt',
|
'native_name' => 'føroyskt',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'fr',
|
'code' => 'fr',
|
||||||
'native_name' => 'Français, langue française',
|
'native_name' => 'français, langue française',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'fy',
|
'code' => 'fy',
|
||||||
'native_name' => 'Frysk',
|
'native_name' => 'Frysk',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ga',
|
'code' => 'ga',
|
||||||
'native_name' => 'Gaeilge',
|
'native_name' => 'Gaeilge',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'gd',
|
'code' => 'gd',
|
||||||
'native_name' => 'Gàidhlig',
|
'native_name' => 'Gàidhlig',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'gl',
|
'code' => 'gl',
|
||||||
'native_name' => 'Galego',
|
'native_name' => 'Galego',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'gn',
|
'code' => 'gn',
|
||||||
'native_name' => "Avañe'ẽ",
|
'native_name' => "Avañe'ẽ",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'gu',
|
'code' => 'gu',
|
||||||
'native_name' => 'ગુજરાતી',
|
'native_name' => 'ગુજરાતી',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'gv',
|
'code' => 'gv',
|
||||||
'native_name' => 'Gaelg, Gailck',
|
'native_name' => 'Gaelg, Gailck',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ha',
|
'code' => 'ha',
|
||||||
'native_name' => '(Hausa) هَوُسَ',
|
'native_name' => '(Hausa) هَوُسَ',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'he',
|
'code' => 'he',
|
||||||
'native_name' => 'עברית',
|
'native_name' => 'עברית',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'hi',
|
'code' => 'hi',
|
||||||
'native_name' => 'हिन्दी, हिंदी',
|
'native_name' => 'हिन्दी, हिंदी',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ho',
|
'code' => 'ho',
|
||||||
'native_name' => 'Hiri Motu',
|
'native_name' => 'Hiri Motu',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'hr',
|
'code' => 'hr',
|
||||||
'native_name' => 'Hrvatski jezik',
|
'native_name' => 'hrvatski jezik',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ht',
|
'code' => 'ht',
|
||||||
'native_name' => 'Kreyòl ayisyen',
|
'native_name' => 'Kreyòl ayisyen',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'hu',
|
'code' => 'hu',
|
||||||
'native_name' => 'Magyar',
|
'native_name' => 'magyar',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'hy',
|
'code' => 'hy',
|
||||||
'native_name' => 'Հայերեն',
|
'native_name' => 'Հայերեն',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'hz',
|
'code' => 'hz',
|
||||||
'native_name' => 'Otjiherero',
|
'native_name' => 'Otjiherero',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ia',
|
'code' => 'ia',
|
||||||
'native_name' => 'Interlingua',
|
'native_name' => 'Interlingua',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'id',
|
'code' => 'id',
|
||||||
'native_name' => 'Bahasa Indonesia',
|
'native_name' => 'Bahasa Indonesia',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ie',
|
'code' => 'ie',
|
||||||
'native_name' => 'Interlingue, formerly Occidental',
|
'native_name' =>
|
||||||
|
'(originally:) Occidental, (after WWII:) Interlingue',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ig',
|
'code' => 'ig',
|
||||||
'native_name' => 'Asụsụ Igbo',
|
'native_name' => 'Asụsụ Igbo',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ii',
|
'code' => 'ii',
|
||||||
'native_name' => 'ꆈꌠ꒿ Nuosuhxop',
|
'native_name' => 'ꆈꌠ꒿ Nuosuhxop',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ik',
|
'code' => 'ik',
|
||||||
'native_name' => 'Iñupiaq, Iñupiatun',
|
'native_name' => 'Iñupiaq, Iñupiatun',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'io',
|
'code' => 'io',
|
||||||
'native_name' => 'Ido',
|
'native_name' => 'Ido',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'is',
|
'code' => 'is',
|
||||||
'native_name' => 'Íslenska',
|
'native_name' => 'Íslenska',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'it',
|
'code' => 'it',
|
||||||
'native_name' => 'Italiano',
|
'native_name' => 'Italiano',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'iu',
|
'code' => 'iu',
|
||||||
'native_name' => 'ᐃᓄᒃᑎᑐᑦ',
|
'native_name' => 'ᐃᓄᒃᑎᑐᑦ',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ja',
|
'code' => 'ja',
|
||||||
'native_name' => '日本語 (にほんご)',
|
'native_name' => '日本語 (にほんご)',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'jv',
|
'code' => 'jv',
|
||||||
'native_name' => 'ꦧꦱꦗꦮ, Basa Jawa',
|
'native_name' => 'ꦧꦱꦗꦮ, Basa Jawa',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ka',
|
'code' => 'ka',
|
||||||
'native_name' => 'ქართული',
|
'native_name' => 'ქართული',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'kg',
|
'code' => 'kg',
|
||||||
'native_name' => 'Kikongo',
|
'native_name' => 'Kikongo',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ki',
|
'code' => 'ki',
|
||||||
'native_name' => 'Gĩkũyũ',
|
'native_name' => 'Gĩkũyũ',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'kj',
|
'code' => 'kj',
|
||||||
'native_name' => 'Kuanyama',
|
'native_name' => 'Kuanyama',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'kk',
|
'code' => 'kk',
|
||||||
'native_name' => 'қазақ тілі',
|
'native_name' => 'қазақ тілі',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'kl',
|
'code' => 'kl',
|
||||||
'native_name' => 'Kalaallisut, kalaallit oqaasii',
|
'native_name' => 'kalaallisut, kalaallit oqaasii',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'km',
|
'code' => 'km',
|
||||||
'native_name' => 'ខ្មែរ, ខេមរភាសា, ភាសាខ្មែរ',
|
'native_name' => 'ខ្មែរ, ខេមរភាសា, ភាសាខ្មែរ',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'kn',
|
'code' => 'kn',
|
||||||
'native_name' => 'ಕನ್ನಡ',
|
'native_name' => 'ಕನ್ನಡ',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ko',
|
'code' => 'ko',
|
||||||
'native_name' => '한국어',
|
'native_name' => '한국어',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'kr',
|
'code' => 'kr',
|
||||||
'native_name' => 'Kanuri',
|
'native_name' => 'Kanuri',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ks',
|
'code' => 'ks',
|
||||||
'native_name' => 'कश्मीरी, كشميري',
|
'native_name' => 'कश्मीरी, كشميري',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ku',
|
'code' => 'ku',
|
||||||
'native_name' => 'Kurdî, کوردی',
|
'native_name' => 'Kurdî, کوردی',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'kv',
|
'code' => 'kv',
|
||||||
'native_name' => 'коми кыв',
|
'native_name' => 'коми кыв',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'kw',
|
'code' => 'kw',
|
||||||
'native_name' => 'Kernewek',
|
'native_name' => 'Kernewek',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ky',
|
'code' => 'ky',
|
||||||
'native_name' => 'Кыргызча, Кыргыз тили',
|
'native_name' => 'Кыргызча, Кыргыз тили',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'la',
|
'code' => 'la',
|
||||||
'native_name' => 'Latine, lingua latina',
|
'native_name' => 'latine, lingua latina',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'lb',
|
'code' => 'lb',
|
||||||
'native_name' => 'Lëtzebuergesch',
|
'native_name' => 'Lëtzebuergesch',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'lg',
|
'code' => 'lg',
|
||||||
'native_name' => 'Luganda',
|
'native_name' => 'Luganda',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'li',
|
'code' => 'li',
|
||||||
'native_name' => 'Limburgs',
|
'native_name' => 'Limburgs',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ln',
|
'code' => 'ln',
|
||||||
'native_name' => 'Lingála',
|
'native_name' => 'Lingála',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'lo',
|
'code' => 'lo',
|
||||||
'native_name' => 'ພາສາລາວ',
|
'native_name' => 'ພາສາລາວ',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'lt',
|
'code' => 'lt',
|
||||||
'native_name' => 'Lietuvių kalba',
|
'native_name' => 'lietuvių kalba',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'lu',
|
'code' => 'lu',
|
||||||
'native_name' => 'Kiluba',
|
'native_name' => 'Kiluba',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'lv',
|
'code' => 'lv',
|
||||||
'native_name' => 'Latviešu valoda',
|
'native_name' => 'latviešu valoda',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'mg',
|
'code' => 'mg',
|
||||||
'native_name' => 'Fiteny malagasy',
|
'native_name' => 'fiteny malagasy',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'mh',
|
'code' => 'mh',
|
||||||
'native_name' => 'Kajin M̧ajeļ',
|
'native_name' => 'Kajin M̧ajeļ',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'mi',
|
'code' => 'mi',
|
||||||
'native_name' => 'Te reo Māori',
|
'native_name' => 'te reo Māori',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'mk',
|
'code' => 'mk',
|
||||||
'native_name' => 'македонски јазик',
|
'native_name' => 'македонски јазик',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ml',
|
'code' => 'ml',
|
||||||
'native_name' => 'മലയാളം',
|
'native_name' => 'മലയാളം',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'mn',
|
'code' => 'mn',
|
||||||
'native_name' => 'Монгол хэл',
|
'native_name' => 'Монгол хэл',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'mr',
|
'code' => 'mr',
|
||||||
'native_name' => 'मराठी',
|
'native_name' => 'मराठी',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ms',
|
'code' => 'ms',
|
||||||
'native_name' => 'Bahasa Melayu, بهاس ملايو',
|
'native_name' => 'Bahasa Melayu, بهاس ملايو',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'mt',
|
'code' => 'mt',
|
||||||
'native_name' => 'Malti',
|
'native_name' => 'Malti',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'my',
|
'code' => 'my',
|
||||||
'native_name' => 'ဗမာစာ',
|
'native_name' => 'ဗမာစာ',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'na',
|
'code' => 'na',
|
||||||
'native_name' => 'Dorerin Naoero',
|
'native_name' => 'Dorerin Naoero',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'nb',
|
'code' => 'nb',
|
||||||
'native_name' => 'Norsk Bokmål',
|
'native_name' => 'Norsk Bokmål',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'nd',
|
'code' => 'nd',
|
||||||
'native_name' => 'isiNdebele',
|
'native_name' => 'isiNdebele',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ne',
|
'code' => 'ne',
|
||||||
'native_name' => 'नेपाली',
|
'native_name' => 'नेपाली',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ng',
|
'code' => 'ng',
|
||||||
'native_name' => 'Owambo',
|
'native_name' => 'Owambo',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'nl',
|
'code' => 'nl',
|
||||||
'native_name' => 'Nederlands, Vlaams',
|
'native_name' => 'Nederlands, Vlaams',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'nn',
|
'code' => 'nn',
|
||||||
'native_name' => 'Norsk Nynorsk',
|
'native_name' => 'Norsk Nynorsk',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'no',
|
'code' => 'no',
|
||||||
'native_name' => 'Norsk',
|
'native_name' => 'Norsk',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'nr',
|
'code' => 'nr',
|
||||||
'native_name' => 'isiNdebele',
|
'native_name' => 'isiNdebele',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'nv',
|
'code' => 'nv',
|
||||||
'native_name' => 'Diné bizaad',
|
'native_name' => 'Diné bizaad',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ny',
|
'code' => 'ny',
|
||||||
'native_name' => 'Chicheŵa, chinyanja',
|
'native_name' => 'chiCheŵa, chinyanja',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'oc',
|
'code' => 'oc',
|
||||||
'native_name' => 'Occitan, lenga d’òc',
|
'native_name' => 'occitan, lenga d’òc',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'oj',
|
'code' => 'oj',
|
||||||
'native_name' => 'ᐊᓂᔑᓈᐯᒧᐎᓐ',
|
'native_name' => 'ᐊᓂᔑᓈᐯᒧᐎᓐ',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'om',
|
'code' => 'om',
|
||||||
'native_name' => 'Afaan Oromoo',
|
'native_name' => 'Afaan Oromoo',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'or',
|
'code' => 'or',
|
||||||
'native_name' => 'ଓଡ଼ିଆ',
|
'native_name' => 'ଓଡ଼ିଆ',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'os',
|
'code' => 'os',
|
||||||
'native_name' => 'ирон æвзаг',
|
'native_name' => 'ирон æвзаг',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'pa',
|
'code' => 'pa',
|
||||||
'native_name' => 'ਪੰਜਾਬੀ, پنجابی',
|
'native_name' => 'ਪੰਜਾਬੀ, پنجابی',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'pi',
|
'code' => 'pi',
|
||||||
'native_name' => 'पालि, पाळि',
|
'native_name' => 'पालि, पाळि',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'pl',
|
'code' => 'pl',
|
||||||
'native_name' => 'język polski, polszczyzna',
|
'native_name' => 'język polski, polszczyzna',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ps',
|
'code' => 'ps',
|
||||||
'native_name' => 'پښتو',
|
'native_name' => 'پښتو',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'pt',
|
'code' => 'pt',
|
||||||
'native_name' => 'Português',
|
'native_name' => 'Português',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'qu',
|
'code' => 'qu',
|
||||||
'native_name' => 'Runa Simi, Kichwa',
|
'native_name' => 'Runa Simi, Kichwa',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'rm',
|
'code' => 'rm',
|
||||||
'native_name' => 'Rumantsch Grischun',
|
'native_name' => 'Rumantsch Grischun',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'rn',
|
'code' => 'rn',
|
||||||
'native_name' => 'Ikirundi',
|
'native_name' => 'Ikirundi',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ro',
|
'code' => 'ro',
|
||||||
'native_name' => 'Română',
|
'native_name' => 'Română',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ru',
|
'code' => 'ru',
|
||||||
'native_name' => 'Pусский',
|
'native_name' => 'русский',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'rw',
|
'code' => 'rw',
|
||||||
'native_name' => 'Ikinyarwanda',
|
'native_name' => 'Ikinyarwanda',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'sa',
|
'code' => 'sa',
|
||||||
'native_name' => 'संस्कृतम्',
|
'native_name' => 'संस्कृतम्',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'sc',
|
'code' => 'sc',
|
||||||
'native_name' => 'Sardu',
|
'native_name' => 'sardu',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'sd',
|
'code' => 'sd',
|
||||||
'native_name' => 'सिन्धी, سنڌي، سندھی',
|
'native_name' => 'सिन्धी, سنڌي، سندھی',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'se',
|
'code' => 'se',
|
||||||
'native_name' => 'Davvisámegiella',
|
'native_name' => 'Davvisámegiella',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'sg',
|
'code' => 'sg',
|
||||||
'native_name' => 'Yângâ tî sängö',
|
'native_name' => 'yângâ tî sängö',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'si',
|
'code' => 'si',
|
||||||
'native_name' => 'සිංහල',
|
'native_name' => 'සිංහල',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'sk',
|
'code' => 'sk',
|
||||||
'native_name' => 'Slovenčina, Slovenský Jazyk',
|
'native_name' => 'Slovenčina, Slovenský Jazyk',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'sl',
|
'code' => 'sl',
|
||||||
'native_name' => 'Slovenski Jezik, Slovenščina',
|
'native_name' => 'Slovenski Jezik, Slovenščina',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'sm',
|
'code' => 'sm',
|
||||||
'native_name' => "Gagana fa'a Samoa",
|
'native_name' => "gagana fa'a Samoa",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'sn',
|
'code' => 'sn',
|
||||||
'native_name' => 'chiShona',
|
'native_name' => 'chiShona',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'so',
|
'code' => 'so',
|
||||||
'native_name' => 'Soomaaliga, af Soomaali',
|
'native_name' => 'Soomaaliga, af Soomaali',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'sq',
|
'code' => 'sq',
|
||||||
'native_name' => 'Shqip',
|
'native_name' => 'Shqip',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'sr',
|
'code' => 'sr',
|
||||||
'native_name' => 'српски језик',
|
'native_name' => 'српски језик',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ss',
|
'code' => 'ss',
|
||||||
'native_name' => 'SiSwati',
|
'native_name' => 'SiSwati',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'st',
|
'code' => 'st',
|
||||||
'native_name' => 'Sesotho',
|
'native_name' => 'Sesotho',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'su',
|
'code' => 'su',
|
||||||
'native_name' => 'Basa Sunda',
|
'native_name' => 'Basa Sunda',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'sv',
|
'code' => 'sv',
|
||||||
'native_name' => 'Svenska',
|
'native_name' => 'Svenska',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'sw',
|
'code' => 'sw',
|
||||||
'native_name' => 'Kiswahili',
|
'native_name' => 'Kiswahili',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ta',
|
'code' => 'ta',
|
||||||
'native_name' => 'தமிழ்',
|
'native_name' => 'தமிழ்',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'te',
|
'code' => 'te',
|
||||||
'native_name' => 'తెలుగు',
|
'native_name' => 'తెలుగు',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'tg',
|
'code' => 'tg',
|
||||||
'native_name' => 'тоҷикӣ, toçikī, تاجیکی',
|
'native_name' => 'тоҷикӣ, toçikī, تاجیکی',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'th',
|
'code' => 'th',
|
||||||
'native_name' => 'ไทย',
|
'native_name' => 'ไทย',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ti',
|
'code' => 'ti',
|
||||||
'native_name' => 'ትግርኛ',
|
'native_name' => 'ትግርኛ',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'tk',
|
'code' => 'tk',
|
||||||
'native_name' => 'Türkmen, Түркмен',
|
'native_name' => 'Türkmen, Түркмен',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'tl',
|
'code' => 'tl',
|
||||||
'native_name' => 'Wikang Tagalog',
|
'native_name' => 'Wikang Tagalog',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'tn',
|
'code' => 'tn',
|
||||||
'native_name' => 'Setswana',
|
'native_name' => 'Setswana',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'to',
|
'code' => 'to',
|
||||||
'native_name' => 'Faka Tonga',
|
'native_name' => 'Faka Tonga',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'tr',
|
'code' => 'tr',
|
||||||
'native_name' => 'Türkçe',
|
'native_name' => 'Türkçe',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ts',
|
'code' => 'ts',
|
||||||
'native_name' => 'Xitsonga',
|
'native_name' => 'Xitsonga',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'tt',
|
'code' => 'tt',
|
||||||
'native_name' => 'татар теле, tatar tele',
|
'native_name' => 'татар теле, tatar tele',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'tw',
|
'code' => 'tw',
|
||||||
'native_name' => 'Twi',
|
'native_name' => 'Twi',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ty',
|
'code' => 'ty',
|
||||||
'native_name' => 'Reo Tahiti',
|
'native_name' => 'Reo Tahiti',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ug',
|
'code' => 'ug',
|
||||||
'native_name' => 'ئۇيغۇرچە, Uyghurche',
|
'native_name' => 'ئۇيغۇرچە, Uyghurche',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'uk',
|
'code' => 'uk',
|
||||||
'native_name' => 'Українська',
|
'native_name' => 'Українська',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'ur',
|
'code' => 'ur',
|
||||||
'native_name' => 'اردو',
|
'native_name' => 'اردو',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'uz',
|
'code' => 'uz',
|
||||||
'native_name' => 'Oʻzbek, Ўзбек, أۇزبېك',
|
'native_name' => 'Oʻzbek, Ўзбек, أۇزبېك',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 've',
|
'code' => 've',
|
||||||
'native_name' => 'Tshivenḓa',
|
'native_name' => 'Tshivenḓa',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'vi',
|
'code' => 'vi',
|
||||||
'native_name' => 'Tiếng Việt',
|
'native_name' => 'Tiếng Việt',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'vo',
|
'code' => 'vo',
|
||||||
'native_name' => 'Volapük',
|
'native_name' => 'Volapük',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'wa',
|
'code' => 'wa',
|
||||||
'native_name' => 'Walon',
|
'native_name' => 'Walon',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'wo',
|
'code' => 'wo',
|
||||||
'native_name' => 'Wollof',
|
'native_name' => 'Wollof',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'xh',
|
'code' => 'xh',
|
||||||
'native_name' => 'isiXhosa',
|
'native_name' => 'isiXhosa',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'yi',
|
'code' => 'yi',
|
||||||
'native_name' => 'ייִדיש',
|
'native_name' => 'ייִדיש',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'yo',
|
'code' => 'yo',
|
||||||
'native_name' => 'Yorùbá',
|
'native_name' => 'Yorùbá',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'za',
|
'code' => 'za',
|
||||||
'native_name' => 'Saɯ cueŋƅ, Saw cuengh',
|
'native_name' => 'Saɯ cueŋƅ, Saw cuengh',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'zh',
|
'code' => 'zh',
|
||||||
'native_name' => '中文 (Zhōngwén), 汉语, 漢語',
|
'native_name' => '中文 (Zhōngwén), 汉语, 漢語',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'code' => 'zu',
|
'code' => 'zu',
|
||||||
'native_name' => 'isiZulu',
|
'native_name' => 'isiZulu',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($data as $languageLine) {
|
$this->db
|
||||||
$this->db
|
->table('languages')
|
||||||
->table('languages')
|
->ignore(true)
|
||||||
->ignore(true)
|
->insertBatch($data);
|
||||||
->insert($languageLine);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
591
app/Database/Seeds/PlatformSeeder.php
Normal file
591
app/Database/Seeds/PlatformSeeder.php
Normal file
|
|
@ -0,0 +1,591 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class PlatformsSeeder Inserts values in platforms table in database
|
||||||
|
*
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @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
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
[
|
||||||
|
'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/',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'slug' => 'paypal',
|
||||||
|
'type' => 'funding',
|
||||||
|
'label' => 'Paypal',
|
||||||
|
'home_url' => 'https://www.paypal.com/',
|
||||||
|
'submit_url' => 'https://www.paypal.com/paypalme/my/grab',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'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/#/',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'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' => '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' => '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',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->db
|
||||||
|
->table('platforms')
|
||||||
|
->ignore(true)
|
||||||
|
->insertBatch($data);
|
||||||
|
}
|
||||||
|
}
|
||||||
41
app/Database/Seeds/TestSeeder.php
Normal file
41
app/Database/Seeds/TestSeeder.php
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class TestSeeder Inserts a superadmin user in the database
|
||||||
|
*
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @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,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,22 +3,22 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @copyright 2020 Ad Aures
|
* @copyright 2020 Podlibre
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace App\Entities;
|
namespace App\Entities;
|
||||||
|
|
||||||
|
use ActivityPub\Entities\Actor as ActivityPubActor;
|
||||||
use App\Models\PodcastModel;
|
use App\Models\PodcastModel;
|
||||||
use Modules\Fediverse\Entities\Actor as FediverseActor;
|
use RuntimeException;
|
||||||
use Override;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property Podcast|null $podcast
|
* @property Podcast|null $podcast
|
||||||
* @property boolean $is_podcast
|
* @property boolean $is_podcast
|
||||||
*/
|
*/
|
||||||
class Actor extends FediverseActor
|
class Actor extends ActivityPubActor
|
||||||
{
|
{
|
||||||
protected ?Podcast $podcast = null;
|
protected ?Podcast $podcast = null;
|
||||||
|
|
||||||
|
|
@ -26,36 +26,19 @@ class Actor extends FediverseActor
|
||||||
|
|
||||||
public function getIsPodcast(): bool
|
public function getIsPodcast(): bool
|
||||||
{
|
{
|
||||||
return $this->getPodcast() instanceof Podcast;
|
return $this->getPodcast() !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPodcast(): ?Podcast
|
public function getPodcast(): ?Podcast
|
||||||
{
|
{
|
||||||
if (! $this->podcast instanceof Podcast) {
|
if ($this->id === null) {
|
||||||
$this->podcast = new PodcastModel()
|
throw new RuntimeException('Podcast id must be set before getting associated podcast.');
|
||||||
->getPodcastByActorId($this->id);
|
}
|
||||||
|
|
||||||
|
if ($this->podcast === null) {
|
||||||
|
$this->podcast = (new PodcastModel())->getPodcastByActorId($this->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->podcast;
|
return $this->podcast;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function getAvatarImageUrl(): string
|
|
||||||
{
|
|
||||||
if ($this->podcast instanceof Podcast) {
|
|
||||||
return $this->podcast->cover->thumbnail_url;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parent::getAvatarImageUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function getAvatarImageMimetype(): string
|
|
||||||
{
|
|
||||||
if ($this->podcast instanceof Podcast) {
|
|
||||||
return $this->podcast->cover->thumbnail_mimetype;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parent::getAvatarImageMimetype();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @copyright 2020 Ad Aures
|
* @copyright 2020 Podlibre
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
@ -15,7 +15,7 @@ use CodeIgniter\Entity\Entity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $id
|
* @property int $id
|
||||||
* @property ?int $parent_id
|
* @property int $parent_id
|
||||||
* @property Category|null $parent
|
* @property Category|null $parent
|
||||||
* @property string $code
|
* @property string $code
|
||||||
* @property string $apple_category
|
* @property string $apple_category
|
||||||
|
|
@ -29,20 +29,22 @@ class Category extends Entity
|
||||||
* @var array<string, string>
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'id' => 'integer',
|
'id' => 'integer',
|
||||||
'parent_id' => '?integer',
|
'parent_id' => '?integer',
|
||||||
'code' => 'string',
|
'code' => 'string',
|
||||||
'apple_category' => 'string',
|
'apple_category' => 'string',
|
||||||
'google_category' => 'string',
|
'google_category' => 'string',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @noRector ReturnTypeDeclarationRector
|
||||||
|
*/
|
||||||
public function getParent(): ?self
|
public function getParent(): ?self
|
||||||
{
|
{
|
||||||
if ($this->parent_id === null) {
|
if ($this->parent_id === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CategoryModel()
|
return (new CategoryModel())->getCategoryById($this->parent_id);
|
||||||
->getCategoryById($this->parent_id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,156 +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\Entities\Clip;
|
|
||||||
|
|
||||||
use App\Entities\Episode;
|
|
||||||
use App\Entities\Podcast;
|
|
||||||
use App\Models\EpisodeModel;
|
|
||||||
use App\Models\PodcastModel;
|
|
||||||
use CodeIgniter\Entity\Entity;
|
|
||||||
use CodeIgniter\Files\File;
|
|
||||||
use CodeIgniter\I18n\Time;
|
|
||||||
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
|
|
||||||
* @property int $podcast_id
|
|
||||||
* @property Podcast $podcast
|
|
||||||
* @property int $episode_id
|
|
||||||
* @property Episode $episode
|
|
||||||
* @property string $title
|
|
||||||
* @property double $start_time
|
|
||||||
* @property ?double $end_time
|
|
||||||
* @property double $duration
|
|
||||||
* @property string $type
|
|
||||||
* @property int|null $media_id
|
|
||||||
* @property Video|Audio|null $media
|
|
||||||
* @property array<mixed>|null $metadata
|
|
||||||
* @property string $status
|
|
||||||
* @property string $logs
|
|
||||||
* @property User $user
|
|
||||||
* @property int $created_by
|
|
||||||
* @property int $updated_by
|
|
||||||
* @property Time|null $job_started_at
|
|
||||||
* @property Time|null $job_ended_at
|
|
||||||
*/
|
|
||||||
class BaseClip extends Entity
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var Video|Audio|null
|
|
||||||
*/
|
|
||||||
protected $media;
|
|
||||||
|
|
||||||
protected ?int $job_duration = null;
|
|
||||||
|
|
||||||
protected ?float $end_time = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array<int, string>
|
|
||||||
* @phpstan-var list<string>
|
|
||||||
*/
|
|
||||||
protected $dates = ['created_at', 'updated_at', 'job_started_at', 'job_ended_at'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array<string, string>
|
|
||||||
*/
|
|
||||||
protected $casts = [
|
|
||||||
'id' => 'integer',
|
|
||||||
'podcast_id' => 'integer',
|
|
||||||
'episode_id' => 'integer',
|
|
||||||
'title' => 'string',
|
|
||||||
'start_time' => 'double',
|
|
||||||
'duration' => 'double',
|
|
||||||
'type' => 'string',
|
|
||||||
'media_id' => '?integer',
|
|
||||||
'metadata' => '?json-array',
|
|
||||||
'status' => 'string',
|
|
||||||
'logs' => 'string',
|
|
||||||
'created_by' => 'integer',
|
|
||||||
'updated_by' => 'integer',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function getJobDuration(): ?int
|
|
||||||
{
|
|
||||||
if ($this->job_duration === null && $this->job_started_at && $this->job_ended_at) {
|
|
||||||
$this->job_duration = ($this->job_started_at->difference($this->job_ended_at))
|
|
||||||
->getSeconds();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->job_duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getEndTime(): float
|
|
||||||
{
|
|
||||||
if ($this->end_time === null) {
|
|
||||||
$this->end_time = $this->start_time + $this->duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->end_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPodcast(): ?Podcast
|
|
||||||
{
|
|
||||||
return new PodcastModel()
|
|
||||||
->getPodcastById($this->podcast_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getEpisode(): ?Episode
|
|
||||||
{
|
|
||||||
return new EpisodeModel()
|
|
||||||
->getEpisodeById($this->episode_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUser(): ?User
|
|
||||||
{
|
|
||||||
/** @var ?User */
|
|
||||||
return new UserModel()
|
|
||||||
->find($this->created_by);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setMedia(File $file, string $fileKey): static
|
|
||||||
{
|
|
||||||
if ($this->media_id !== null) {
|
|
||||||
$this->getMedia()
|
|
||||||
->setFile($file);
|
|
||||||
$this->getMedia()
|
|
||||||
->updated_by = $this->attributes['updated_by'];
|
|
||||||
new MediaModel('audio')
|
|
||||||
->updateMedia($this->getMedia());
|
|
||||||
} else {
|
|
||||||
$media = new Audio([
|
|
||||||
'file_key' => $fileKey,
|
|
||||||
'language_code' => $this->getPodcast()
|
|
||||||
->language_code,
|
|
||||||
'uploaded_by' => $this->attributes['updated_by'],
|
|
||||||
'updated_by' => $this->attributes['updated_by'],
|
|
||||||
]);
|
|
||||||
$media->setFile($file);
|
|
||||||
|
|
||||||
$this->attributes['media_id'] = new MediaModel()->saveMedia($media);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getMedia(): Audio | Video | null
|
|
||||||
{
|
|
||||||
if ($this->media_id !== null && $this->media === null) {
|
|
||||||
$this->media = new MediaModel($this->type)
|
|
||||||
->getMediaById($this->media_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->media;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +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\Entities\Clip;
|
|
||||||
|
|
||||||
class Soundbite extends BaseClip
|
|
||||||
{
|
|
||||||
protected string $type = 'audio';
|
|
||||||
}
|
|
||||||
|
|
@ -1,88 +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\Entities\Clip;
|
|
||||||
|
|
||||||
use CodeIgniter\Files\File;
|
|
||||||
use Modules\Media\Entities\Video;
|
|
||||||
use Modules\Media\Models\MediaModel;
|
|
||||||
use Override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @property array{name:string,preview:string} $theme
|
|
||||||
* @property string $format
|
|
||||||
*/
|
|
||||||
class VideoClip extends BaseClip
|
|
||||||
{
|
|
||||||
protected string $type = 'video';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string, mixed>|null $data
|
|
||||||
*/
|
|
||||||
public function __construct(?array $data = null)
|
|
||||||
{
|
|
||||||
parent::__construct($data);
|
|
||||||
|
|
||||||
if ($this->metadata !== null && $this->metadata !== []) {
|
|
||||||
$this->theme = $this->metadata['theme'];
|
|
||||||
$this->format = $this->metadata['format'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array{name:string,preview:string} $theme
|
|
||||||
*/
|
|
||||||
public function setTheme(array $theme): self
|
|
||||||
{
|
|
||||||
// TODO: change?
|
|
||||||
$this->attributes['metadata'] = json_decode($this->attributes['metadata'] ?? '[]', true);
|
|
||||||
|
|
||||||
$this->attributes['theme'] = $theme;
|
|
||||||
$this->attributes['metadata']['theme'] = $theme;
|
|
||||||
|
|
||||||
$this->attributes['metadata'] = json_encode($this->attributes['metadata']);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setFormat(string $format): self
|
|
||||||
{
|
|
||||||
$this->attributes['metadata'] = json_decode((string) $this->attributes['metadata'], true);
|
|
||||||
|
|
||||||
$this->attributes['format'] = $format;
|
|
||||||
$this->attributes['metadata']['format'] = $format;
|
|
||||||
|
|
||||||
$this->attributes['metadata'] = json_encode($this->attributes['metadata']);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function setMedia(File $file, string $fileKey): static
|
|
||||||
{
|
|
||||||
if ($this->attributes['media_id'] !== null) {
|
|
||||||
// media is already set, do nothing
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
$video = new Video([
|
|
||||||
'file_key' => $fileKey,
|
|
||||||
'language_code' => $this->getPodcast()
|
|
||||||
->language_code,
|
|
||||||
'uploaded_by' => $this->attributes['created_by'],
|
|
||||||
'updated_by' => $this->attributes['created_by'],
|
|
||||||
]);
|
|
||||||
$video->setFile($file);
|
|
||||||
|
|
||||||
$this->attributes['media_id'] = new MediaModel('video')->saveMedia($video);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @copyright 2020 Ad Aures
|
* @copyright 2020 Podlibre
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
@ -45,19 +45,22 @@ class Credit extends Entity
|
||||||
* @var array<string, string>
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'podcast_id' => 'integer',
|
'podcast_id' => 'integer',
|
||||||
'episode_id' => '?integer',
|
'episode_id' => '?integer',
|
||||||
'person_id' => 'integer',
|
'person_id' => 'integer',
|
||||||
'full_name' => 'string',
|
'full_name' => 'string',
|
||||||
'person_group' => 'string',
|
'person_group' => 'string',
|
||||||
'person_role' => 'string',
|
'person_role' => 'string',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function getPerson(): ?Person
|
public function getPerson(): ?Person
|
||||||
{
|
{
|
||||||
if (! $this->person instanceof Person) {
|
if ($this->person_id === null) {
|
||||||
$this->person = new PersonModel()
|
throw new RuntimeException('Credit must have person_id before getting person.');
|
||||||
->getPersonById($this->person_id);
|
}
|
||||||
|
|
||||||
|
if ($this->person === null) {
|
||||||
|
$this->person = (new PersonModel())->getPersonById($this->person_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->person;
|
return $this->person;
|
||||||
|
|
@ -65,9 +68,12 @@ class Credit extends Entity
|
||||||
|
|
||||||
public function getPodcast(): ?Podcast
|
public function getPodcast(): ?Podcast
|
||||||
{
|
{
|
||||||
if (! $this->podcast instanceof Podcast) {
|
if ($this->podcast_id === null) {
|
||||||
$this->podcast = new PodcastModel()
|
throw new RuntimeException('Credit must have podcast_id before getting podcast.');
|
||||||
->getPodcastById($this->podcast_id);
|
}
|
||||||
|
|
||||||
|
if ($this->podcast === null) {
|
||||||
|
$this->podcast = (new PodcastModel())->getPodcastById($this->podcast_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->podcast;
|
return $this->podcast;
|
||||||
|
|
@ -79,9 +85,8 @@ class Credit extends Entity
|
||||||
throw new RuntimeException('Credit must have episode_id before getting episode.');
|
throw new RuntimeException('Credit must have episode_id before getting episode.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $this->episode instanceof Episode) {
|
if ($this->episode === null) {
|
||||||
$this->episode = new EpisodeModel()
|
$this->episode = (new EpisodeModel())->getPublishedEpisodeById($this->podcast_id, $this->episode_id);
|
||||||
->getPublishedEpisodeById($this->podcast_id, $this->episode_id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->episode;
|
return $this->episode;
|
||||||
|
|
@ -89,11 +94,10 @@ class Credit extends Entity
|
||||||
|
|
||||||
public function getGroupLabel(): string
|
public function getGroupLabel(): string
|
||||||
{
|
{
|
||||||
if ($this->person_group === '') {
|
if ($this->person_group === null) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
return lang("PersonsTaxonomy.persons.{$this->person_group}.label");
|
return lang("PersonsTaxonomy.persons.{$this->person_group}.label");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,7 +111,6 @@ class Credit extends Entity
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
return lang("PersonsTaxonomy.persons.{$this->person_group}.roles.{$this->person_role}.label");
|
return lang("PersonsTaxonomy.persons.{$this->person_group}.roles.{$this->person_role}.label");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,113 +3,108 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @copyright 2020 Ad Aures
|
* @copyright 2020 Podlibre
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace App\Entities;
|
namespace App\Entities;
|
||||||
|
|
||||||
use App\Entities\Clip\Soundbite;
|
use App\Libraries\SimpleRSSElement;
|
||||||
use App\Models\ClipModel;
|
|
||||||
use App\Models\EpisodeCommentModel;
|
|
||||||
use App\Models\EpisodeModel;
|
|
||||||
use App\Models\PersonModel;
|
use App\Models\PersonModel;
|
||||||
use App\Models\PodcastModel;
|
use App\Models\PodcastModel;
|
||||||
use App\Models\PostModel;
|
use App\Models\SoundbiteModel;
|
||||||
|
use App\Models\StatusModel;
|
||||||
use CodeIgniter\Entity\Entity;
|
use CodeIgniter\Entity\Entity;
|
||||||
use CodeIgniter\Files\File;
|
use CodeIgniter\Files\File;
|
||||||
use CodeIgniter\HTTP\Files\UploadedFile;
|
use CodeIgniter\HTTP\Files\UploadedFile;
|
||||||
use CodeIgniter\I18n\Time;
|
use CodeIgniter\I18n\Time;
|
||||||
use Exception;
|
use League\CommonMark\CommonMarkConverter;
|
||||||
use League\CommonMark\Environment\Environment;
|
use RuntimeException;
|
||||||
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\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 $id
|
||||||
* @property int $podcast_id
|
* @property int $podcast_id
|
||||||
* @property Podcast $podcast
|
* @property Podcast $podcast
|
||||||
* @property ?string $preview_id
|
|
||||||
* @property string $preview_link
|
|
||||||
* @property string $link
|
* @property string $link
|
||||||
* @property string $guid
|
* @property string $guid
|
||||||
* @property string $slug
|
* @property string $slug
|
||||||
* @property string $title
|
* @property string $title
|
||||||
* @property int $audio_id
|
* @property File $audio_file
|
||||||
* @property ?Audio $audio
|
* @property string $audio_file_url
|
||||||
* @property string $audio_url
|
* @property string $audio_file_analytics_url
|
||||||
* @property string $audio_web_url
|
* @property string $audio_file_web_url
|
||||||
* @property string $audio_opengraph_url
|
* @property string $audio_file_opengraph_url
|
||||||
|
* @property string $audio_file_path
|
||||||
|
* @property double $audio_file_duration
|
||||||
|
* @property string $audio_file_mimetype
|
||||||
|
* @property int $audio_file_size
|
||||||
|
* @property int $audio_file_header_size
|
||||||
* @property string|null $description Holds text only description, striped of any markdown or html special characters
|
* @property string|null $description Holds text only description, striped of any markdown or html special characters
|
||||||
* @property string $description_markdown
|
* @property string $description_markdown
|
||||||
* @property string $description_html
|
* @property string $description_html
|
||||||
* @property ?int $cover_id
|
* @property Image $image
|
||||||
* @property ?Image $cover
|
* @property string|null $image_path
|
||||||
* @property int|null $transcript_id
|
* @property string|null $image_mimetype
|
||||||
* @property Transcript|null $transcript
|
* @property File|null $transcript_file
|
||||||
* @property string|null $transcript_remote_url
|
* @property string|null $transcript_file_url
|
||||||
* @property int|null $chapters_id
|
* @property string|null $transcript_file_path
|
||||||
* @property Chapters|null $chapters
|
* @property string|null $transcript_file_remote_url
|
||||||
* @property string|null $chapters_remote_url
|
* @property File|null $chapters_file
|
||||||
|
* @property string|null $chapters_file_url
|
||||||
|
* @property string|null $chapters_file_path
|
||||||
|
* @property string|null $chapters_file_remote_url
|
||||||
* @property string|null $parental_advisory
|
* @property string|null $parental_advisory
|
||||||
* @property ?int $number
|
* @property int $number
|
||||||
* @property ?int $season_number
|
* @property int $season_number
|
||||||
* @property string $type
|
* @property string $type
|
||||||
* @property bool $is_blocked
|
* @property bool $is_blocked
|
||||||
* @property Location|null $location
|
* @property Location|null $location
|
||||||
* @property string|null $location_name
|
* @property string|null $location_name
|
||||||
* @property string|null $location_geo
|
* @property string|null $location_geo
|
||||||
* @property string|null $location_osm
|
* @property string|null $location_osm
|
||||||
* @property bool $is_published_on_hubs
|
* @property array|null $custom_rss
|
||||||
* @property int $downloads_count
|
* @property string $custom_rss_string
|
||||||
* @property int $posts_count
|
* @property int $favourites_total
|
||||||
* @property int $comments_count
|
* @property int $reblogs_total
|
||||||
* @property EpisodeComment[]|null $comments
|
* @property int $statuses_total
|
||||||
* @property bool $is_premium
|
|
||||||
* @property int $created_by
|
* @property int $created_by
|
||||||
* @property int $updated_by
|
* @property int $updated_by
|
||||||
* @property string $publication_status
|
* @property string $publication_status;
|
||||||
* @property Time|null $published_at
|
* @property Time|null $published_at;
|
||||||
* @property Time $created_at
|
* @property Time $created_at;
|
||||||
* @property Time $updated_at
|
* @property Time $updated_at;
|
||||||
|
* @property Time|null $deleted_at;
|
||||||
*
|
*
|
||||||
* @property Person[] $persons
|
* @property Person[] $persons;
|
||||||
* @property Soundbite[] $soundbites
|
* @property Soundbite[] $soundbites;
|
||||||
* @property string $embed_url
|
* @property string $embeddable_player_url;
|
||||||
*/
|
*/
|
||||||
class Episode extends Entity
|
class Episode extends Entity
|
||||||
{
|
{
|
||||||
public string $link = '';
|
|
||||||
|
|
||||||
public string $audio_url = '';
|
|
||||||
|
|
||||||
public string $audio_web_url = '';
|
|
||||||
|
|
||||||
public string $audio_opengraph_url = '';
|
|
||||||
|
|
||||||
protected Podcast $podcast;
|
protected Podcast $podcast;
|
||||||
|
|
||||||
protected ?Audio $audio = null;
|
protected string $link;
|
||||||
|
|
||||||
protected string $embed_url = '';
|
protected File $audio_file;
|
||||||
|
|
||||||
protected ?Image $cover = null;
|
protected string $audio_file_url;
|
||||||
|
|
||||||
|
protected string $audio_file_analytics_url;
|
||||||
|
|
||||||
|
protected string $audio_file_web_url;
|
||||||
|
|
||||||
|
protected string $audio_file_opengraph_url;
|
||||||
|
|
||||||
|
protected string $embeddable_player_url;
|
||||||
|
|
||||||
|
protected Image $image;
|
||||||
|
|
||||||
protected ?string $description = null;
|
protected ?string $description = null;
|
||||||
|
|
||||||
protected ?Transcript $transcript = null;
|
protected File $transcript_file;
|
||||||
|
|
||||||
protected ?Chapters $chapters = null;
|
protected File $chapters_file;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Person[]|null
|
* @var Person[]|null
|
||||||
|
|
@ -122,272 +117,237 @@ class Episode extends Entity
|
||||||
protected ?array $soundbites = null;
|
protected ?array $soundbites = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Post[]|null
|
* @var Status[]|null
|
||||||
*/
|
*/
|
||||||
protected ?array $posts = null;
|
protected ?array $statuses = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var EpisodeComment[]|null
|
* @var Status[]|null
|
||||||
*/
|
*/
|
||||||
protected ?array $comments = null;
|
protected ?array $comments = null;
|
||||||
|
|
||||||
protected ?Location $location = null;
|
protected ?Location $location = null;
|
||||||
|
|
||||||
|
protected string $custom_rss_string;
|
||||||
|
|
||||||
protected ?string $publication_status = null;
|
protected ?string $publication_status = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array<int, string>
|
* @var string[]
|
||||||
* @phpstan-var list<string>
|
|
||||||
*/
|
*/
|
||||||
protected $dates = ['published_at', 'created_at', 'updated_at'];
|
protected $dates = ['published_at', 'created_at', 'updated_at', 'deleted_at'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array<string, string>
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'id' => 'integer',
|
'id' => 'integer',
|
||||||
'podcast_id' => 'integer',
|
'podcast_id' => 'integer',
|
||||||
'preview_id' => '?string',
|
'guid' => 'string',
|
||||||
'guid' => 'string',
|
'slug' => 'string',
|
||||||
'slug' => 'string',
|
'title' => 'string',
|
||||||
'title' => 'string',
|
'audio_file_path' => 'string',
|
||||||
'audio_id' => 'integer',
|
'audio_file_duration' => 'double',
|
||||||
'description_markdown' => 'string',
|
'audio_file_mimetype' => 'string',
|
||||||
'description_html' => 'string',
|
'audio_file_size' => 'integer',
|
||||||
'cover_id' => '?integer',
|
'audio_file_header_size' => 'integer',
|
||||||
'transcript_id' => '?integer',
|
'description_markdown' => 'string',
|
||||||
'transcript_remote_url' => '?string',
|
'description_html' => 'string',
|
||||||
'chapters_id' => '?integer',
|
'image_path' => '?string',
|
||||||
'chapters_remote_url' => '?string',
|
'image_mimetype' => '?string',
|
||||||
'parental_advisory' => '?string',
|
'transcript_file_path' => '?string',
|
||||||
'number' => '?integer',
|
'transcript_file_remote_url' => '?string',
|
||||||
'season_number' => '?integer',
|
'chapters_file_path' => '?string',
|
||||||
'type' => 'string',
|
'chapters_file_remote_url' => '?string',
|
||||||
'is_blocked' => 'boolean',
|
'parental_advisory' => '?string',
|
||||||
'location_name' => '?string',
|
'number' => '?integer',
|
||||||
'location_geo' => '?string',
|
'season_number' => '?integer',
|
||||||
'location_osm' => '?string',
|
'type' => 'string',
|
||||||
'is_published_on_hubs' => 'boolean',
|
'is_blocked' => 'boolean',
|
||||||
'downloads_count' => 'integer',
|
'location_name' => '?string',
|
||||||
'posts_count' => 'integer',
|
'location_geo' => '?string',
|
||||||
'comments_count' => 'integer',
|
'location_osm' => '?string',
|
||||||
'is_premium' => 'boolean',
|
'custom_rss' => '?json-array',
|
||||||
'created_by' => 'integer',
|
'favourites_total' => 'integer',
|
||||||
'updated_by' => 'integer',
|
'reblogs_total' => 'integer',
|
||||||
|
'statuses_total' => 'integer',
|
||||||
|
'created_by' => 'integer',
|
||||||
|
'updated_by' => 'integer',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, mixed> $data
|
* Saves an episode image
|
||||||
*/
|
*/
|
||||||
#[Override]
|
public function setImage(?Image $image = null): static
|
||||||
public function injectRawData(array $data): static
|
|
||||||
{
|
{
|
||||||
parent::injectRawData($data);
|
if ($image === null) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
$this->link = url_to('episode', esc($this->getPodcast()->handle, 'url'), esc($this->attributes['slug'], 'url'));
|
// Save image
|
||||||
|
$image->saveImage('podcasts/' . $this->getPodcast()->name, $this->attributes['slug']);
|
||||||
|
|
||||||
$this->audio_url = url_to(
|
$this->attributes['image_mimetype'] = $image->mimetype;
|
||||||
'episode-audio',
|
$this->attributes['image_path'] = $image->path;
|
||||||
$this->getPodcast()
|
|
||||||
->handle,
|
return $this;
|
||||||
$this->slug,
|
}
|
||||||
$this->getAudio()
|
|
||||||
->file_extension,
|
public function getImage(): Image
|
||||||
|
{
|
||||||
|
if ($imagePath = $this->attributes['image_path']) {
|
||||||
|
return new Image(null, $imagePath, $this->attributes['image_mimetype']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->getPodcast()
|
||||||
|
->image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves an audio file
|
||||||
|
*/
|
||||||
|
public function setAudioFile(UploadedFile | File $audioFile): static
|
||||||
|
{
|
||||||
|
helper(['media', 'id3']);
|
||||||
|
|
||||||
|
$audioMetadata = get_file_tags($audioFile);
|
||||||
|
|
||||||
|
$this->attributes['audio_file_path'] = save_media(
|
||||||
|
$audioFile,
|
||||||
|
'podcasts/' . $this->getPodcast()->name,
|
||||||
|
$this->attributes['slug'],
|
||||||
|
);
|
||||||
|
$this->attributes['audio_file_duration'] =
|
||||||
|
$audioMetadata['playtime_seconds'];
|
||||||
|
$this->attributes['audio_file_mimetype'] = $audioMetadata['mime_type'];
|
||||||
|
$this->attributes['audio_file_size'] = $audioMetadata['filesize'];
|
||||||
|
$this->attributes['audio_file_header_size'] =
|
||||||
|
$audioMetadata['avdataoffset'];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves an episode transcript file
|
||||||
|
*/
|
||||||
|
public function setTranscriptFile(UploadedFile | File $transcriptFile): static
|
||||||
|
{
|
||||||
|
helper('media');
|
||||||
|
|
||||||
|
$this->attributes['transcript_file_path'] = save_media(
|
||||||
|
$transcriptFile,
|
||||||
|
'podcasts/' . $this->getPodcast()
|
||||||
|
->name,
|
||||||
|
$this->attributes['slug'] . '-transcript',
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->audio_opengraph_url = $this->audio_url . '?_from=-+Open+Graph+-';
|
|
||||||
$this->audio_web_url = $this->audio_url . '?_from=-+Website+-';
|
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setCover(UploadedFile | File|null $file = null): self
|
|
||||||
{
|
|
||||||
if (! $file instanceof File || ($file instanceof UploadedFile && ! $file->isValid())) {
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (array_key_exists('cover_id', $this->attributes) && $this->attributes['cover_id'] !== null) {
|
|
||||||
$this->getCover()
|
|
||||||
->setFile($file);
|
|
||||||
$this->getCover()
|
|
||||||
->updated_by = $this->attributes['updated_by'];
|
|
||||||
new MediaModel('image')
|
|
||||||
->updateMedia($this->getCover());
|
|
||||||
} else {
|
|
||||||
$cover = new Image([
|
|
||||||
'file_key' => 'podcasts/' . $this->getPodcast()->handle . '/' . $this->attributes['slug'] . '.' . $file->getExtension(),
|
|
||||||
'sizes' => config('Images')
|
|
||||||
->podcastCoverSizes,
|
|
||||||
'uploaded_by' => $this->attributes['updated_by'],
|
|
||||||
'updated_by' => $this->attributes['updated_by'],
|
|
||||||
]);
|
|
||||||
$cover->setFile($file);
|
|
||||||
|
|
||||||
$this->attributes['cover_id'] = new MediaModel('image')->saveMedia($cover);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCover(): Image
|
|
||||||
{
|
|
||||||
if ($this->cover instanceof Image) {
|
|
||||||
return $this->cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->cover_id === null) {
|
|
||||||
$this->cover = $this->getPodcast()
|
|
||||||
->getCover();
|
|
||||||
|
|
||||||
return $this->cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->cover = new MediaModel('image')
|
|
||||||
->getMediaById($this->cover_id);
|
|
||||||
|
|
||||||
return $this->cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setAudio(UploadedFile | File|null $file = null): self
|
|
||||||
{
|
|
||||||
if (! $file instanceof File || ($file instanceof UploadedFile && ! $file->isValid())) {
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->audio_id !== 0) {
|
|
||||||
$this->getAudio()
|
|
||||||
->setFile($file);
|
|
||||||
$this->getAudio()
|
|
||||||
->updated_by = $this->attributes['updated_by'];
|
|
||||||
new MediaModel('audio')
|
|
||||||
->updateMedia($this->getAudio());
|
|
||||||
} else {
|
|
||||||
$audio = new Audio([
|
|
||||||
'file_key' => 'podcasts/' . $this->getPodcast()->handle . '/' . $file->getRandomName(),
|
|
||||||
'language_code' => $this->getPodcast()
|
|
||||||
->language_code,
|
|
||||||
'uploaded_by' => $this->attributes['updated_by'],
|
|
||||||
'updated_by' => $this->attributes['updated_by'],
|
|
||||||
]);
|
|
||||||
$audio->setFile($file);
|
|
||||||
|
|
||||||
$this->attributes['audio_id'] = new MediaModel()->saveMedia($audio);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAudio(): Audio
|
|
||||||
{
|
|
||||||
if (! $this->audio instanceof Audio) {
|
|
||||||
$this->audio = new MediaModel('audio')
|
|
||||||
->getMediaById($this->audio_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->audio;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setTranscript(UploadedFile | File|null $file = null): self
|
|
||||||
{
|
|
||||||
if (! $file instanceof File || ($file instanceof UploadedFile && ! $file->isValid())) {
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->getTranscript() instanceof Transcript) {
|
|
||||||
$this->getTranscript()
|
|
||||||
->setFile($file);
|
|
||||||
$this->getTranscript()
|
|
||||||
->updated_by = $this->attributes['updated_by'];
|
|
||||||
new MediaModel('transcript')
|
|
||||||
->updateMedia($this->getTranscript());
|
|
||||||
} else {
|
|
||||||
$transcript = new Transcript([
|
|
||||||
'file_key' => 'podcasts/' . $this->getPodcast()->handle . '/' . $this->attributes['slug'] . '-transcript.' . $file->getExtension(),
|
|
||||||
'language_code' => $this->getPodcast()
|
|
||||||
->language_code,
|
|
||||||
'uploaded_by' => $this->attributes['updated_by'],
|
|
||||||
'updated_by' => $this->attributes['updated_by'],
|
|
||||||
]);
|
|
||||||
$transcript->setFile($file);
|
|
||||||
|
|
||||||
$this->attributes['transcript_id'] = new MediaModel('transcript')->saveMedia($transcript);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTranscript(): ?Transcript
|
|
||||||
{
|
|
||||||
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|null $file = null): self
|
|
||||||
{
|
|
||||||
if (! $file instanceof File || ($file instanceof UploadedFile && ! $file->isValid())) {
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->getChapters() instanceof Chapters) {
|
|
||||||
$this->getChapters()
|
|
||||||
->setFile($file);
|
|
||||||
$this->getChapters()
|
|
||||||
->updated_by = $this->attributes['updated_by'];
|
|
||||||
new MediaModel('chapters')
|
|
||||||
->updateMedia($this->getChapters());
|
|
||||||
} else {
|
|
||||||
$chapters = new Chapters([
|
|
||||||
'file_key' => 'podcasts/' . $this->getPodcast()->handle . '/' . $this->attributes['slug'] . '-chapters' . '.' . $file->getExtension(),
|
|
||||||
'language_code' => $this->getPodcast()
|
|
||||||
->language_code,
|
|
||||||
'uploaded_by' => $this->attributes['updated_by'],
|
|
||||||
'updated_by' => $this->attributes['updated_by'],
|
|
||||||
]);
|
|
||||||
$chapters->setFile($file);
|
|
||||||
|
|
||||||
$this->attributes['chapters_id'] = new MediaModel('chapters')->saveMedia($chapters);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getChapters(): ?Chapters
|
|
||||||
{
|
|
||||||
if ($this->chapters_id !== null && ! $this->chapters instanceof Chapters) {
|
|
||||||
$this->chapters = new MediaModel('chapters')
|
|
||||||
->getMediaById($this->chapters_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->chapters;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets transcript url from transcript file uri if it exists or returns the transcript_remote_url which can be null.
|
* Saves an episode chapters file
|
||||||
*/
|
*/
|
||||||
public function getTranscriptUrl(): ?string
|
public function setChaptersFile(UploadedFile | File $chaptersFile): static
|
||||||
{
|
{
|
||||||
if ($this->transcript instanceof Transcript) {
|
helper('media');
|
||||||
return $this->transcript->file_url;
|
|
||||||
|
$this->attributes['chapters_file_path'] = save_media(
|
||||||
|
$chaptersFile,
|
||||||
|
'podcasts/' . $this->getPodcast()
|
||||||
|
->name,
|
||||||
|
$this->attributes['slug'] . '-chapters',
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAudioFile(): File
|
||||||
|
{
|
||||||
|
helper('media');
|
||||||
|
|
||||||
|
return new File(media_path($this->audio_file_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTranscriptFile(): ?File
|
||||||
|
{
|
||||||
|
if ($this->attributes['transcript_file_path']) {
|
||||||
|
helper('media');
|
||||||
|
|
||||||
|
return new File(media_path($this->attributes['transcript_file_path']));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->transcript_remote_url;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getChaptersFile(): ?File
|
||||||
|
{
|
||||||
|
if ($this->attributes['chapters_file_path']) {
|
||||||
|
helper('media');
|
||||||
|
|
||||||
|
return new File(media_path($this->attributes['chapters_file_path']));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAudioFileUrl(): string
|
||||||
|
{
|
||||||
|
helper('media');
|
||||||
|
|
||||||
|
return media_base_url($this->audio_file_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAudioFileAnalyticsUrl(): string
|
||||||
|
{
|
||||||
|
helper('analytics');
|
||||||
|
|
||||||
|
// remove 'podcasts/' from audio file path
|
||||||
|
$strippedAudioFilePath = substr($this->audio_file_path, 9);
|
||||||
|
|
||||||
|
return generate_episode_analytics_url(
|
||||||
|
$this->podcast_id,
|
||||||
|
$this->id,
|
||||||
|
$strippedAudioFilePath,
|
||||||
|
$this->audio_file_duration,
|
||||||
|
$this->audio_file_size,
|
||||||
|
$this->audio_file_header_size,
|
||||||
|
$this->published_at,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAudioFileWebUrl(): string
|
||||||
|
{
|
||||||
|
return $this->getAudioFileAnalyticsUrl() . '?_from=-+Website+-';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAudioFileOpengraphUrl(): string
|
||||||
|
{
|
||||||
|
return $this->getAudioFileAnalyticsUrl() . '?_from=-+Open+Graph+-';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets chapters file url from chapters file uri if it exists or returns the chapters_remote_url which can be null.
|
* Gets transcript url from transcript file uri if it exists or returns the transcript_file_remote_url which can be
|
||||||
|
* null.
|
||||||
|
*/
|
||||||
|
public function getTranscriptFileUrl(): ?string
|
||||||
|
{
|
||||||
|
if ($this->attributes['transcript_file_path']) {
|
||||||
|
return media_base_url($this->attributes['transcript_file_path']);
|
||||||
|
}
|
||||||
|
return $this->attributes['transcript_file_remote_url'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets chapters file url from chapters file uri if it exists or returns the chapters_file_remote_url which can be
|
||||||
|
* null.
|
||||||
*/
|
*/
|
||||||
public function getChaptersFileUrl(): ?string
|
public function getChaptersFileUrl(): ?string
|
||||||
{
|
{
|
||||||
if ($this->chapters instanceof Chapters) {
|
if ($this->chapters_file_path) {
|
||||||
return $this->chapters->file_url;
|
return media_base_url($this->chapters_file_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->chapters_remote_url;
|
return $this->chapters_file_remote_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -397,101 +357,145 @@ class Episode extends Entity
|
||||||
*/
|
*/
|
||||||
public function getPersons(): array
|
public function getPersons(): array
|
||||||
{
|
{
|
||||||
|
if ($this->id === null) {
|
||||||
|
throw new RuntimeException('Episode must be created before getting persons.');
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->persons === null) {
|
if ($this->persons === null) {
|
||||||
$this->persons = new PersonModel()
|
$this->persons = (new PersonModel())->getEpisodePersons($this->podcast_id, $this->id);
|
||||||
->getEpisodePersons($this->podcast_id, $this->id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->persons;
|
return $this->persons;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the episode’s clips
|
* Returns the episode’s soundbites
|
||||||
*
|
*
|
||||||
* @return Soundbite[]
|
* @return Soundbite[]
|
||||||
*/
|
*/
|
||||||
public function getSoundbites(): array
|
public function getSoundbites(): array
|
||||||
{
|
{
|
||||||
|
if ($this->id === null) {
|
||||||
|
throw new RuntimeException('Episode must be created before getting soundbites.');
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->soundbites === null) {
|
if ($this->soundbites === null) {
|
||||||
$this->soundbites = new ClipModel()
|
$this->soundbites = (new SoundbiteModel())->getEpisodeSoundbites($this->getPodcast() ->id, $this->id);
|
||||||
->getEpisodeSoundbites($this->getPodcast()->id, $this->id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->soundbites;
|
return $this->soundbites;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Post[]
|
* @return Status[]
|
||||||
*/
|
*/
|
||||||
public function getPosts(): array
|
public function getStatuses(): array
|
||||||
{
|
{
|
||||||
if ($this->posts === null) {
|
if ($this->id === null) {
|
||||||
$this->posts = new PostModel()
|
throw new RuntimeException('Episode must be created before getting statuses.');
|
||||||
->getEpisodePosts($this->id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->posts;
|
if ($this->statuses === null) {
|
||||||
|
$this->statuses = (new StatusModel())->getEpisodeStatuses($this->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->statuses;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return EpisodeComment[]
|
* @return Status[]
|
||||||
*/
|
*/
|
||||||
public function getComments(): array
|
public function getComments(): array
|
||||||
{
|
{
|
||||||
|
if ($this->id === null) {
|
||||||
|
throw new RuntimeException('Episode must be created before getting comments.');
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->comments === null) {
|
if ($this->comments === null) {
|
||||||
$this->comments = new EpisodeCommentModel()
|
$this->comments = (new StatusModel())->getEpisodeComments($this->id);
|
||||||
->getEpisodeComments($this->id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->comments;
|
return $this->comments;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEmbedUrl(?string $theme = null): string
|
public function getLink(): string
|
||||||
{
|
{
|
||||||
return $theme
|
return url_to('episode', $this->getPodcast()->name, $this->attributes['slug']);
|
||||||
? 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 getEmbeddablePlayerUrl(string $theme = null): string
|
||||||
|
{
|
||||||
|
return base_url(
|
||||||
|
$theme
|
||||||
|
? route_to(
|
||||||
|
'embeddable-player-theme',
|
||||||
|
$this->getPodcast()
|
||||||
|
->name,
|
||||||
|
$this->attributes['slug'],
|
||||||
|
$theme,
|
||||||
|
)
|
||||||
|
: route_to('embeddable-player', $this->getPodcast() ->name, $this->attributes['slug']),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setGuid(?string $guid = null): static
|
public function setGuid(?string $guid = null): static
|
||||||
{
|
{
|
||||||
$this->attributes['guid'] = $guid ?? $this->link;
|
$this->attributes['guid'] = $guid === null ? $this->getLink() : $guid;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPodcast(): ?Podcast
|
public function getPodcast(): ?Podcast
|
||||||
{
|
{
|
||||||
return new PodcastModel()
|
return (new PodcastModel())->getPodcastById($this->podcast_id);
|
||||||
->getPodcastById($this->podcast_id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setDescriptionMarkdown(string $descriptionMarkdown): static
|
public function setDescriptionMarkdown(string $descriptionMarkdown): static
|
||||||
{
|
{
|
||||||
$config = [
|
$converter = new CommonMarkConverter([
|
||||||
'html_input' => 'escape',
|
'html_input' => 'strip',
|
||||||
'allow_unsafe_links' => false,
|
'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['description_markdown'] = $descriptionMarkdown;
|
$this->attributes['description_markdown'] = $descriptionMarkdown;
|
||||||
$this->attributes['description_html'] = $converter->convert($descriptionMarkdown);
|
$this->attributes['description_html'] = $converter->convertToHtml($descriptionMarkdown);
|
||||||
|
|
||||||
return $this;
|
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
|
public function getDescription(): string
|
||||||
{
|
{
|
||||||
if ($this->description === null) {
|
if ($this->description === null) {
|
||||||
$this->description = trim(
|
$this->description = trim(
|
||||||
(string) preg_replace('~\s+~', ' ', strip_tags((string) $this->attributes['description_html'])),
|
preg_replace('~\s+~', ' ', strip_tags($this->attributes['description_html'])),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -501,10 +505,8 @@ class Episode extends Entity
|
||||||
public function getPublicationStatus(): string
|
public function getPublicationStatus(): string
|
||||||
{
|
{
|
||||||
if ($this->publication_status === null) {
|
if ($this->publication_status === null) {
|
||||||
if (! $this->published_at instanceof Time) {
|
if ($this->published_at === null) {
|
||||||
$this->publication_status = 'not_published';
|
$this->publication_status = 'not_published';
|
||||||
} elseif ($this->getPodcast()->publication_status !== 'published') {
|
|
||||||
$this->publication_status = 'with_podcast';
|
|
||||||
} elseif ($this->published_at->isBefore(Time::now())) {
|
} elseif ($this->published_at->isBefore(Time::now())) {
|
||||||
$this->publication_status = 'published';
|
$this->publication_status = 'published';
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -520,7 +522,7 @@ class Episode extends Entity
|
||||||
*/
|
*/
|
||||||
public function setLocation(?Location $location = null): static
|
public function setLocation(?Location $location = null): static
|
||||||
{
|
{
|
||||||
if (! $location instanceof Location) {
|
if ($location === null) {
|
||||||
$this->attributes['location_name'] = null;
|
$this->attributes['location_name'] = null;
|
||||||
$this->attributes['location_geo'] = null;
|
$this->attributes['location_geo'] = null;
|
||||||
$this->attributes['location_osm'] = null;
|
$this->attributes['location_osm'] = null;
|
||||||
|
|
@ -548,33 +550,88 @@ class Episode extends Entity
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $this->location instanceof Location) {
|
if ($this->location === null) {
|
||||||
$this->location = new Location($this->location_name, $this->location_geo, $this->location_osm);
|
$this->location = new Location($this->location_name, $this->location_geo, $this->location_osm);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->location;
|
return $this->location;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPreviewLink(): string
|
/**
|
||||||
|
* Get custom rss tag as XML String
|
||||||
|
*/
|
||||||
|
public function getCustomRssString(): string
|
||||||
{
|
{
|
||||||
if ($this->preview_id === null) {
|
if ($this->custom_rss === null) {
|
||||||
// generate preview id
|
return '';
|
||||||
if (! $previewUUID = new EpisodeModel()->setEpisodePreviewId($this->id)) {
|
|
||||||
throw new Exception('Could not set episode preview id');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->preview_id = $previewUUID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return url_to('episode-preview', (string) $this->preview_id);
|
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 str_replace(['<item>', '</item>'], '', $xmlNode->asXML());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the episode's clip count
|
* Saves custom rss tag into json
|
||||||
*/
|
*/
|
||||||
public function getClipCount(): int|string
|
public function setCustomRssString(?string $customRssString = null): static
|
||||||
{
|
{
|
||||||
return new ClipModel()
|
if ($customRssString === null) {
|
||||||
->getClipCount($this->podcast_id, $this->id);
|
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 : '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,142 +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\Entities;
|
|
||||||
|
|
||||||
use App\Models\ActorModel;
|
|
||||||
use App\Models\EpisodeCommentModel;
|
|
||||||
use App\Models\EpisodeModel;
|
|
||||||
use CodeIgniter\I18n\Time;
|
|
||||||
use Michalsn\Uuid\UuidEntity;
|
|
||||||
use RuntimeException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @property string $id
|
|
||||||
* @property string $uri
|
|
||||||
* @property int $episode_id
|
|
||||||
* @property Episode|null $episode
|
|
||||||
* @property int $actor_id
|
|
||||||
* @property Actor|null $actor
|
|
||||||
* @property ?string $in_reply_to_id
|
|
||||||
* @property EpisodeComment|null $reply_to_comment
|
|
||||||
* @property string $message
|
|
||||||
* @property string $message_html
|
|
||||||
* @property int $likes_count
|
|
||||||
* @property int $replies_count
|
|
||||||
* @property Time $created_at
|
|
||||||
* @property int $created_by
|
|
||||||
*
|
|
||||||
* @property EpisodeComment[] $replies
|
|
||||||
*/
|
|
||||||
class EpisodeComment extends UuidEntity
|
|
||||||
{
|
|
||||||
protected ?Episode $episode = null;
|
|
||||||
|
|
||||||
protected ?Actor $actor = null;
|
|
||||||
|
|
||||||
protected ?EpisodeComment $reply_to_comment = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var EpisodeComment[]|null
|
|
||||||
*/
|
|
||||||
protected ?array $replies = null;
|
|
||||||
|
|
||||||
protected bool $has_replies = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array<int, string>
|
|
||||||
* @phpstan-var list<string>
|
|
||||||
*/
|
|
||||||
protected $dates = ['created_at'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array<string, string>
|
|
||||||
*/
|
|
||||||
protected $casts = [
|
|
||||||
'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',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function getEpisode(): ?Episode
|
|
||||||
{
|
|
||||||
if (! $this->episode instanceof Episode) {
|
|
||||||
$this->episode = new EpisodeModel()
|
|
||||||
->getEpisodeById($this->episode_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->episode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the comment's actor
|
|
||||||
*/
|
|
||||||
public function getActor(): ?Actor
|
|
||||||
{
|
|
||||||
if (! $this->actor instanceof Actor) {
|
|
||||||
$this->actor = model(ActorModel::class, false)
|
|
||||||
->getActorById($this->actor_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->actor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return EpisodeComment[]
|
|
||||||
*/
|
|
||||||
public function getReplies(): array
|
|
||||||
{
|
|
||||||
if ($this->replies === null) {
|
|
||||||
$this->replies = new EpisodeCommentModel()
|
|
||||||
->getCommentReplies($this->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->replies;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getHasReplies(): bool
|
|
||||||
{
|
|
||||||
return $this->getReplies() !== [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getReplyToComment(): ?self
|
|
||||||
{
|
|
||||||
if ($this->in_reply_to_id === null) {
|
|
||||||
throw new RuntimeException('Comment is not a reply.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $this->reply_to_comment instanceof self) {
|
|
||||||
$this->reply_to_comment = model(EpisodeCommentModel::class, false)
|
|
||||||
->getCommentById($this->in_reply_to_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->reply_to_comment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setMessage(string $message): static
|
|
||||||
{
|
|
||||||
helper('fediverse');
|
|
||||||
|
|
||||||
$messageWithoutTags = strip_tags($message);
|
|
||||||
|
|
||||||
$this->attributes['message'] = $messageWithoutTags;
|
|
||||||
$this->attributes['message_html'] = str_replace("\n", '<br />', linkify($messageWithoutTags));
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
232
app/Entities/Image.php
Normal file
232
app/Entities/Image.php
Normal file
|
|
@ -0,0 +1,232 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2021 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Entities;
|
||||||
|
|
||||||
|
use CodeIgniter\Entity\Entity;
|
||||||
|
use CodeIgniter\Files\File;
|
||||||
|
use Config\Images;
|
||||||
|
use Config\Services;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property File|null $file
|
||||||
|
* @property string $dirname
|
||||||
|
* @property string $filename
|
||||||
|
* @property string $extension
|
||||||
|
* @property string $mimetype
|
||||||
|
* @property string $path
|
||||||
|
* @property string $url
|
||||||
|
* @property string $thumbnail_path
|
||||||
|
* @property string $thumbnail_url
|
||||||
|
* @property string $medium_path
|
||||||
|
* @property string $medium_url
|
||||||
|
* @property string $large_path
|
||||||
|
* @property string $large_url
|
||||||
|
* @property string $feed_path
|
||||||
|
* @property string $feed_url
|
||||||
|
* @property string $id3_path
|
||||||
|
* @property string $id3_url
|
||||||
|
*/
|
||||||
|
class Image extends Entity
|
||||||
|
{
|
||||||
|
protected Images $config;
|
||||||
|
|
||||||
|
protected ?File $file = null;
|
||||||
|
|
||||||
|
protected string $dirname;
|
||||||
|
|
||||||
|
protected string $filename;
|
||||||
|
|
||||||
|
protected string $extension;
|
||||||
|
|
||||||
|
public function __construct(?File $file, string $path = '', string $mimetype = '')
|
||||||
|
{
|
||||||
|
if ($file === null && $path === '') {
|
||||||
|
throw new RuntimeException('File or path must be set to create an Image.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->config = config('Images');
|
||||||
|
|
||||||
|
$dirname = '';
|
||||||
|
$filename = '';
|
||||||
|
$extension = '';
|
||||||
|
|
||||||
|
if ($file !== null) {
|
||||||
|
$dirname = $file->getPath();
|
||||||
|
$filename = $file->getBasename();
|
||||||
|
$extension = $file->getExtension();
|
||||||
|
$mimetype = $file->getMimeType();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($path !== '') {
|
||||||
|
[
|
||||||
|
'filename' => $filename,
|
||||||
|
'dirname' => $dirname,
|
||||||
|
'extension' => $extension,
|
||||||
|
] = pathinfo($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->file = $file;
|
||||||
|
$this->dirname = $dirname;
|
||||||
|
$this->filename = $filename;
|
||||||
|
$this->extension = $extension;
|
||||||
|
$this->mimetype = $mimetype;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFile(): File
|
||||||
|
{
|
||||||
|
if ($this->file === null) {
|
||||||
|
$this->file = new File($this->path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPath(): string
|
||||||
|
{
|
||||||
|
return $this->dirname . '/' . $this->filename . '.' . $this->extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUrl(): string
|
||||||
|
{
|
||||||
|
helper('media');
|
||||||
|
|
||||||
|
return media_base_url($this->path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getThumbnailPath(): string
|
||||||
|
{
|
||||||
|
return $this->dirname .
|
||||||
|
'/' .
|
||||||
|
$this->filename .
|
||||||
|
$this->config->thumbnailSuffix .
|
||||||
|
'.' .
|
||||||
|
$this->extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getThumbnailUrl(): string
|
||||||
|
{
|
||||||
|
helper('media');
|
||||||
|
|
||||||
|
return media_base_url($this->thumbnail_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMediumPath(): string
|
||||||
|
{
|
||||||
|
return $this->dirname .
|
||||||
|
'/' .
|
||||||
|
$this->filename .
|
||||||
|
$this->config->mediumSuffix .
|
||||||
|
'.' .
|
||||||
|
$this->extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMediumUrl(): string
|
||||||
|
{
|
||||||
|
helper('media');
|
||||||
|
|
||||||
|
return media_base_url($this->medium_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLargePath(): string
|
||||||
|
{
|
||||||
|
return $this->dirname .
|
||||||
|
'/' .
|
||||||
|
$this->filename .
|
||||||
|
$this->config->largeSuffix .
|
||||||
|
'.' .
|
||||||
|
$this->extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLargeUrl(): string
|
||||||
|
{
|
||||||
|
helper('media');
|
||||||
|
|
||||||
|
return media_base_url($this->large_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFeedPath(): string
|
||||||
|
{
|
||||||
|
return $this->dirname .
|
||||||
|
'/' .
|
||||||
|
$this->filename .
|
||||||
|
$this->config->feedSuffix .
|
||||||
|
'.' .
|
||||||
|
$this->extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFeedUrl(): string
|
||||||
|
{
|
||||||
|
helper('media');
|
||||||
|
|
||||||
|
return media_base_url($this->feed_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId3Path(): string
|
||||||
|
{
|
||||||
|
return $this->dirname .
|
||||||
|
'/' .
|
||||||
|
$this->filename .
|
||||||
|
$this->config->id3Suffix .
|
||||||
|
'.' .
|
||||||
|
$this->extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId3Url(): string
|
||||||
|
{
|
||||||
|
helper('media');
|
||||||
|
|
||||||
|
return media_base_url($this->id3_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveImage(string $dirname, string $filename): void
|
||||||
|
{
|
||||||
|
helper('media');
|
||||||
|
|
||||||
|
$this->dirname = $dirname;
|
||||||
|
$this->filename = $filename;
|
||||||
|
|
||||||
|
save_media($this->file, $this->dirname, $this->filename);
|
||||||
|
|
||||||
|
$imageService = Services::image();
|
||||||
|
|
||||||
|
$thumbnailSize = $this->config->thumbnailSize;
|
||||||
|
$mediumSize = $this->config->mediumSize;
|
||||||
|
$largeSize = $this->config->largeSize;
|
||||||
|
$feedSize = $this->config->feedSize;
|
||||||
|
$id3Size = $this->config->id3Size;
|
||||||
|
|
||||||
|
$imageService
|
||||||
|
->withFile(media_path($this->path))
|
||||||
|
->resize($thumbnailSize, $thumbnailSize)
|
||||||
|
->save(media_path($this->thumbnail_path));
|
||||||
|
|
||||||
|
$imageService
|
||||||
|
->withFile(media_path($this->path))
|
||||||
|
->resize($mediumSize, $mediumSize)
|
||||||
|
->save(media_path($this->medium_path));
|
||||||
|
|
||||||
|
$imageService
|
||||||
|
->withFile(media_path($this->path))
|
||||||
|
->resize($largeSize, $largeSize)
|
||||||
|
->save(media_path($this->large_path));
|
||||||
|
|
||||||
|
$imageService
|
||||||
|
->withFile(media_path($this->path))
|
||||||
|
->resize($feedSize, $feedSize)
|
||||||
|
->save(media_path($this->feed_path));
|
||||||
|
|
||||||
|
$imageService
|
||||||
|
->withFile(media_path($this->path))
|
||||||
|
->resize($id3Size, $id3Size)
|
||||||
|
->save(media_path($this->id3_path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @copyright 2020 Ad Aures
|
* @copyright 2020 Podlibre
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
@ -22,7 +22,7 @@ class Language extends Entity
|
||||||
* @var array<string, string>
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'code' => 'string',
|
'code' => 'string',
|
||||||
'native_name' => 'string',
|
'native_name' => 'string',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,33 +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;
|
|
||||||
|
|
||||||
use Michalsn\Uuid\UuidEntity;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @property int $actor_id
|
|
||||||
* @property string $comment_id
|
|
||||||
*/
|
|
||||||
class Like extends UuidEntity
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var string[]
|
|
||||||
*/
|
|
||||||
protected $uuids = ['comment_id'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array<string, string>
|
|
||||||
*/
|
|
||||||
protected $casts = [
|
|
||||||
'actor_id' => 'integer',
|
|
||||||
'comment_id' => 'string',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @copyright 2021 Ad Aures
|
* @copyright 2021 Podlibre
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
||||||
namespace App\Entities;
|
namespace App\Entities;
|
||||||
|
|
||||||
use CodeIgniter\Entity\Entity;
|
use CodeIgniter\Entity\Entity;
|
||||||
|
use Config\Services;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property string $url
|
* @property string $url
|
||||||
|
|
@ -22,9 +23,15 @@ use CodeIgniter\Entity\Entity;
|
||||||
*/
|
*/
|
||||||
class Location extends Entity
|
class Location extends Entity
|
||||||
{
|
{
|
||||||
private const string OSM_URL = 'https://www.openstreetmap.org/';
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private const OSM_URL = 'https://www.openstreetmap.org/';
|
||||||
|
|
||||||
private const string NOMINATIM_URL = 'https://nominatim.openstreetmap.org/';
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private const NOMINATIM_URL = 'https://nominatim.openstreetmap.org/';
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected string $name,
|
protected string $name,
|
||||||
|
|
@ -35,18 +42,14 @@ class Location extends Entity
|
||||||
$longitude = null;
|
$longitude = null;
|
||||||
if ($geo !== null) {
|
if ($geo !== null) {
|
||||||
$geoArray = explode(',', substr($geo, 4));
|
$geoArray = explode(',', substr($geo, 4));
|
||||||
|
$latitude = floatval($geoArray[0]);
|
||||||
if (count($geoArray) === 2) {
|
$longitude = floatval($geoArray[1]);
|
||||||
$latitude = (float) $geoArray[0];
|
|
||||||
$longitude = (float) $geoArray[1];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parent::__construct([
|
parent::__construct([
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'geo' => $geo,
|
'geo' => $geo,
|
||||||
'osm' => $osm,
|
'osm' => $osm,
|
||||||
'latitude' => $latitude,
|
'latitude' => $latitude,
|
||||||
'longitude' => $longitude,
|
'longitude' => $longitude,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
@ -78,7 +81,7 @@ class Location extends Entity
|
||||||
*/
|
*/
|
||||||
public function fetchOsmLocation(): static
|
public function fetchOsmLocation(): static
|
||||||
{
|
{
|
||||||
$client = service('curlrequest');
|
$client = Services::curlrequest();
|
||||||
|
|
||||||
$response = $client->request(
|
$response = $client->request(
|
||||||
'GET',
|
'GET',
|
||||||
|
|
@ -89,12 +92,12 @@ class Location extends Entity
|
||||||
[
|
[
|
||||||
'headers' => [
|
'headers' => [
|
||||||
'User-Agent' => 'Castopod/' . CP_VERSION,
|
'User-Agent' => 'Castopod/' . CP_VERSION,
|
||||||
'Accept' => 'application/json',
|
'Accept' => 'application/json',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
$places = json_decode((string) $response->getBody(), false, 512, JSON_THROW_ON_ERROR);
|
$places = json_decode($response->getBody(), false, 512, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
if ($places === []) {
|
if ($places === []) {
|
||||||
return $this;
|
return $this;
|
||||||
|
|
@ -102,16 +105,16 @@ class Location extends Entity
|
||||||
|
|
||||||
if (property_exists($places[0], 'lat') && $places[0]->lat !== null && (property_exists(
|
if (property_exists($places[0], 'lat') && $places[0]->lat !== null && (property_exists(
|
||||||
$places[0],
|
$places[0],
|
||||||
'lon',
|
'lon'
|
||||||
) && $places[0]->lon !== null)) {
|
) && $places[0]->lon !== null)) {
|
||||||
$this->attributes['geo'] = "geo:{$places[0]->lat},{$places[0]->lon}";
|
$this->attributes['geo'] = "geo:{$places[0]->lat},{$places[0]->lon}";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (property_exists($places[0], 'osm_type') && $places[0]->osm_type !== null && (property_exists(
|
if (property_exists($places[0], 'osm_type') && $places[0]->osm_type !== null && (property_exists(
|
||||||
$places[0],
|
$places[0],
|
||||||
'osm_id',
|
'osm_id'
|
||||||
) && $places[0]->osm_id !== null)) {
|
) && $places[0]->osm_id !== null)) {
|
||||||
$this->attributes['osm'] = strtoupper(substr((string) $places[0]->osm_type, 0, 1)) . $places[0]->osm_id;
|
$this->attributes['osm'] = strtoupper(substr($places[0]->osm_type, 0, 1)) . $places[0]->osm_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @copyright 2020 Ad Aures
|
* @copyright 2020 Podlibre
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
@ -12,12 +12,7 @@ namespace App\Entities;
|
||||||
|
|
||||||
use CodeIgniter\Entity\Entity;
|
use CodeIgniter\Entity\Entity;
|
||||||
use CodeIgniter\I18n\Time;
|
use CodeIgniter\I18n\Time;
|
||||||
use League\CommonMark\Environment\Environment;
|
use League\CommonMark\CommonMarkConverter;
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $id
|
* @property int $id
|
||||||
|
|
@ -40,11 +35,11 @@ class Page extends Entity
|
||||||
* @var array<string, string>
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'id' => 'integer',
|
'id' => 'integer',
|
||||||
'title' => 'string',
|
'title' => 'string',
|
||||||
'slug' => 'string',
|
'slug' => 'string',
|
||||||
'content_markdown' => 'string',
|
'content_markdown' => 'string',
|
||||||
'content_html' => 'string',
|
'content_html' => 'string',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function getLink(): string
|
public function getLink(): string
|
||||||
|
|
@ -54,20 +49,13 @@ class Page extends Entity
|
||||||
|
|
||||||
public function setContentMarkdown(string $contentMarkdown): static
|
public function setContentMarkdown(string $contentMarkdown): static
|
||||||
{
|
{
|
||||||
$config = [
|
$converter = new CommonMarkConverter([
|
||||||
|
'html_input' => 'strip',
|
||||||
'allow_unsafe_links' => false,
|
'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['content_markdown'] = $contentMarkdown;
|
$this->attributes['content_markdown'] = $contentMarkdown;
|
||||||
$this->attributes['content_html'] = $converter->convert($contentMarkdown);
|
$this->attributes['content_html'] = $converter->convertToHtml($contentMarkdown);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @copyright 2021 Ad Aures
|
* @copyright 2021 Podlibre
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
@ -12,10 +12,6 @@ namespace App\Entities;
|
||||||
|
|
||||||
use App\Models\PersonModel;
|
use App\Models\PersonModel;
|
||||||
use CodeIgniter\Entity\Entity;
|
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;
|
use RuntimeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -23,15 +19,20 @@ use RuntimeException;
|
||||||
* @property string $full_name
|
* @property string $full_name
|
||||||
* @property string $unique_name
|
* @property string $unique_name
|
||||||
* @property string|null $information_url
|
* @property string|null $information_url
|
||||||
* @property ?int $avatar_id
|
* @property Image $image
|
||||||
* @property ?Image $avatar
|
* @property string $image_path
|
||||||
|
* @property string $image_mimetype
|
||||||
* @property int $created_by
|
* @property int $created_by
|
||||||
* @property int $updated_by
|
* @property int $updated_by
|
||||||
* @property object[]|null $roles
|
* @property object[]|null $roles
|
||||||
*/
|
*/
|
||||||
class Person extends Entity
|
class Person extends Entity
|
||||||
{
|
{
|
||||||
protected ?Image $avatar = null;
|
protected Image $image;
|
||||||
|
|
||||||
|
protected ?int $podcast_id = null;
|
||||||
|
|
||||||
|
protected ?int $episode_id = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var object[]|null
|
* @var object[]|null
|
||||||
|
|
@ -42,61 +43,37 @@ class Person extends Entity
|
||||||
* @var array<string, string>
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'id' => 'integer',
|
'id' => 'integer',
|
||||||
'full_name' => 'string',
|
'full_name' => 'string',
|
||||||
'unique_name' => 'string',
|
'unique_name' => 'string',
|
||||||
'information_url' => '?string',
|
'information_url' => '?string',
|
||||||
'avatar_id' => '?int',
|
'image_path' => 'string',
|
||||||
'podcast_id' => '?integer',
|
'image_mimetype' => 'string',
|
||||||
'episode_id' => '?integer',
|
'podcast_id' => '?integer',
|
||||||
'created_by' => 'integer',
|
'episode_id' => '?integer',
|
||||||
'updated_by' => 'integer',
|
'created_by' => 'integer',
|
||||||
|
'updated_by' => 'integer',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the person avatar in `public/media/persons/`
|
* Saves a picture in `public/media/persons/`
|
||||||
*/
|
*/
|
||||||
public function setAvatar(UploadedFile | File|null $file = null): static
|
public function setImage(Image $image): static
|
||||||
{
|
{
|
||||||
if (! $file instanceof File || ($file instanceof UploadedFile && ! $file->isValid())) {
|
helper('media');
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (array_key_exists('avatar_id', $this->attributes) && $this->attributes['avatar_id'] !== null) {
|
// Save image
|
||||||
$this->getAvatar()
|
$image->saveImage('persons', $this->attributes['unique_name']);
|
||||||
->setFile($file);
|
|
||||||
$this->getAvatar()
|
|
||||||
->updated_by = $this->attributes['updated_by'];
|
|
||||||
new MediaModel('image')
|
|
||||||
->updateMedia($this->getAvatar());
|
|
||||||
} else {
|
|
||||||
$avatar = new Image([
|
|
||||||
'file_key' => 'persons/' . $this->attributes['unique_name'] . '.' . $file->getExtension(),
|
|
||||||
'sizes' => config('Images')
|
|
||||||
->personAvatarSizes,
|
|
||||||
'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['image_mimetype'] = $image->mimetype;
|
||||||
}
|
$this->attributes['image_path'] = $image->path;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAvatar(): ?Image
|
public function getImage(): Image
|
||||||
{
|
{
|
||||||
if ($this->avatar_id === null) {
|
return new Image(null, $this->attributes['image_path'], $this->attributes['image_mimetype']);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $this->avatar instanceof Image) {
|
|
||||||
$this->avatar = new MediaModel('image')
|
|
||||||
->getMediaById($this->avatar_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->avatar;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -109,12 +86,11 @@ class Person extends Entity
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->roles === null) {
|
if ($this->roles === null) {
|
||||||
$this->roles = new PersonModel()
|
$this->roles = (new PersonModel())->getPersonRoles(
|
||||||
->getPersonRoles(
|
$this->id,
|
||||||
$this->id,
|
(int) $this->attributes['podcast_id'],
|
||||||
(int) $this->attributes['podcast_id'],
|
array_key_exists('episode_id', $this->attributes) ? (int) $this->attributes['episode_id'] : null
|
||||||
array_key_exists('episode_id', $this->attributes) ? (int) $this->attributes['episode_id'] : null,
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->roles;
|
return $this->roles;
|
||||||
|
|
|
||||||
42
app/Entities/Platform.php
Normal file
42
app/Entities/Platform.php
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright 2020 Podlibre
|
||||||
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
|
* @link https://castopod.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Entities;
|
||||||
|
|
||||||
|
use CodeIgniter\Entity\Entity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property string $slug
|
||||||
|
* @property string $type
|
||||||
|
* @property string $label
|
||||||
|
* @property string $home_url
|
||||||
|
* @property string|null $submit_url
|
||||||
|
* @property string|null $link_url
|
||||||
|
* @property string|null $link_content
|
||||||
|
* @property bool|null $is_visible
|
||||||
|
* @property bool|null $is_on_embeddable_player
|
||||||
|
*/
|
||||||
|
class Platform extends Entity
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array<string, string>
|
||||||
|
*/
|
||||||
|
protected $casts = [
|
||||||
|
'slug' => 'string',
|
||||||
|
'type' => 'string',
|
||||||
|
'label' => 'string',
|
||||||
|
'home_url' => 'string',
|
||||||
|
'submit_url' => '?string',
|
||||||
|
'link_url' => '?string',
|
||||||
|
'link_content' => '?string',
|
||||||
|
'is_visible' => '?boolean',
|
||||||
|
'is_on_embeddable_player' => '?boolean',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -3,36 +3,22 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @copyright 2020 Ad Aures
|
* @copyright 2020 Podlibre
|
||||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||||
* @link https://castopod.org/
|
* @link https://castopod.org/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace App\Entities;
|
namespace App\Entities;
|
||||||
|
|
||||||
use App\Models\ActorModel;
|
use App\Libraries\SimpleRSSElement;
|
||||||
use App\Models\CategoryModel;
|
use App\Models\CategoryModel;
|
||||||
use App\Models\EpisodeModel;
|
use App\Models\EpisodeModel;
|
||||||
use App\Models\PersonModel;
|
use App\Models\PersonModel;
|
||||||
|
use App\Models\PlatformModel;
|
||||||
|
use App\Models\UserModel;
|
||||||
use CodeIgniter\Entity\Entity;
|
use CodeIgniter\Entity\Entity;
|
||||||
use CodeIgniter\Files\File;
|
|
||||||
use CodeIgniter\HTTP\Files\UploadedFile;
|
|
||||||
use CodeIgniter\I18n\Time;
|
use CodeIgniter\I18n\Time;
|
||||||
use CodeIgniter\Shield\Entities\User;
|
use League\CommonMark\CommonMarkConverter;
|
||||||
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\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;
|
use RuntimeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -40,18 +26,16 @@ use RuntimeException;
|
||||||
* @property string $guid
|
* @property string $guid
|
||||||
* @property int $actor_id
|
* @property int $actor_id
|
||||||
* @property Actor|null $actor
|
* @property Actor|null $actor
|
||||||
* @property string $handle
|
* @property string $name
|
||||||
* @property string $at_handle
|
|
||||||
* @property string $link
|
* @property string $link
|
||||||
* @property string $feed_url
|
* @property string $feed_url
|
||||||
* @property string $title
|
* @property string $title
|
||||||
* @property string|null $description Holds text only description, striped of any markdown or html special characters
|
* @property string|null $description Holds text only description, striped of any markdown or html special characters
|
||||||
* @property string $description_markdown
|
* @property string $description_markdown
|
||||||
* @property string $description_html
|
* @property string $description_html
|
||||||
* @property int $cover_id
|
* @property Image $image
|
||||||
* @property ?Image $cover
|
* @property string $image_path
|
||||||
* @property int|null $banner_id
|
* @property string $image_mimetype
|
||||||
* @property ?Image $banner
|
|
||||||
* @property string $language_code
|
* @property string $language_code
|
||||||
* @property int $category_id
|
* @property int $category_id
|
||||||
* @property Category|null $category
|
* @property Category|null $category
|
||||||
|
|
@ -63,6 +47,8 @@ use RuntimeException;
|
||||||
* @property string $owner_email
|
* @property string $owner_email
|
||||||
* @property string $type
|
* @property string $type
|
||||||
* @property string|null $copyright
|
* @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_blocked
|
||||||
* @property bool $is_completed
|
* @property bool $is_completed
|
||||||
* @property bool $is_locked
|
* @property bool $is_locked
|
||||||
|
|
@ -72,20 +58,21 @@ use RuntimeException;
|
||||||
* @property string|null $location_name
|
* @property string|null $location_name
|
||||||
* @property string|null $location_geo
|
* @property string|null $location_geo
|
||||||
* @property string|null $location_osm
|
* @property string|null $location_osm
|
||||||
* @property bool $is_published_on_hubs
|
* @property string|null $payment_pointer
|
||||||
|
* @property array|null $custom_rss
|
||||||
|
* @property string $custom_rss_string
|
||||||
|
* @property string|null $partner_id
|
||||||
|
* @property string|null $partner_link_url
|
||||||
|
* @property string|null $partner_image_url
|
||||||
* @property int $created_by
|
* @property int $created_by
|
||||||
* @property int $updated_by
|
* @property int $updated_by
|
||||||
* @property string $publication_status
|
* @property Time $created_at;
|
||||||
* @property bool $is_premium_by_default
|
* @property Time $updated_at;
|
||||||
* @property bool $is_premium
|
* @property Time|null $deleted_at;
|
||||||
* @property Time|null $published_at
|
|
||||||
* @property Time $created_at
|
|
||||||
* @property Time $updated_at
|
|
||||||
*
|
*
|
||||||
* @property Episode[] $episodes
|
* @property Episode[] $episodes
|
||||||
* @property Person[] $persons
|
* @property Person[] $persons
|
||||||
* @property User[] $contributors
|
* @property User[] $contributors
|
||||||
* @property Subscription[] $subscriptions
|
|
||||||
* @property Platform[] $podcasting_platforms
|
* @property Platform[] $podcasting_platforms
|
||||||
* @property Platform[] $social_platforms
|
* @property Platform[] $social_platforms
|
||||||
* @property Platform[] $funding_platforms
|
* @property Platform[] $funding_platforms
|
||||||
|
|
@ -94,13 +81,9 @@ class Podcast extends Entity
|
||||||
{
|
{
|
||||||
protected string $link;
|
protected string $link;
|
||||||
|
|
||||||
protected string $at_handle;
|
|
||||||
|
|
||||||
protected ?Actor $actor = null;
|
protected ?Actor $actor = null;
|
||||||
|
|
||||||
protected ?Image $cover = null;
|
protected Image $image;
|
||||||
|
|
||||||
protected ?Image $banner = null;
|
|
||||||
|
|
||||||
protected ?string $description = null;
|
protected ?string $description = null;
|
||||||
|
|
||||||
|
|
@ -112,9 +95,9 @@ class Podcast extends Entity
|
||||||
protected ?array $other_categories = null;
|
protected ?array $other_categories = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var int[]
|
* @var string[]|null
|
||||||
*/
|
*/
|
||||||
protected array $other_categories_ids = [];
|
protected ?array $other_categories_ids = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Episode[]|null
|
* @var Episode[]|null
|
||||||
|
|
@ -131,11 +114,6 @@ class Podcast extends Entity
|
||||||
*/
|
*/
|
||||||
protected ?array $contributors = null;
|
protected ?array $contributors = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Subscription[]|null
|
|
||||||
*/
|
|
||||||
protected ?array $subscriptions = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Platform[]|null
|
* @var Platform[]|null
|
||||||
*/
|
*/
|
||||||
|
|
@ -153,164 +131,89 @@ class Podcast extends Entity
|
||||||
|
|
||||||
protected ?Location $location = null;
|
protected ?Location $location = null;
|
||||||
|
|
||||||
protected ?string $publication_status = null;
|
protected string $custom_rss_string;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array<int, string>
|
|
||||||
* @phpstan-var list<string>
|
|
||||||
*/
|
|
||||||
protected $dates = ['published_at', 'created_at', 'updated_at'];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array<string, string>
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'id' => 'integer',
|
'id' => 'integer',
|
||||||
'guid' => 'string',
|
'guid' => 'string',
|
||||||
'actor_id' => 'integer',
|
'actor_id' => 'integer',
|
||||||
'handle' => 'string',
|
'name' => 'string',
|
||||||
'title' => 'string',
|
'title' => 'string',
|
||||||
'description_markdown' => 'string',
|
'description_markdown' => 'string',
|
||||||
'description_html' => 'string',
|
'description_html' => 'string',
|
||||||
'cover_id' => 'int',
|
'image_path' => 'string',
|
||||||
'banner_id' => '?int',
|
'image_mimetype' => 'string',
|
||||||
'language_code' => 'string',
|
'language_code' => 'string',
|
||||||
'category_id' => 'integer',
|
'category_id' => 'integer',
|
||||||
'parental_advisory' => '?string',
|
'parental_advisory' => '?string',
|
||||||
'publisher' => '?string',
|
'publisher' => '?string',
|
||||||
'owner_name' => 'string',
|
'owner_name' => 'string',
|
||||||
'owner_email' => 'string',
|
'owner_email' => 'string',
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'copyright' => '?string',
|
'copyright' => '?string',
|
||||||
'is_blocked' => 'boolean',
|
'episode_description_footer_markdown' => '?string',
|
||||||
'is_completed' => 'boolean',
|
'episode_description_footer_html' => '?string',
|
||||||
'is_locked' => 'boolean',
|
'is_blocked' => 'boolean',
|
||||||
'is_premium_by_default' => 'boolean',
|
'is_completed' => 'boolean',
|
||||||
'imported_feed_url' => '?string',
|
'is_locked' => 'boolean',
|
||||||
'new_feed_url' => '?string',
|
'imported_feed_url' => '?string',
|
||||||
'location_name' => '?string',
|
'new_feed_url' => '?string',
|
||||||
'location_geo' => '?string',
|
'location_name' => '?string',
|
||||||
'location_osm' => '?string',
|
'location_geo' => '?string',
|
||||||
'is_published_on_hubs' => 'boolean',
|
'location_osm' => '?string',
|
||||||
'created_by' => 'integer',
|
'payment_pointer' => '?string',
|
||||||
'updated_by' => 'integer',
|
'custom_rss' => '?json-array',
|
||||||
|
'partner_id' => '?string',
|
||||||
|
'partner_link_url' => '?string',
|
||||||
|
'partner_image_url' => '?string',
|
||||||
|
'created_by' => 'integer',
|
||||||
|
'updated_by' => 'integer',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function getAtHandle(): string
|
public function getActor(): Actor
|
||||||
{
|
|
||||||
return '@' . $this->handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getActor(): ?Actor
|
|
||||||
{
|
{
|
||||||
if ($this->actor_id === 0) {
|
if ($this->actor_id === 0) {
|
||||||
throw new RuntimeException('Podcast must have an actor_id before getting actor.');
|
throw new RuntimeException('Podcast must have an actor_id before getting actor.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $this->actor instanceof Actor) {
|
if ($this->actor === null) {
|
||||||
$this->actor = model(ActorModel::class, false)
|
$this->actor = model('ActorModel')
|
||||||
->getActorById($this->actor_id);
|
->getActorById($this->actor_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->actor;
|
return $this->actor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setCover(UploadedFile | File|null $file = null): self
|
/**
|
||||||
|
* Saves a cover image to the corresponding podcast folder in `public/media/podcast_name/`
|
||||||
|
*/
|
||||||
|
public function setImage(Image $image): static
|
||||||
{
|
{
|
||||||
if (! $file instanceof File || ($file instanceof UploadedFile && ! $file->isValid())) {
|
// Save image
|
||||||
return $this;
|
$image->saveImage('podcasts/' . $this->attributes['name'], 'cover');
|
||||||
}
|
|
||||||
|
|
||||||
if (array_key_exists('cover_id', $this->attributes) && $this->attributes['cover_id'] !== null) {
|
$this->attributes['image_mimetype'] = $image->mimetype;
|
||||||
$this->getCover()
|
$this->attributes['image_path'] = $image->path;
|
||||||
->setFile($file);
|
|
||||||
$this->getCover()
|
|
||||||
->updated_by = $this->attributes['updated_by'];
|
|
||||||
new MediaModel('image')
|
|
||||||
->updateMedia($this->getCover());
|
|
||||||
} else {
|
|
||||||
$cover = new Image([
|
|
||||||
'file_key' => 'podcasts/' . $this->attributes['handle'] . '/cover.' . $file->getExtension(),
|
|
||||||
'sizes' => config('Images')
|
|
||||||
->podcastCoverSizes,
|
|
||||||
'uploaded_by' => $this->attributes['updated_by'],
|
|
||||||
'updated_by' => $this->attributes['updated_by'],
|
|
||||||
]);
|
|
||||||
$cover->setFile($file);
|
|
||||||
|
|
||||||
$this->attributes['cover_id'] = new MediaModel('image')->saveMedia($cover);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCover(): Image
|
public function getImage(): Image
|
||||||
{
|
{
|
||||||
if (! $this->cover instanceof Image) {
|
return new Image(null, $this->image_path, $this->image_mimetype);
|
||||||
$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|null $file = null): self
|
|
||||||
{
|
|
||||||
if (! $file instanceof File || ($file instanceof UploadedFile && ! $file->isValid())) {
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (array_key_exists('banner_id', $this->attributes) && $this->attributes['banner_id'] !== null) {
|
|
||||||
$this->getBanner()
|
|
||||||
->setFile($file);
|
|
||||||
$this->getBanner()
|
|
||||||
->updated_by = $this->attributes['updated_by'];
|
|
||||||
new MediaModel('image')
|
|
||||||
->updateMedia($this->getBanner());
|
|
||||||
} else {
|
|
||||||
$banner = new Image([
|
|
||||||
'file_key' => 'podcasts/' . $this->attributes['handle'] . '/banner.' . $file->getExtension(),
|
|
||||||
'sizes' => config('Images')
|
|
||||||
->podcastBannerSizes,
|
|
||||||
'uploaded_by' => $this->attributes['updated_by'],
|
|
||||||
'updated_by' => $this->attributes['updated_by'],
|
|
||||||
]);
|
|
||||||
$banner->setFile($file);
|
|
||||||
|
|
||||||
$this->attributes['banner_id'] = new MediaModel('image')->saveMedia($banner);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getBanner(): ?Image
|
|
||||||
{
|
|
||||||
if ($this->banner_id === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $this->banner instanceof Image) {
|
|
||||||
$this->banner = new MediaModel('image')
|
|
||||||
->getMediaById($this->banner_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->banner;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLink(): string
|
public function getLink(): string
|
||||||
{
|
{
|
||||||
return url_to('podcast-activity', $this->attributes['handle']);
|
return url_to('podcast-activity', $this->attributes['name']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFeedUrl(): string
|
public function getFeedUrl(): string
|
||||||
{
|
{
|
||||||
return url_to('podcast-rss-feed', $this->attributes['handle']);
|
return url_to('podcast_feed', $this->attributes['name']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -320,23 +223,17 @@ class Podcast extends Entity
|
||||||
*/
|
*/
|
||||||
public function getEpisodes(): array
|
public function getEpisodes(): array
|
||||||
{
|
{
|
||||||
|
if ($this->id === null) {
|
||||||
|
throw new RuntimeException('Podcast must be created before getting episodes.');
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->episodes === null) {
|
if ($this->episodes === null) {
|
||||||
$this->episodes = new EpisodeModel()
|
$this->episodes = (new EpisodeModel())->getPodcastEpisodes($this->id, $this->type);
|
||||||
->getPodcastEpisodes($this->id, $this->type);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->episodes;
|
return $this->episodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the podcast's episodes count
|
|
||||||
*/
|
|
||||||
public function getEpisodesCount(): int|string
|
|
||||||
{
|
|
||||||
return new EpisodeModel()
|
|
||||||
->getPodcastEpisodesCount($this->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the podcast's persons
|
* Returns the podcast's persons
|
||||||
*
|
*
|
||||||
|
|
@ -344,9 +241,12 @@ class Podcast extends Entity
|
||||||
*/
|
*/
|
||||||
public function getPersons(): array
|
public function getPersons(): array
|
||||||
{
|
{
|
||||||
|
if ($this->id === null) {
|
||||||
|
throw new RuntimeException('Podcast must be created before getting persons.');
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->persons === null) {
|
if ($this->persons === null) {
|
||||||
$this->persons = new PersonModel()
|
$this->persons = (new PersonModel())->getPodcastPersons($this->id);
|
||||||
->getPodcastPersons($this->id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->persons;
|
return $this->persons;
|
||||||
|
|
@ -357,29 +257,17 @@ class Podcast extends Entity
|
||||||
*/
|
*/
|
||||||
public function getCategory(): ?Category
|
public function getCategory(): ?Category
|
||||||
{
|
{
|
||||||
if (! $this->category instanceof Category) {
|
if ($this->id === null) {
|
||||||
$this->category = new CategoryModel()
|
throw new RuntimeException('Podcast must be created before getting category.');
|
||||||
->getCategoryById($this->category_id);
|
}
|
||||||
|
|
||||||
|
if ($this->category === null) {
|
||||||
|
$this->category = (new CategoryModel())->getCategoryById($this->category_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->category;
|
return $this->category;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all podcast subscriptions
|
|
||||||
*
|
|
||||||
* @return Subscription[]
|
|
||||||
*/
|
|
||||||
public function getSubscriptions(): array
|
|
||||||
{
|
|
||||||
if ($this->subscriptions === null) {
|
|
||||||
$this->subscriptions = new SubscriptionModel()
|
|
||||||
->getPodcastSubscriptions($this->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->subscriptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all podcast contributors
|
* Returns all podcast contributors
|
||||||
*
|
*
|
||||||
|
|
@ -387,9 +275,12 @@ class Podcast extends Entity
|
||||||
*/
|
*/
|
||||||
public function getContributors(): array
|
public function getContributors(): array
|
||||||
{
|
{
|
||||||
|
if ($this->id === null) {
|
||||||
|
throw new RuntimeException('Podcasts must be created before getting contributors.');
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->contributors === null) {
|
if ($this->contributors === null) {
|
||||||
$this->contributors = new UserModel()
|
$this->contributors = (new UserModel())->getPodcastContributors($this->id);
|
||||||
->getPodcastContributors($this->id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->contributors;
|
return $this->contributors;
|
||||||
|
|
@ -397,21 +288,41 @@ class Podcast extends Entity
|
||||||
|
|
||||||
public function setDescriptionMarkdown(string $descriptionMarkdown): static
|
public function setDescriptionMarkdown(string $descriptionMarkdown): static
|
||||||
{
|
{
|
||||||
$config = [
|
$converter = new CommonMarkConverter([
|
||||||
'html_input' => 'escape',
|
'html_input' => 'strip',
|
||||||
'allow_unsafe_links' => false,
|
'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['description_markdown'] = $descriptionMarkdown;
|
$this->attributes['description_markdown'] = $descriptionMarkdown;
|
||||||
$this->attributes['description_html'] = $converter->convert($descriptionMarkdown);
|
$this->attributes['description_html'] = $converter->convertToHtml($descriptionMarkdown);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
$converter = new CommonMarkConverter([
|
||||||
|
'html_input' => 'strip',
|
||||||
|
'allow_unsafe_links' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->attributes[
|
||||||
|
'episode_description_footer_markdown'
|
||||||
|
] = $episodeDescriptionFooterMarkdown;
|
||||||
|
$this->attributes[
|
||||||
|
'episode_description_footer_html'
|
||||||
|
] = $converter->convertToHtml($episodeDescriptionFooterMarkdown);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
@ -420,28 +331,13 @@ class Podcast extends Entity
|
||||||
{
|
{
|
||||||
if ($this->description === null) {
|
if ($this->description === null) {
|
||||||
$this->description = trim(
|
$this->description = trim(
|
||||||
(string) preg_replace('~\s+~', ' ', strip_tags((string) $this->attributes['description_html'])),
|
(string) preg_replace('~\s+~', ' ', strip_tags($this->attributes['description_html'])),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->description;
|
return $this->description;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPublicationStatus(): string
|
|
||||||
{
|
|
||||||
if ($this->publication_status === null) {
|
|
||||||
if (! $this->published_at instanceof Time) {
|
|
||||||
$this->publication_status = 'not_published';
|
|
||||||
} elseif ($this->published_at->isBefore(Time::now())) {
|
|
||||||
$this->publication_status = 'published';
|
|
||||||
} else {
|
|
||||||
$this->publication_status = 'scheduled';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->publication_status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the podcast's podcasting platform links
|
* Returns the podcast's podcasting platform links
|
||||||
*
|
*
|
||||||
|
|
@ -449,9 +345,12 @@ class Podcast extends Entity
|
||||||
*/
|
*/
|
||||||
public function getPodcastingPlatforms(): array
|
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) {
|
if ($this->podcasting_platforms === null) {
|
||||||
$this->podcasting_platforms = new PlatformModel()
|
$this->podcasting_platforms = (new PlatformModel())->getPodcastPlatforms($this->id, 'podcasting');
|
||||||
->getPlatforms($this->id, 'podcasting');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->podcasting_platforms;
|
return $this->podcasting_platforms;
|
||||||
|
|
@ -464,9 +363,12 @@ class Podcast extends Entity
|
||||||
*/
|
*/
|
||||||
public function getSocialPlatforms(): array
|
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) {
|
if ($this->social_platforms === null) {
|
||||||
$this->social_platforms = new PlatformModel()
|
$this->social_platforms = (new PlatformModel())->getPodcastPlatforms($this->id, 'social');
|
||||||
->getPlatforms($this->id, 'social');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->social_platforms;
|
return $this->social_platforms;
|
||||||
|
|
@ -479,9 +381,12 @@ class Podcast extends Entity
|
||||||
*/
|
*/
|
||||||
public function getFundingPlatforms(): array
|
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) {
|
if ($this->funding_platforms === null) {
|
||||||
$this->funding_platforms = new PlatformModel()
|
$this->funding_platforms = (new PlatformModel())->getPodcastPlatforms($this->id, 'funding');
|
||||||
->getPlatforms($this->id, 'funding');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->funding_platforms;
|
return $this->funding_platforms;
|
||||||
|
|
@ -492,9 +397,12 @@ class Podcast extends Entity
|
||||||
*/
|
*/
|
||||||
public function getOtherCategories(): array
|
public function getOtherCategories(): array
|
||||||
{
|
{
|
||||||
|
if ($this->id === null) {
|
||||||
|
throw new RuntimeException('Podcast must be created before getting other categories.');
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->other_categories === null) {
|
if ($this->other_categories === null) {
|
||||||
$this->other_categories = new CategoryModel()
|
$this->other_categories = (new CategoryModel())->getPodcastCategories($this->id);
|
||||||
->getPodcastCategories($this->id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->other_categories;
|
return $this->other_categories;
|
||||||
|
|
@ -505,7 +413,7 @@ class Podcast extends Entity
|
||||||
*/
|
*/
|
||||||
public function getOtherCategoriesIds(): array
|
public function getOtherCategoriesIds(): array
|
||||||
{
|
{
|
||||||
if ($this->other_categories_ids === []) {
|
if ($this->other_categories_ids === null) {
|
||||||
$this->other_categories_ids = array_column($this->getOtherCategories(), 'id');
|
$this->other_categories_ids = array_column($this->getOtherCategories(), 'id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -517,7 +425,7 @@ class Podcast extends Entity
|
||||||
*/
|
*/
|
||||||
public function setLocation(?Location $location = null): static
|
public function setLocation(?Location $location = null): static
|
||||||
{
|
{
|
||||||
if (! $location instanceof Location) {
|
if ($location === null) {
|
||||||
$this->attributes['location_name'] = null;
|
$this->attributes['location_name'] = null;
|
||||||
$this->attributes['location_geo'] = null;
|
$this->attributes['location_geo'] = null;
|
||||||
$this->attributes['location_osm'] = null;
|
$this->attributes['location_osm'] = null;
|
||||||
|
|
@ -545,17 +453,58 @@ class Podcast extends Entity
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $this->location instanceof Location) {
|
if ($this->location === null) {
|
||||||
$this->location = new Location($this->location_name, $this->location_geo, $this->location_osm);
|
$this->location = new Location($this->location_name, $this->location_geo, $this->location_osm);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->location;
|
return $this->location;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getIsPremium(): bool
|
/**
|
||||||
|
* Get custom rss tag as XML String
|
||||||
|
*/
|
||||||
|
public function getCustomRssString(): string
|
||||||
{
|
{
|
||||||
// podcast is premium if at least one of its episodes is set as premium
|
if ($this->attributes['custom_rss'] === null) {
|
||||||
return new EpisodeModel()
|
return '';
|
||||||
->doesPodcastHavePremiumEpisodes($this->id);
|
}
|
||||||
|
|
||||||
|
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 str_replace(['<channel>', '</channel>'], '', $xmlNode->asXML());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves custom rss tag into json
|
||||||
|
*/
|
||||||
|
public function setCustomRssString(string $customRssString): static
|
||||||
|
{
|
||||||
|
if ($customRssString === '') {
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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