adding kirby3-janitor
This commit is contained in:
165
site/plugins/kirby3-janitor/classes/BackupZipJob.php
Normal file
165
site/plugins/kirby3-janitor/classes/BackupZipJob.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Bnomei;
|
||||
|
||||
use Kirby\Cms\Page;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Dir;
|
||||
use Kirby\Toolkit\F;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use ZipArchive;
|
||||
|
||||
final class BackupZipJob extends JanitorJob
|
||||
{
|
||||
/** @var array */
|
||||
private $options;
|
||||
|
||||
public function __construct(?Page $page = null, ?string $data = null)
|
||||
{
|
||||
parent::__construct($page, $data);
|
||||
|
||||
$this->options = [
|
||||
'ulimit' => option('bnomei.janitor.backupzip.ulimit', 512), // 1024 seems to be unix default
|
||||
'date' => option('bnomei.janitor.backupzip.date', null), // null to disable, 'since 1 day ago'
|
||||
'roots' => option('bnomei.janitor.backupzip.roots', function () {
|
||||
return [
|
||||
kirby()->roots()->accounts(),
|
||||
kirby()->roots()->content(),
|
||||
];
|
||||
}),
|
||||
'target' => option('bnomei.janitor.backupzip.target', function () {
|
||||
$dir = realpath(kirby()->roots()->accounts() . '/../') . '/backups';
|
||||
Dir::make($dir);
|
||||
$prefix = option('bnomei.janitor.backupzip.prefix', '');
|
||||
return $dir . '/' . $prefix . time() . '.zip'; // date('Y-m-d')
|
||||
}),
|
||||
];
|
||||
|
||||
foreach ($this->options as $key => $value) {
|
||||
if (!is_string($value) && is_callable($value)) {
|
||||
$this->options[$key] = $value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $key
|
||||
* @return array
|
||||
*/
|
||||
public function option(?string $key = null)
|
||||
{
|
||||
if ($key) {
|
||||
return A::get($this->options, $key);
|
||||
}
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
public static function directory(): string
|
||||
{
|
||||
return \dirname((string) (new self())->option('target'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function job(): array
|
||||
{
|
||||
$time = time();
|
||||
$climate = \Bnomei\Janitor::climate();
|
||||
|
||||
$zipPath = (string) $this->option('target');
|
||||
$zip = new ZipArchive();
|
||||
if ($zip->open($zipPath, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE) !== true) {
|
||||
if ($climate) {
|
||||
$climate->red('Failed to create: ' . $zipPath);
|
||||
}
|
||||
return [
|
||||
'status' => 500,
|
||||
];
|
||||
}
|
||||
|
||||
$roots = $this->option('roots');
|
||||
$finder = new Finder();
|
||||
$finder->files()->in($roots);
|
||||
if ($date = $this->option('date')) {
|
||||
$finder->date($date);
|
||||
}
|
||||
$count = iterator_count($finder); // closing of zip
|
||||
|
||||
$ulimit = $this->option('ulimit');
|
||||
$zipped = 0;
|
||||
if ($climate) {
|
||||
$climate->out('Files: ' . $count);
|
||||
}
|
||||
|
||||
$progress = null;
|
||||
if ($count && $climate) {
|
||||
$progress = $climate->progress()->total($count);
|
||||
}
|
||||
|
||||
foreach ($finder as $file) {
|
||||
$filePath = $file->getPath() . DIRECTORY_SEPARATOR . $file->getFilename();
|
||||
$localFilePath = $filePath;
|
||||
foreach ($roots as $root) {
|
||||
$localFilePath = str_replace(dirname($root), '', $localFilePath);
|
||||
}
|
||||
if ($zip->addFile($filePath, $localFilePath)) {
|
||||
$mime = F::mime($filePath);
|
||||
|
||||
if (in_array($mime, [
|
||||
'application/json', 'text/json',
|
||||
'application/yaml', 'text/yaml',
|
||||
'text/html',
|
||||
'text/plain',
|
||||
'text/xml',
|
||||
'application/x-javascript',
|
||||
'text/css',
|
||||
'text/csv', 'text/x-comma-separated-values',
|
||||
'text/comma-separated-values', 'application/octet-stream',
|
||||
])) {
|
||||
$zip->setCompressionName($filePath, ZipArchive::CM_DEFLATE);
|
||||
} else {
|
||||
$zip->setCompressionName($filePath, ZipArchive::CM_STORE);
|
||||
}
|
||||
|
||||
$zipped++;
|
||||
|
||||
if ($progress && $climate) {
|
||||
$progress->current($zipped);
|
||||
}
|
||||
if ($zipped % $ulimit === 0) {
|
||||
$zip->close();
|
||||
if ($zip->open($zipPath) === false) {
|
||||
@unlink($zipPath);
|
||||
if ($climate) {
|
||||
$climate->red('Hit ulimit but failed to reopen zip: ' . $zipPath);
|
||||
}
|
||||
return [
|
||||
'status' => 500,
|
||||
'error' => 'Hit ulimit but failed to reopen zip: ' . $zipPath,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($climate) {
|
||||
$climate->out('Closing zip...');
|
||||
}
|
||||
$zip->close();
|
||||
if ($climate) {
|
||||
$climate->out($zipPath);
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => $zipped > 0 ? 200 : 204,
|
||||
'duration' => time() - $time,
|
||||
'filename' => \basename($zipPath, '.zip'),
|
||||
'files' => $zipped,
|
||||
'nicesize' => \Kirby\Toolkit\F::niceSize($zipPath),
|
||||
'modified' => date('d/m/Y, H:i:s', \Kirby\Toolkit\F::modified($zipPath)),
|
||||
];
|
||||
}
|
||||
}
|
||||
39
site/plugins/kirby3-janitor/classes/CleanCacheFilesJob.php
Normal file
39
site/plugins/kirby3-janitor/classes/CleanCacheFilesJob.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Bnomei;
|
||||
|
||||
use Kirby\Toolkit\F;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
final class CleanCacheFilesJob extends JanitorJob
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function job(): array
|
||||
{
|
||||
$dir = kirby()->roots()->cache();
|
||||
$removed = 0;
|
||||
$finder = new Finder();
|
||||
$finder->files()->name('*.cache')->in($dir);
|
||||
$count = iterator_count($finder);
|
||||
$climate = \Bnomei\Janitor::climate();
|
||||
$progress = null;
|
||||
if ($count && $climate) {
|
||||
$progress = $climate->progress()->total($count);
|
||||
}
|
||||
foreach ($finder as $cacheFile) {
|
||||
if (F::remove($cacheFile->getRealPath())) {
|
||||
$removed++;
|
||||
if ($progress && $climate) {
|
||||
$progress->current($removed);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [
|
||||
'status' => $removed > 0 ? 200 : 204,
|
||||
];
|
||||
}
|
||||
}
|
||||
116
site/plugins/kirby3-janitor/classes/CleanContentJob.php
Normal file
116
site/plugins/kirby3-janitor/classes/CleanContentJob.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Bnomei;
|
||||
|
||||
use Kirby\Toolkit\F;
|
||||
|
||||
final class CleanContentJob extends JanitorJob
|
||||
{
|
||||
private $progress;
|
||||
private $climate;
|
||||
|
||||
/**
|
||||
* based on cookbook by @texnixe
|
||||
* https://getkirby.com/docs/cookbook/extensions/content-file-cleanup
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function job(): array
|
||||
{
|
||||
$kirby = kirby();
|
||||
|
||||
// Authenticate as almighty
|
||||
$kirby->impersonate('kirby');
|
||||
|
||||
// Define your collection
|
||||
// Don't use `$site->index()` for thousands of pages
|
||||
$collection = $kirby->site()->index();
|
||||
|
||||
$time = microtime(true);
|
||||
$count = $collection->count();
|
||||
$updated = 0;
|
||||
$this->climate = \Bnomei\Janitor::climate();
|
||||
|
||||
// set the fields to be ignored
|
||||
$ignore = option('bnomei.jabitor.cleancontentjob.ignore', ['title', 'slug', 'template', 'sort']);
|
||||
|
||||
// call the script for all languages if multilang
|
||||
if ($kirby->multilang() === true) {
|
||||
$languages = $kirby->languages();
|
||||
foreach ($languages as $language) {
|
||||
$updated += $this->cleanUp($collection, $ignore, $language->code());
|
||||
}
|
||||
} else {
|
||||
$updated += $this->cleanUp($collection, $ignore);
|
||||
}
|
||||
|
||||
if ($this->climate) {
|
||||
$this->climate->blue('duration: ' . round((microtime(true) - $time) * 1000) . 'ms');
|
||||
$this->climate->blue('count: ' . $count);
|
||||
$this->climate->blue('updated: ' . $updated);
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => $updated > 0 ? 200 : 204,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* based on cookbook by @texnixe
|
||||
* https://getkirby.com/docs/cookbook/extensions/content-file-cleanup
|
||||
*/
|
||||
protected function cleanUp($collection, $ignore = null, string $lang = null): int
|
||||
{
|
||||
$updated = 0;
|
||||
foreach ($collection as $item) {
|
||||
// get all fields in the content file
|
||||
$contentFields = $item->content($lang)->fields();
|
||||
|
||||
// unset all fields in the `$ignore` array
|
||||
foreach ($ignore as $field) {
|
||||
if (array_key_exists($field, $contentFields) === true) {
|
||||
unset($contentFields[$field]);
|
||||
}
|
||||
}
|
||||
|
||||
// get the keys
|
||||
$contentFields = array_keys($contentFields);
|
||||
|
||||
// get all field keys from blueprint
|
||||
$blueprintFields = array_keys($item->blueprint()->fields());
|
||||
|
||||
// get all field keys that are in $contentFields but not in $blueprintFields
|
||||
$fieldsToBeDeleted = array_diff($contentFields, $blueprintFields);
|
||||
|
||||
// update page only if there are any fields to be deleted
|
||||
if (count($fieldsToBeDeleted) > 0) {
|
||||
|
||||
// flip keys and values and set new values to null
|
||||
$data = array_map(function ($value) {
|
||||
return null;
|
||||
}, array_flip($fieldsToBeDeleted));
|
||||
|
||||
// try to update the page with the data
|
||||
try {
|
||||
$item->update($data, $lang);
|
||||
$updated++;
|
||||
if ($this->climate) {
|
||||
$this->climate->green('+++ ' . $item->id());
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if ($this->climate) {
|
||||
$this->climate->red('ERR ' . $item->id() . ': ' .$e->getMessage());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($this->climate) {
|
||||
$this->climate->white('=== ' . $item->id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $updated;
|
||||
}
|
||||
}
|
||||
19
site/plugins/kirby3-janitor/classes/CleanSessionsJob.php
Normal file
19
site/plugins/kirby3-janitor/classes/CleanSessionsJob.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Bnomei;
|
||||
|
||||
final class CleanSessionsJob extends JanitorJob
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function job(): array
|
||||
{
|
||||
$success = kirby()->app()->session()->store()->collectGarbage();
|
||||
return [
|
||||
'status' => $success ? 200 : 204,
|
||||
];
|
||||
}
|
||||
}
|
||||
19
site/plugins/kirby3-janitor/classes/ContextJob.php
Normal file
19
site/plugins/kirby3-janitor/classes/ContextJob.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Bnomei;
|
||||
|
||||
final class ContextJob extends JanitorJob
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function job(): array
|
||||
{
|
||||
return [
|
||||
'status' => 200,
|
||||
'label' => $this->page()->title()->value() . ' ' . $this->data(),
|
||||
];
|
||||
}
|
||||
}
|
||||
24
site/plugins/kirby3-janitor/classes/FlushLapseJob.php
Normal file
24
site/plugins/kirby3-janitor/classes/FlushLapseJob.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Bnomei;
|
||||
|
||||
final class FlushLapseJob extends JanitorJob
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function job(): array
|
||||
{
|
||||
$success = false;
|
||||
|
||||
if (class_exists('\Bnomei\Lapse')) {
|
||||
$success = \Bnomei\Lapse::singleton()->flush();
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => $success ? 200 : 204,
|
||||
];
|
||||
}
|
||||
}
|
||||
18
site/plugins/kirby3-janitor/classes/FlushPagesCacheJob.php
Normal file
18
site/plugins/kirby3-janitor/classes/FlushPagesCacheJob.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Bnomei;
|
||||
|
||||
final class FlushPagesCacheJob extends JanitorJob
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function job(): array
|
||||
{
|
||||
return [
|
||||
'status' => kirby()->cache('pages')->flush() ? 200 : 404,
|
||||
];
|
||||
}
|
||||
}
|
||||
29
site/plugins/kirby3-janitor/classes/FlushRedisDBJob.php
Normal file
29
site/plugins/kirby3-janitor/classes/FlushRedisDBJob.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Bnomei;
|
||||
|
||||
final class FlushRedisDBJob extends JanitorJob
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function job(): array
|
||||
{
|
||||
$success = false;
|
||||
|
||||
if (class_exists('\Bnomei\Redis')) {
|
||||
$redis = new \Bnomei\Redis();
|
||||
if ($redis->redisClient()->dbsize() > 1) {
|
||||
// DANGER: $this->connection->flushdb()
|
||||
$redis->flush();
|
||||
$success = $redis->redisClient()->dbsize() === 0;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => $success ? 200 : 204,
|
||||
];
|
||||
}
|
||||
}
|
||||
39
site/plugins/kirby3-janitor/classes/FlushSessionFilesJob.php
Normal file
39
site/plugins/kirby3-janitor/classes/FlushSessionFilesJob.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Bnomei;
|
||||
|
||||
use Kirby\Toolkit\F;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
final class FlushSessionFilesJob extends JanitorJob
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function job(): array
|
||||
{
|
||||
$dir = kirby()->root('sessions');
|
||||
$removed = 0;
|
||||
$finder = new Finder();
|
||||
$finder->files()->name('*.sess')->in($dir);
|
||||
$count = iterator_count($finder);
|
||||
$climate = \Bnomei\Janitor::climate();
|
||||
$progress = null;
|
||||
if ($count && $climate) {
|
||||
$progress = $climate->progress()->total($count);
|
||||
}
|
||||
foreach ($finder as $cacheFile) {
|
||||
if (F::remove($cacheFile->getRealPath())) {
|
||||
$removed++;
|
||||
if ($progress && $climate) {
|
||||
$progress->current($removed);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [
|
||||
'status' => $removed > 0 ? 200 : 204,
|
||||
];
|
||||
}
|
||||
}
|
||||
274
site/plugins/kirby3-janitor/classes/Janitor.php
Normal file
274
site/plugins/kirby3-janitor/classes/Janitor.php
Normal file
@@ -0,0 +1,274 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Bnomei;
|
||||
|
||||
use Kirby\Cms\File;
|
||||
use Kirby\Cms\Page;
|
||||
use Kirby\Cms\User;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Str;
|
||||
use League\CLImate\CLImate;
|
||||
|
||||
final class Janitor
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Janitor constructor.
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$defaults = [
|
||||
'debug' => option('debug'),
|
||||
'log' => option('bnomei.janitor.log.fn'),
|
||||
'jobs' => option('bnomei.janitor.jobs'),
|
||||
'jobs-defaults' => ['bnomei.janitor.jobs-defaults'],
|
||||
'jobs-extends' => option('bnomei.janitor.jobs-extends'),
|
||||
'secret' => option('bnomei.janitor.secret'),
|
||||
];
|
||||
$this->options = array_merge($defaults, $options);
|
||||
|
||||
$extends = array_merge($this->options['jobs-defaults'], $this->options['jobs-extends']);
|
||||
foreach ($extends as $extend) {
|
||||
// NOTE: it is intended that jobs override merged not other way around
|
||||
$this->options['jobs'] = array_change_key_case(
|
||||
array_merge(option($extend, []), $this->options['jobs'])
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($this->options as $key => $call) {
|
||||
if (is_callable($call) && in_array($key, ['secret'])) {
|
||||
$this->options[$key] = $call();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $key
|
||||
* @return array
|
||||
*/
|
||||
public function option(?string $key = null)
|
||||
{
|
||||
if ($key) {
|
||||
return A::get($this->options, $key);
|
||||
}
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $secret
|
||||
* @param string $name
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function jobWithSecret(string $secret, string $name, array $data = []): array
|
||||
{
|
||||
if ($secret === $this->option('secret')) {
|
||||
return $this->job($name, $data);
|
||||
}
|
||||
return [
|
||||
'status' => 401,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function job(string $name, array $data = []): array
|
||||
{
|
||||
$job = $this->findJob($name);
|
||||
|
||||
if (!is_string($job) && is_callable($job)) {
|
||||
return $this->jobFromCallable($job, $data);
|
||||
} elseif (class_exists($job)) {
|
||||
return $this->jobFromClass($job, $data);
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => 404,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function listJobs()
|
||||
{
|
||||
// find in jobs config
|
||||
return array_keys($this->option('jobs'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function findJob(string $name)
|
||||
{
|
||||
// find in jobs config
|
||||
$jobInConfig = A::get($this->option('jobs'), strtolower($name));
|
||||
if ($jobInConfig) {
|
||||
return $jobInConfig;
|
||||
}
|
||||
|
||||
// could be a class
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $job
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function jobFromCallable($job, array $data): array
|
||||
{
|
||||
$return = false;
|
||||
try {
|
||||
set_time_limit(0);
|
||||
} catch (\Exception $ex) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
try {
|
||||
$return = $job(
|
||||
page(str_replace('+', '/', urldecode(A::get($data, 'contextPage', '')))),
|
||||
str_replace('+S_L_A_S_H+', '/', urldecode(A::get($data, 'contextData', '')))
|
||||
);
|
||||
} catch (\BadMethodCallException $ex) {
|
||||
$return = $job();
|
||||
}
|
||||
if (is_array($return)) {
|
||||
return $return;
|
||||
}
|
||||
return [
|
||||
'status' => $return ? 200 : 404,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $job
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function jobFromClass(string $job, array $data): array
|
||||
{
|
||||
$object = new $job(
|
||||
page(str_replace('+', '/', urldecode(A::get($data, 'contextPage', '')))),
|
||||
str_replace('+S_L_A_S_H+', '/', urldecode(A::get($data, 'contextData', '')))
|
||||
);
|
||||
|
||||
if (method_exists($object, 'job')) {
|
||||
try {
|
||||
set_time_limit(0);
|
||||
} catch (\Exception $ex) {
|
||||
// ignore
|
||||
}
|
||||
return $object->job();
|
||||
}
|
||||
return [
|
||||
'status' => 400,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $msg
|
||||
* @param string $level
|
||||
* @param array $context
|
||||
* @return bool
|
||||
*/
|
||||
public function log(string $msg = '', string $level = 'info', array $context = []): bool
|
||||
{
|
||||
$log = $this->option('log');
|
||||
if ($log && is_callable($log)) {
|
||||
if (!$this->option('debug') && $level == 'debug') {
|
||||
// skip but...
|
||||
return true;
|
||||
} else {
|
||||
return $log($msg, $level, $context);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* @var Janitor
|
||||
*/
|
||||
private static $singleton;
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* @return Janitor
|
||||
*/
|
||||
public static function singleton(array $options = []): Janitor
|
||||
{
|
||||
if (self::$singleton) {
|
||||
return self::$singleton;
|
||||
}
|
||||
|
||||
self::$singleton = new Janitor($options);
|
||||
return self::$singleton;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $template
|
||||
* @param mixed|null $model
|
||||
* @return string
|
||||
*/
|
||||
public static function query(string $template = null, $model = null): string
|
||||
{
|
||||
$page = null;
|
||||
$file = null;
|
||||
$user = kirby()->user();
|
||||
if ($model && $model instanceof Page) {
|
||||
$page = $model;
|
||||
} elseif ($model && $model instanceof File) {
|
||||
$file = $model;
|
||||
} elseif ($model && $model instanceof User) {
|
||||
$user = $model;
|
||||
}
|
||||
return Str::template($template, [
|
||||
'kirby' => kirby(),
|
||||
'site' => kirby()->site(),
|
||||
'page' => $page,
|
||||
'file' => $file,
|
||||
'user' => $user,
|
||||
'model' => $model ? get_class($model) : null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $val
|
||||
* @param bool $return_null
|
||||
* @return bool
|
||||
*/
|
||||
public static function isTrue($val, $return_null = false): bool
|
||||
{
|
||||
$boolval = (is_string($val) ? filter_var($val, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) : (bool) $val);
|
||||
$boolval = ($boolval === null && !$return_null ? false : $boolval);
|
||||
return $boolval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var \League\CLImate\CLImate
|
||||
*/
|
||||
private static $climate;
|
||||
|
||||
/**
|
||||
* @param CLImate|null $climate
|
||||
* @return CLImate|null
|
||||
*/
|
||||
public static function climate(?CLImate $climate = null): ?CLImate
|
||||
{
|
||||
if ($climate && !self::$climate) {
|
||||
self::$climate = $climate;
|
||||
}
|
||||
return self::$climate;
|
||||
}
|
||||
}
|
||||
47
site/plugins/kirby3-janitor/classes/JanitorJob.php
Normal file
47
site/plugins/kirby3-janitor/classes/JanitorJob.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Bnomei;
|
||||
|
||||
use Kirby\Cms\Page;
|
||||
|
||||
abstract class JanitorJob implements Job
|
||||
{
|
||||
/*
|
||||
* @var Page
|
||||
*/
|
||||
private $page;
|
||||
|
||||
/*
|
||||
* @var string
|
||||
*/
|
||||
private $data;
|
||||
|
||||
public function __construct(?Page $page = null, ?string $data = null)
|
||||
{
|
||||
$this->page = $page;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function data(): ?string
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Page|null
|
||||
*/
|
||||
public function page(): ?Page
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
abstract public function job(): array;
|
||||
}
|
||||
18
site/plugins/kirby3-janitor/classes/Job.php
Normal file
18
site/plugins/kirby3-janitor/classes/Job.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Bnomei;
|
||||
|
||||
use Kirby\Cms\Page;
|
||||
|
||||
interface Job
|
||||
{
|
||||
public function __construct(?Page $page = null, ?string $data = null);
|
||||
|
||||
public function data(): ?string;
|
||||
|
||||
public function page(): ?Page;
|
||||
|
||||
public function job(): array;
|
||||
}
|
||||
13
site/plugins/kirby3-janitor/classes/QuietWriter.php
Normal file
13
site/plugins/kirby3-janitor/classes/QuietWriter.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Bnomei;
|
||||
|
||||
use League\CLImate\Util\Writer\WriterInterface;
|
||||
|
||||
class QuietWriter implements WriterInterface
|
||||
{
|
||||
public function write($content)
|
||||
{
|
||||
// be quiet here
|
||||
}
|
||||
}
|
||||
24
site/plugins/kirby3-janitor/classes/ReindexAutoIDJob.php
Normal file
24
site/plugins/kirby3-janitor/classes/ReindexAutoIDJob.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Bnomei;
|
||||
|
||||
final class ReindexAutoIDJob extends JanitorJob
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function job(): array
|
||||
{
|
||||
$success = false;
|
||||
|
||||
if (class_exists('\Bnomei\AutoID')) {
|
||||
$success = \Bnomei\AutoID::index(true) > 0;
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => $success ? 200 : 204,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Bnomei;
|
||||
|
||||
final class ReindexSearchForKirbyJob extends JanitorJob
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function job(): array
|
||||
{
|
||||
if (class_exists('\Kirby\Search\Index')) {
|
||||
try {
|
||||
(new \Kirby\Search\Index())->build();
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'status' => 500,
|
||||
'error' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
return [
|
||||
'status' => 200,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => 204,
|
||||
];
|
||||
}
|
||||
}
|
||||
187
site/plugins/kirby3-janitor/classes/RenderJob.php
Normal file
187
site/plugins/kirby3-janitor/classes/RenderJob.php
Normal file
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Bnomei;
|
||||
|
||||
use Exception;
|
||||
use Kirby\Cms\Media;
|
||||
use Kirby\Cms\Page;
|
||||
use Kirby\Cms\Pages;
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Http\Remote;
|
||||
use Kirby\Toolkit\Query;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
final class RenderJob extends JanitorJob
|
||||
{
|
||||
/**
|
||||
* @var int|void
|
||||
*/
|
||||
private $countPages;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $verbose;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $failed;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $found;
|
||||
private $countLanguages;
|
||||
private $renderSiteUrl;
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $renderTemplate;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function job(): array
|
||||
{
|
||||
$kirby = kirby();
|
||||
$climate = Janitor::climate();
|
||||
$progress = null;
|
||||
$this->verbose = $climate ? $climate->arguments->defined('verbose') : false;
|
||||
$time = time();
|
||||
|
||||
// make sure the thumbs are triggered
|
||||
$kirby->cache('pages')->flush();
|
||||
if (class_exists('\Bnomei\Lapse')) {
|
||||
Lapse::singleton()->flush();
|
||||
}
|
||||
|
||||
// visit all pages to generate media/*.job files
|
||||
$allPages = $this->getAllPagesIDs();
|
||||
$this->countPages = count($allPages);
|
||||
$this->countLanguages = $kirby->languages()->count() > 0 ? $kirby->languages()->count() : 1;
|
||||
$visited = 0;
|
||||
|
||||
if ($climate) {
|
||||
$climate->out('Languages: ' . $this->countLanguages);
|
||||
$climate->out('Pages: ' . $this->countPages);
|
||||
$climate->blue('Rendering Pages...');
|
||||
}
|
||||
if ($this->countPages && $climate) {
|
||||
$progress = $climate->progress()->total($this->countPages);
|
||||
}
|
||||
$this->failed = [];
|
||||
$this->found = [];
|
||||
|
||||
$this->renderSiteUrl = rtrim((string)\option('bnomei.janitor.renderSiteUrl')(), '/');
|
||||
|
||||
foreach ($allPages as $pageId) {
|
||||
try {
|
||||
$content = '';
|
||||
if (strlen($this->renderSiteUrl) > 0) {
|
||||
$content = $this->remoteGetPage($pageId);
|
||||
} else {
|
||||
$content = $this->renderPage($pageId);
|
||||
}
|
||||
if ($this->verbose && strlen($content) > 0) {
|
||||
$this->verboseCheckContent($content);
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
$this->failed[] = $pageId . ': ' . $ex->getMessage();
|
||||
}
|
||||
|
||||
$visited++;
|
||||
if ($progress && $climate) {
|
||||
$progress->current($visited);
|
||||
}
|
||||
}
|
||||
|
||||
$this->found = array_unique($this->found);
|
||||
if ($climate && count($this->found)) {
|
||||
$climate->out('Found images with media/pages/* : ' . count($this->found));
|
||||
}
|
||||
|
||||
if (count($this->failed)) {
|
||||
$climate->out('Rendering failed for Pages: ' . count($this->failed));
|
||||
foreach ($this->failed as $fail) {
|
||||
$climate->red($fail);
|
||||
}
|
||||
}
|
||||
|
||||
$duration = time() - $time;
|
||||
|
||||
return [
|
||||
'status' => $visited > 0 ? 200 : 204,
|
||||
'duration' => $duration,
|
||||
];
|
||||
}
|
||||
|
||||
private function getAllPagesIDs(): array
|
||||
{
|
||||
$ids = [];
|
||||
$allPages = null;
|
||||
if ($this->data()) {
|
||||
$allPages = (new Query(
|
||||
$this->data(),
|
||||
[
|
||||
'kirby' => kirby(),
|
||||
'site' => site(),
|
||||
'page' => $this->page(),
|
||||
]
|
||||
))->result();
|
||||
if (is_a($allPages, Page::class)) {
|
||||
$allPages = new Pages([$allPages]);
|
||||
}
|
||||
foreach ($allPages as $page) {
|
||||
$ids[] = $page->id(); // this should not fully load the page yet
|
||||
}
|
||||
}
|
||||
if (!$allPages) {
|
||||
$finder = new Finder();
|
||||
$finder->directories()
|
||||
->in(kirby()->roots()->content());
|
||||
foreach ($finder as $folder) {
|
||||
$id = $folder->getRelativePathname();
|
||||
if (strpos($id, '_drafts') === false) {
|
||||
$ids[] = ltrim(preg_replace('/\/*\d+_/', '/', $id), '/');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
private function remoteGetPage(string $pageId): string
|
||||
{
|
||||
$content = Remote::get($this->renderSiteUrl . '/' . $pageId)->content();
|
||||
foreach (kirby()->languages() as $lang) {
|
||||
$content .= Remote::get($this->renderSiteUrl . '/' . $lang->code() . '/' . $pageId)->content();
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function verboseCheckContent(string $content)
|
||||
{
|
||||
preg_match_all('~/media/pages/([a-zA-Z0-9-_./]+.(?:png|jpg|jpeg|webp|avif|gif))~', $content, $matches);
|
||||
if ($matches && count($matches) > 1) {
|
||||
$this->found = array_merge($this->found, $matches[1]);
|
||||
}
|
||||
}
|
||||
|
||||
private function renderPage(string $pageId)
|
||||
{
|
||||
$page = page($pageId);
|
||||
$content = '';
|
||||
if ($this->countLanguages > 1) {
|
||||
$content = $page->render();
|
||||
foreach (kirby()->languages() as $lang) {
|
||||
site()->visit($page, $lang->code());
|
||||
$content .= $page->render();
|
||||
}
|
||||
} else {
|
||||
site()->visit($page);
|
||||
$content = $page->render();
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
124
site/plugins/kirby3-janitor/classes/ThumbsJob.php
Normal file
124
site/plugins/kirby3-janitor/classes/ThumbsJob.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Bnomei;
|
||||
|
||||
use Kirby\Cms\Media;
|
||||
use Kirby\Cms\Page;
|
||||
use Kirby\Cms\Pages;
|
||||
use Kirby\Data\Data;
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Http\Remote;
|
||||
use Kirby\Toolkit\A;
|
||||
use Kirby\Toolkit\Query;
|
||||
use Kirby\Toolkit\Str;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
final class ThumbsJob extends JanitorJob
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function job(): array
|
||||
{
|
||||
$climate = Janitor::climate();
|
||||
$progress = null;
|
||||
$verbose = $climate ? $climate->arguments->defined('verbose') : false;
|
||||
$time = time();
|
||||
|
||||
$root = realpath(kirby()->roots()->index() . '/media/') . '/pages';
|
||||
if ($this->page() && $this->data()) {
|
||||
$root = $this->page()->mediaRoot();
|
||||
}
|
||||
Dir::make($root);
|
||||
|
||||
if ($verbose) {
|
||||
$finder = new Finder();
|
||||
$finder->files()
|
||||
->in($root)
|
||||
->name('/\.(?:avif|png|jpg|jpeg|webp|gif)$/');
|
||||
if ($climate) {
|
||||
$climate->out('Thumbs found: ' . iterator_count($finder));
|
||||
}
|
||||
}
|
||||
|
||||
$finder = new Finder();
|
||||
$finder->files()
|
||||
->in($root)
|
||||
->ignoreDotFiles(false)
|
||||
->name('/\.json$/');
|
||||
$countJobs = iterator_count($finder);
|
||||
$jobs = 0;
|
||||
$created = 0;
|
||||
$jobsSkipped = [];
|
||||
if ($climate) {
|
||||
$climate->out('Jobs found: ' . $countJobs);
|
||||
}
|
||||
|
||||
if ($countJobs && $climate) {
|
||||
$climate->blue('Generating Thumbs...');
|
||||
$progress = $climate->progress()->total($countJobs);
|
||||
}
|
||||
|
||||
foreach ($finder as $file) {
|
||||
$jobs++;
|
||||
|
||||
$parentID = null;
|
||||
$page = null;
|
||||
|
||||
$page = null;
|
||||
if (preg_match('/.*\/media\/pages\/(.*)\/.*-[\d]*\/\.jobs/', $file->getPath(), $matches)) {
|
||||
$page = page($matches[1]);
|
||||
}
|
||||
if (!$page) {
|
||||
$jobsSkipped[] = 'Page not found: ' . $parentID;
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = $file->getPath() . '/' . $file->getFilename();
|
||||
$options = Data::read($path);
|
||||
$jobFilename = $file->getFilenameWithoutExtension();
|
||||
$filename = A::get($options, 'filename');
|
||||
|
||||
$pageFile = $page->file($filename);
|
||||
if (!$pageFile) {
|
||||
$jobsSkipped[] = 'File not found: ' . $parentID . '/' . $filename;
|
||||
continue;
|
||||
}
|
||||
|
||||
$hash = basename(str_replace('/.jobs', '', $file->getPath()));
|
||||
|
||||
if (Media::link($page, $hash, $jobFilename) !== false) {
|
||||
$created++;
|
||||
}
|
||||
|
||||
if ($progress && $climate) {
|
||||
$progress->current($jobs);
|
||||
}
|
||||
}
|
||||
|
||||
$duration = time() - $time;
|
||||
if ($climate) {
|
||||
$climate->out('Thumbs created: ' . $created);
|
||||
if ($jobsSkipped) {
|
||||
$climate->out('Jobs executed: ' . $jobs);
|
||||
$climate->out('Jobs skipped: ' . count($jobsSkipped));
|
||||
foreach ($jobsSkipped as $skip) {
|
||||
$climate->red($skip);
|
||||
}
|
||||
}
|
||||
$climate->out('Duration in seconds: ' . strval($duration));
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => $created > 0 ? 200 : 204,
|
||||
'duration' => $duration,
|
||||
'thumbs' => [
|
||||
'jobs' => $countJobs,
|
||||
'created' => $created,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
19
site/plugins/kirby3-janitor/classes/WhistleJob.php
Normal file
19
site/plugins/kirby3-janitor/classes/WhistleJob.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Bnomei;
|
||||
|
||||
final class WhistleJob extends JanitorJob
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function job(): array
|
||||
{
|
||||
return [
|
||||
'status' => 200,
|
||||
'label' => '♫',
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user