castopod/modules/Admin/Controllers/SettingsController.php

321 lines
11 KiB
PHP

<?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 Modules\Admin\Controllers;
use App\Models\ActorModel;
use App\Models\EpisodeCommentModel;
use App\Models\EpisodeModel;
use App\Models\MediaModel;
use App\Models\PersonModel;
use App\Models\PodcastModel;
use App\Models\PostModel;
use CodeIgniter\Files\File;
use CodeIgniter\HTTP\RedirectResponse;
use PHP_ICO;
class SettingsController extends BaseController
{
public function index(): string
{
helper('form');
return view('settings/general');
}
public function attemptInstanceEdit(): RedirectResponse
{
$rules = [
'site_icon' =>
'is_image[site_icon]|ext_in[site_icon,png,jpeg]|is_image_ratio[site_icon,1,1]|min_dims[image,512,512]|permit_empty',
];
if (! $this->validate($rules)) {
return redirect()
->back()
->withInput()
->with('errors', $this->validator->getErrors());
}
$siteName = $this->request->getPost('site_name');
if ($siteName !== service('settings')->get('App.siteName')) {
service('settings')->set('App.siteName', $siteName);
}
$siteDescription = $this->request->getPost('site_description');
if ($siteDescription !== service('settings')->get('App.siteDescription')) {
service('settings')->set('App.siteDescription', $siteDescription);
}
$siteIconFile = $this->request->getFile('site_icon');
if ($siteIconFile !== null && $siteIconFile->isValid()) {
helper(['filesystem', 'media']);
// delete site folder in media before repopulating it
delete_files(media_path('/site'));
// save original in disk
$originalFilename = save_media($siteIconFile, 'site', 'icon');
// convert jpeg image to png if not
if ($siteIconFile->getClientMimeType() !== 'image/png') {
service('image')->withFile(media_path($originalFilename))
->convert(IMAGETYPE_JPEG)
->save(media_path('/site/icon.png'));
}
// generate random hash to use as a suffix to renew browser cache
$randomHash = substr(bin2hex(random_bytes(18)), 0, 8);
// generate ico
$ico_lib = new PHP_ICO();
$ico_lib->add_image(media_path('/site/icon.png'), [[32, 32], [64, 64]]);
$ico_lib->save_ico(media_path("/site/favicon.{$randomHash}.ico"));
// resize original to needed sizes
foreach ([64, 180, 192, 512] as $size) {
service('image')
->withFile(media_path('/site/icon.png'))
->resize($size, $size)
->save(media_path("/site/icon-{$size}.{$randomHash}.png"));
}
service('settings')
->set('App.siteIcon', [
'ico' => '/' . media_path("/site/favicon.{$randomHash}.ico"),
'64' => '/' . media_path("/site/icon-64.{$randomHash}.png"),
'180' => '/' . media_path("/site/icon-180.{$randomHash}.png"),
'192' => '/' . media_path("/site/icon-192.{$randomHash}.png"),
'512' => '/' . media_path("/site/icon-512.{$randomHash}.png"),
]);
}
return redirect('settings-general')->with('message', lang('Settings.instance.editSuccess'));
}
public function deleteIcon(): RedirectResponse
{
helper(['filesystem', 'media']);
// delete site folder in media
delete_files(media_path('/site'));
service('settings')
->forget('App.siteIcon');
return redirect('settings-general')->with('message', lang('Settings.instance.deleteIconSuccess'));
}
public function regenerateImages(): RedirectResponse
{
helper('media');
$allPodcasts = (new PodcastModel())->findAll();
$imageExt = ['jpg', 'png', 'webp'];
foreach ($allPodcasts as $podcast) {
foreach ($imageExt as $ext) {
$podcastImages = glob(media_path("/podcasts/{$podcast->handle}/*_*{$ext}"));
if ($podcastImages) {
foreach ($podcastImages as $podcastImage) {
if (is_file($podcastImage)) {
unlink($podcastImage);
}
}
}
}
$podcast->cover->saveSizes();
if ($podcast->banner_id !== null) {
$podcast->banner->saveSizes();
}
foreach ($podcast->episodes as $episode) {
if ($episode->cover_id !== null) {
$episode->cover->saveSizes();
}
}
}
foreach ($imageExt as $ext) {
$personsImages = glob(media_path("/persons/*_*{$ext}"));
if ($personsImages) {
foreach ($personsImages as $personsImage) {
if (is_file($personsImage)) {
unlink($personsImage);
}
}
}
}
$persons = (new PersonModel())->findAll();
foreach ($persons as $person) {
if ($person->avatar_id !== null) {
$person->avatar->saveSizes();
}
}
return redirect('settings-general')->with('message', lang('Settings.images.regenerationSuccess'));
}
public function runHousekeeping(): RedirectResponse
{
if ($this->request->getPost('reset_counts') === 'yes') {
// recalculate fediverse counts
(new ActorModel())->resetFollowersCount();
(new ActorModel())->resetPostsCount();
(new PostModel())->setEpisodeIdForRepliesOfEpisodePosts();
(new PostModel())->resetFavouritesCount();
(new PostModel())->resetReblogsCount();
(new PostModel())->resetRepliesCount();
(new EpisodeModel())->resetCommentsCount();
(new EpisodeModel())->resetPostsCount();
(new EpisodeCommentModel())->resetLikesCount();
(new EpisodeCommentModel())->resetRepliesCount();
}
helper('media');
if ($this->request->getPost('rewrite_media') === 'yes') {
$imageExt = ['jpg', 'png', 'webp'];
// Delete all podcast image sizes to recreate them
$allPodcasts = (new PodcastModel())->findAll();
foreach ($allPodcasts as $podcast) {
foreach ($imageExt as $ext) {
$podcastImages = glob(media_path("/podcasts/{$podcast->handle}/*_*{$ext}"));
if ($podcastImages) {
foreach ($podcastImages as $podcastImage) {
if (is_file($podcastImage)) {
unlink($podcastImage);
}
}
}
}
}
// Delete all person image sizes to recreate them
foreach ($imageExt as $ext) {
$personsImages = glob(media_path("/persons/*_*{$ext}"));
if ($personsImages) {
foreach ($personsImages as $personsImage) {
if (is_file($personsImage)) {
unlink($personsImage);
}
}
}
}
$allImages = (new MediaModel('image'))->getAllOfType();
foreach ($allImages as $image) {
if (str_starts_with($image->file_path, 'podcasts')) {
if (str_ends_with($image->file_path, 'banner.jpg') || str_ends_with(
$image->file_path,
'banner.png'
) || str_ends_with($image->file_path, 'banner.jpeg')) {
$image->sizes = config('Images')
->podcastBannerSizes;
} else {
$image->sizes = config('Images')
->podcastCoverSizes;
}
} elseif (str_starts_with($image->file_path, 'persons')) {
$image->sizes = config('Images')
->personAvatarSizes;
} else {
$image->sizes = [];
}
$image->setFile(new File(media_path($image->file_path)));
(new MediaModel('image'))->updateMedia($image);
}
$allAudio = (new MediaModel('audio'))->getAllOfType();
foreach ($allAudio as $audio) {
$audio->setFile(new File(media_path($audio->file_path)));
(new MediaModel('audio'))->updateMedia($audio);
}
$allTranscripts = (new MediaModel('transcript'))->getAllOfType();
foreach ($allTranscripts as $transcript) {
$transcript->setFile(new File(media_path($transcript->file_path)));
(new MediaModel('transcript'))->updateMedia($transcript);
}
$allChapters = (new MediaModel('chapters'))->getAllOfType();
foreach ($allChapters as $chapters) {
$chapters->setFile(new File(media_path($chapters->file_path)));
(new MediaModel('chapters'))->updateMedia($chapters);
}
$allVideos = (new MediaModel('video'))->getAllOfType();
foreach ($allVideos as $video) {
$video->setFile(new File(media_path($video->file_path)));
(new MediaModel('video'))->updateMedia($video);
}
// reset avatar and banner image urls for each podcast actor
foreach ($allPodcasts as $podcast) {
$actorModel = new ActorModel();
$actor = $actorModel->getActorById($podcast->actor_id);
if ($actor !== null) {
// update values
$actor->avatar_image_url = $podcast->cover->federation_url;
$actor->avatar_image_mimetype = $podcast->cover->file_mimetype;
$actor->cover_image_url = $podcast->banner->federation_url;
$actor->cover_image_mimetype = $podcast->banner->file_mimetype;
if ($actor->hasChanged()) {
$actorModel->update($actor->id, $actor);
}
}
}
}
if ($this->request->getPost('clear_cache') === 'yes') {
cache()->clean();
}
if ($this->request->getPost('rename_episodes_files') === 'yes') {
$allAudio = (new MediaModel('audio'))->getAllOfType();
foreach ($allAudio as $audio) {
$audio->rename();
}
}
return redirect('settings-general')->with('message', lang('Settings.housekeeping.runSuccess'));
}
public function theme(): string
{
helper('form');
return view('settings/theme');
}
public function attemptSetInstanceTheme(): RedirectResponse
{
$theme = $this->request->getPost('theme');
service('settings')
->set('App.theme', $theme);
// delete all pages cache
cache()
->deleteMatching('page*');
return redirect('settings-theme')->with('message', lang('Settings.theme.setInstanceThemeSuccess'));
}
}