adding kirby3-janitor
This commit is contained in:
25
site/plugins/kirby3-janitor/.editorconfig
Normal file
25
site/plugins/kirby3-janitor/.editorconfig
Normal file
@@ -0,0 +1,25 @@
|
||||
[*.{css,scss,less,js,json,ts,sass,html,hbs,mustache,phtml,html.twig,md,yml}]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md, *.txt]
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[site/templates/**.php]
|
||||
indent_size = 2
|
||||
|
||||
[site/snippets/**.php]
|
||||
indent_size = 2
|
||||
|
||||
[package.json,.{babelrc,editorconfig,eslintrc,lintstagedrc,stylelintrc}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[composer.json]
|
||||
indent_size = 4
|
||||
18
site/plugins/kirby3-janitor/.eslintrc.json
Normal file
18
site/plugins/kirby3-janitor/.eslintrc.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:vue/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"vue/require-default-prop": "off"
|
||||
}
|
||||
}
|
||||
19
site/plugins/kirby3-janitor/.php-cs-fixer.dist.php
Normal file
19
site/plugins/kirby3-janitor/.php-cs-fixer.dist.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
|
||||
$finder = PhpCsFixer\Finder::create()
|
||||
->exclude('content')
|
||||
->exclude('kirby')
|
||||
->exclude('node_modules')
|
||||
//->exclude('site/plugins')
|
||||
->exclude('src')
|
||||
->exclude('vendor')
|
||||
->in(__DIR__)
|
||||
;
|
||||
|
||||
return (new PhpCsFixer\Config())
|
||||
->setRules([
|
||||
'@PSR12' => true,
|
||||
])
|
||||
->setFinder($finder)
|
||||
;
|
||||
21
site/plugins/kirby3-janitor/LICENSE
Normal file
21
site/plugins/kirby3-janitor/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Bruno Meilick
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
67
site/plugins/kirby3-janitor/README.md
Normal file
67
site/plugins/kirby3-janitor/README.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Kirby 3 Janitor
|
||||
|
||||

|
||||

|
||||
[](https://travis-ci.com/bnomei/kirby3-janitor)
|
||||
[](https://coveralls.io/github/bnomei/kirby3-janitor)
|
||||
[](https://codeclimate.com/github/bnomei/kirby3-janitor)
|
||||
[](https://twitter.com/bnomei)
|
||||
|
||||
Kirby 3 Plugin for running jobs.
|
||||
|
||||
- It is a Panel Button!
|
||||
- It has jobs build-in for cleaning the cache, sessions, create zip-backup, pre-generate thumbs, open URLs, refresh the current Panel page and more.
|
||||
- You can define your own jobs (call API hooks, play a game, hack a server, ...)
|
||||
- It can be triggered in your frontend code and with CRON.
|
||||
- It can also be used as a CLI with fancy output.
|
||||
- It can also create logs of what it did.
|
||||
|
||||
## Install
|
||||
|
||||
Using composer:
|
||||
|
||||
```bash
|
||||
composer require bnomei/kirby3-janitor
|
||||
```
|
||||
|
||||
Using git submodules:
|
||||
|
||||
```bash
|
||||
git submodule add https://github.com/bnomei/kirby3-janitor.git site/plugins/kirby3-janitor
|
||||
```
|
||||
|
||||
Using download & copy: download [the latest release](https://github.com/bnomei/kirby3-janitor/releases) and copy to `site/plugins`
|
||||
|
||||
## Commerical Usage
|
||||
|
||||
> <br>
|
||||
><b>Support open source!</b><br><br>
|
||||
> This plugin is free but if you use it in a commercial project please consider to sponsor me or make a donation.<br>
|
||||
> If my work helped you to make some cash it seems fair to me that I might get a little reward as well, right?<br><br>
|
||||
> Be kind. Share a little. Thanks.<br><br>
|
||||
> ‐ Bruno<br>
|
||||
>
|
||||
|
||||
| M | O | N | E | Y |
|
||||
|---|----|---|---|---|
|
||||
| [Github sponsor](https://github.com/sponsors/bnomei) | [Patreon](https://patreon.com/bnomei) | [Buy Me a Coffee](https://buymeacoff.ee/bnomei) | [Paypal dontation](https://www.paypal.me/bnomei/15) | [Hire me](mailto:b@bnomei.com?subject=Kirby) |
|
||||
|
||||
## Wiki
|
||||
|
||||
Continue to the [Janitor Wiki](https://github.com/bnomei/kirby3-janitor/wiki) to read more on how to install, setup and use this plugin.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- [Symfony Finder](https://symfony.com/doc/current/components/finder.html)
|
||||
- [CLIMate](https://github.com/thephpleague/climate)
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This plugin is provided "as is" with no guarantee. Use it at your own risk and always test it yourself before using it in a production environment. If you find any issues, please [create a new issue](https://github.com/bnomei/kirby3-janitor/issues/new).
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://opensource.org/licenses/MIT)
|
||||
|
||||
It is discouraged to use this plugin in any project that promotes racism, sexism, homophobia, animal abuse, violence or any other form of hate speech.
|
||||
|
||||
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' => '♫',
|
||||
];
|
||||
}
|
||||
}
|
||||
75
site/plugins/kirby3-janitor/composer.json
Normal file
75
site/plugins/kirby3-janitor/composer.json
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"name": "bnomei/kirby3-janitor",
|
||||
"type": "kirby-plugin",
|
||||
"version": "2.16.0",
|
||||
"license": "MIT",
|
||||
"description": "Kirby 3 Plugin for running jobs like cleaning the cache from within the Panel, PHP code or a cronjob",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Bruno Meilick",
|
||||
"email": "b@bnomei.com"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"kirby3",
|
||||
"kirby3-cms",
|
||||
"kirby3-plugin",
|
||||
"cache",
|
||||
"clean",
|
||||
"janitor",
|
||||
"job-runner",
|
||||
"cronjob",
|
||||
"ajax",
|
||||
"button"
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Bnomei\\": "classes/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"getkirby/composer-installer": true
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.4.0",
|
||||
"getkirby/composer-installer": "^1.2",
|
||||
"league/climate": "^3.7",
|
||||
"symfony/deprecation-contracts": "2.5",
|
||||
"symfony/finder": "^5.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"getkirby/cms": "^3.5",
|
||||
"php-coveralls/php-coveralls": "^2.4",
|
||||
"phpunit/phpunit": "^9.5"
|
||||
},
|
||||
"scripts": {
|
||||
"build": [
|
||||
"yarn",
|
||||
"yarn run build"
|
||||
],
|
||||
"analyze": "phpstan analyse classes",
|
||||
"fix": "php-cs-fixer fix",
|
||||
"test": [
|
||||
"mkdir -p tests/logs",
|
||||
"@putenv XDEBUG_MODE=coverage",
|
||||
"phpunit --configuration ./phpunit.xml"
|
||||
],
|
||||
"dist": [
|
||||
"composer install --no-dev --optimize-autoloader",
|
||||
"git rm -rf --cached .; git add .;"
|
||||
],
|
||||
"kirby": [
|
||||
"composer install",
|
||||
"composer update",
|
||||
"composer install --working-dir=tests/kirby --no-dev --optimize-autoloader",
|
||||
"composer update --working-dir=tests/kirby"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"kirby-cms-path": "tests/kirby"
|
||||
}
|
||||
}
|
||||
4619
site/plugins/kirby3-janitor/composer.lock
generated
Normal file
4619
site/plugins/kirby3-janitor/composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
12
site/plugins/kirby3-janitor/docker-compose.yml
Normal file
12
site/plugins/kirby3-janitor/docker-compose.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
webserver:
|
||||
image: webdevops/php-apache:7.4
|
||||
ports:
|
||||
- 8000:80
|
||||
environment:
|
||||
WEB_DOCUMENT_ROOT: /app/tests/
|
||||
WEB_ALIAS_DOMAIN: janitor.test
|
||||
volumes:
|
||||
- .:/app:rw
|
||||
1
site/plugins/kirby3-janitor/index.css
Normal file
1
site/plugins/kirby3-janitor/index.css
Normal file
@@ -0,0 +1 @@
|
||||
.janitor{background-color:var(--color-text);color:#fff;border-radius:3px;padding:.5rem 1rem;line-height:1.25rem;text-align:left}.janitor:hover{background-color:#222}.janitor .k-button-text{opacity:1}.janitor.is-running{background-color:var(--color-border)}.janitor.is-running .k-button-text{color:var(--color-text)}.janitor.has-response{background-color:var(--color-text)}.janitor.is-success{background-color:var(--color-positive)}.janitor.has-error{background-color:var(--color-negative-light)}.visually-hidden{position:absolute;width:1px;height:1px;border:0;padding:0;margin:0;clip-path:inset(50%);overflow:hidden;white-space:nowrap}
|
||||
1
site/plugins/kirby3-janitor/index.js
Normal file
1
site/plugins/kirby3-janitor/index.js
Normal file
@@ -0,0 +1 @@
|
||||
(()=>{(function(){"use strict";var p=function(){var t=this,e=t.$createElement,i=t._self._c||e;return i("div",{staticClass:"janitor-wrapper"},[i("k-button",{class:["janitor",t.button.state],attrs:{id:t.id,icon:t.currentIcon,job:t.job,disabled:!t.isUnsaved&&t.hasChanges},on:{click:t.runJanitor}},[t._v(" "+t._s(t.button.label||t.label)+" ")]),i("a",{directives:[{name:"show",rawName:"v-show",value:t.downloadRequest,expression:"downloadRequest"}],ref:"downloadAnchor",staticClass:"visually-hidden",attrs:{href:t.downloadRequest,download:""}}),i("a",{directives:[{name:"show",rawName:"v-show",value:t.urlRequest,expression:"urlRequest"}],ref:"tabAnchor",staticClass:"visually-hidden",attrs:{href:t.urlRequest,target:"_blank"}})],1)},v=[],q="";function g(t,e,i,c,o,r,l,h){var s=typeof t=="function"?t.options:t;e&&(s.render=e,s.staticRenderFns=i,s._compiled=!0),c&&(s.functional=!0),r&&(s._scopeId="data-v-"+r);var a;if(l?(a=function(n){n=n||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,!n&&typeof __VUE_SSR_CONTEXT__!="undefined"&&(n=__VUE_SSR_CONTEXT__),o&&o.call(this,n),n&&n._registeredComponents&&n._registeredComponents.add(l)},s._ssrRegister=a):o&&(a=h?function(){o.call(this,(s.functional?this.parent:this).$root.$options.shadowRoot)}:o),a)if(s.functional){s._injectStyles=a;var R=s.render;s.render=function(k,b){return a.call(b),R(k,b)}}else{var f=s.beforeCreate;s.beforeCreate=f?[].concat(f,a):[a]}return{exports:t,options:s}}const u="janitor.runAfterAutosave",m={props:{label:String,progress:String,job:String,cooldown:Number,status:String,data:String,pageURI:String,clipboard:Boolean,unsaved:Boolean,autosave:Boolean,intab:Boolean,confirm:String,icon:{type:[Boolean,String],default:!1}},data(){return{button:{label:null,state:null},downloadRequest:null,clipboardRequest:null,urlRequest:null,isUnsaved:!1,icons:{"is-running":"janitorLoader","is-success":"check","has-error":"alert"}}},computed:{id(){var t;return"janitor-"+this.hashCode(this.job+((t=this.button.label)!=null?t:"")+this.pageURI)},hasChanges(){return this.$store.getters["content/hasChanges"]()},currentIcon(){var t;return(t=this.icons[this.status])!=null?t:this.icon}},created(){this.$events.$on("model.update",()=>sessionStorage.getItem(u)&&location.reload()),sessionStorage.getItem(u)===this.id&&(sessionStorage.removeItem(u),this.runJanitor())},methods:{hashCode(t){let e=0;if(t.length===0)return e;for(const i of t)e=(e<<5)-e+t.charCodeAt(i),e=e&e;return e},async runJanitor(){if(this.confirm&&!window.confirm(this.confirm))return;if(this.autosave&&this.hasChanges){const e=document.querySelector(".k-panel .k-form-buttons .k-view").lastChild;if(e){this.isUnsaved=!1,sessionStorage.setItem(u,this.id),this.simulateClick(e);return}}if(this.clipboard){this.clipboardRequest=this.data,this.button.label=this.progress,this.button.state="is-success",setTimeout(this.resetButton,this.cooldown),this.$nextTick(()=>{this.copyToClipboard(this.data)});return}if(this.clipboardRequest){await this.copyToClipboard(this.clipboardRequest),this.resetButton(),this.clipboardRequest=null;return}if(this.status)return;let t=this.job+"/"+encodeURIComponent(this.pageURI);this.data&&(t=t+"/"+encodeURIComponent(this.data)),this.getRequest(t)},async getRequest(t){var h;this.button.label=(h=this.progress)!=null?h:`${this.label} \u2026`,this.button.state="is-running";const{label:e,status:i,reload:c,href:o,download:r,clipboard:l}=await this.$api.get(t);e&&(this.button.label=e),i?this.button.state=i===200?"is-success":"has-error":this.button.state="has-response",c&&location.reload(),o&&(this.intab?(this.urlRequest=o,this.$nextTick(()=>{this.simulateClick(this.$refs.tabAnchor)})):location.href=o),r&&(this.downloadRequest=r,this.$nextTick(()=>{this.simulateClick(this.$refs.downloadAnchor)})),l?this.clipboardRequest=l:setTimeout(this.resetButton,this.cooldown)},resetButton(){this.button.label=null,this.button.state=null},simulateClick(t){const e=new MouseEvent("click",{bubbles:!0,cancelable:!0,view:window});t.dispatchEvent(e)},async copyToClipboard(t){try{await navigator.clipboard.writeText(t)}catch{console.error("navigator.clipboard is not available")}}}},d={};var _=g(m,p,v,!1,w,null,null,null);function w(t){for(let e in d)this[e]=d[e]}var C=function(){return _.exports}();window.panel.plugin("bnomei/janitor",{fields:{janitor:C},icons:{janitorLoader:'<g fill="none" fill-rule="evenodd"><g transform="translate(1 1)" stroke-width="1.75"><circle cx="7" cy="7" r="7.2" stroke="#000" stroke-opacity=".2"/><path d="M14.2,7c0-4-3.2-7.2-7.2-7.2" stroke="#000"><animateTransform attributeName="transform" type="rotate" from="0 7 7" to="360 7 7" dur="1s" repeatCount="indefinite"/></path></g></g>'}})})();})();
|
||||
210
site/plugins/kirby3-janitor/index.php
Normal file
210
site/plugins/kirby3-janitor/index.php
Normal file
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
@include_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
/*
|
||||
janitor [noun]
|
||||
one who keeps the premises of a building (such as an apartment or
|
||||
office) clean, tends the heating system, and makes minor repairs
|
||||
*/
|
||||
|
||||
Kirby::plugin('bnomei/janitor', [
|
||||
'options' => [
|
||||
'jobs' => [],
|
||||
'jobs-defaults' => [
|
||||
'clean' => 'Bnomei\\CleanCacheFilesJob', // legacy
|
||||
'cleanCache' => 'Bnomei\\CleanCacheFilesJob',
|
||||
'flush' => 'Bnomei\\FlushPagesCacheJob', // legacy
|
||||
'flushPages' => 'Bnomei\\FlushPagesCacheJob',
|
||||
'cleanSessions' => 'Bnomei\\CleanSessionsJob',
|
||||
'cleanContent' => 'Bnomei\\CleanContentJob',
|
||||
'flushSessions' => 'Bnomei\\FlushSessionFilesJob',
|
||||
'flushLapse' => 'Bnomei\\FlushLapseJob',
|
||||
'flushRedisDB' => 'Bnomei\\FlushRedisDBJob',
|
||||
'reindexAutoID' => 'Bnomei\\ReindexAutoIDJob',
|
||||
'reindexSearch' => 'Bnomei\\ReindexSearchForKirbyJob',
|
||||
'backupZip' => 'Bnomei\\BackupZipJob',
|
||||
'render' => 'Bnomei\\RenderJob',
|
||||
'thumbs' => 'Bnomei\\ThumbsJob',
|
||||
],
|
||||
'jobs-extends' => [
|
||||
'bnomei.lapse.jobs', // https://github.com/bnomei/kirby3-lapse/blob/master/index.php#L10
|
||||
],
|
||||
|
||||
'label.cooldown' => 2000, // ms
|
||||
'secret' => null,
|
||||
|
||||
'thumbsOnUpload' => false,
|
||||
|
||||
'renderSiteUrl' => function () {
|
||||
$url = site()->url();
|
||||
// $url = 'https://www.example.com/';
|
||||
return php_sapi_name() === 'cli' ? $url : '';
|
||||
},
|
||||
|
||||
'log.enabled' => false,
|
||||
'log.fn' => function (string $msg, string $level = 'info', array $context = []): bool {
|
||||
if (option('bnomei.janitor.log.enabled')) {
|
||||
if (function_exists('monolog')) {
|
||||
monolog()->{$level}($msg, $context);
|
||||
} elseif (function_exists('kirbyLog')) {
|
||||
kirbyLog('bnomei.janitor.log')->log($msg, $level, $context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
'icon' => false,
|
||||
],
|
||||
'snippets' => [
|
||||
'maintenance' => __DIR__ . '/snippets/maintenance.php',
|
||||
],
|
||||
'fields' => [
|
||||
'janitor' => [
|
||||
'props' => [
|
||||
'label' => function ($label = null) {
|
||||
return \Kirby\Toolkit\I18n::translate($label, $label);
|
||||
},
|
||||
'progress' => function ($progress = null) {
|
||||
return \Kirby\Toolkit\I18n::translate($progress, $progress);
|
||||
},
|
||||
'job' => function (?string $job = null) {
|
||||
return 'plugin-janitor/' . $job;
|
||||
},
|
||||
'cooldown' => function (int $cooldownMilliseconds = 2000) {
|
||||
return intval(option('bnomei.janitor.label.cooldown', $cooldownMilliseconds));
|
||||
},
|
||||
'data' => function (?string $data = null) {
|
||||
$data = \Bnomei\Janitor::query($data, $this->model());
|
||||
return str_replace(
|
||||
'/',
|
||||
'+S_L_A_S_H+',
|
||||
\Kirby\Toolkit\I18n::translate($data, $data)
|
||||
);
|
||||
},
|
||||
'clipboard' => function ($clipboard = null) {
|
||||
return \Bnomei\Janitor::isTrue($clipboard);
|
||||
},
|
||||
'unsaved' => function ($allowUnsaved = true) {
|
||||
return \Bnomei\Janitor::isTrue($allowUnsaved);
|
||||
},
|
||||
'autosave' => function ($doAutosave = false) {
|
||||
return \Bnomei\Janitor::isTrue($doAutosave);
|
||||
},
|
||||
'intab' => function ($intab = false) {
|
||||
return \Bnomei\Janitor::isTrue($intab);
|
||||
},
|
||||
'confirm' => function ($confirm = '') {
|
||||
return $confirm;
|
||||
},
|
||||
'pageURI' => function () {
|
||||
$uri = kirby()->site()->homePageId();
|
||||
if (is_a($this->model(), \Kirby\Cms\Page::class)) {
|
||||
$uri = $this->model()->uri();
|
||||
}
|
||||
if (is_a($this->model(), \Kirby\Cms\File::class)) {
|
||||
$uri = $this->model()->parent()->uri();
|
||||
}
|
||||
if (is_a($this->model(), \Kirby\Cms\User::class)) {
|
||||
$uri = $this->model()->panelPath();
|
||||
}
|
||||
if (is_a($this->model(), \Kirby\Cms\Site::class)) {
|
||||
$uri = '$'; // any not empty string so route /$/DATA is used
|
||||
}
|
||||
return str_replace('/', '+', $uri);
|
||||
},
|
||||
'icon' => function ($icon = false) {
|
||||
return $icon ?? option('bnomei.janitor.icon');
|
||||
},
|
||||
],
|
||||
],
|
||||
],
|
||||
'routes' => [
|
||||
[
|
||||
'pattern' => 'plugin-janitor/(:any)/(:any)',
|
||||
'action' => function (string $job, string $secret) {
|
||||
$janitor = new \Bnomei\Janitor();
|
||||
$janitor->log('janitor-api-secret', 'debug');
|
||||
$response = $janitor->jobWithSecret($secret, $job);
|
||||
return Kirby\Http\Response::json($response, A::get($response, 'status', 400));
|
||||
},
|
||||
],
|
||||
],
|
||||
'hooks' => [
|
||||
'file.create:after' => function ($file) {
|
||||
if (option('bnomei.janitor.thumbsOnUpload') && $file->isResizable()) {
|
||||
janitor('render', $file->page(), 'page');
|
||||
janitor('thumbs', $file->page(), 'page');
|
||||
}
|
||||
},
|
||||
'route:before' => function () {
|
||||
$isPanel = strpos(
|
||||
kirby()->request()->url()->toString(),
|
||||
kirby()->urls()->panel()
|
||||
) !== false;
|
||||
$isApi = strpos(
|
||||
kirby()->request()->url()->toString(),
|
||||
kirby()->urls()->api()
|
||||
) !== false;
|
||||
if (!$isPanel && !$isApi) {
|
||||
if (F::exists(kirby()->roots()->index() . '/down')) {
|
||||
snippet('maintenance');
|
||||
die;
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
'api' => [
|
||||
'routes' => [
|
||||
[
|
||||
'pattern' => 'plugin-janitor/(:any)/(:any)/(:any)',
|
||||
'action' => function (string $job, string $page, string $data) {
|
||||
$janitor = \Bnomei\Janitor::singleton();
|
||||
$janitor->log('janitor-api-auth', 'debug');
|
||||
return $janitor->job($job, [
|
||||
'contextPage' => $page,
|
||||
'contextData' => $data,
|
||||
]);
|
||||
},
|
||||
],
|
||||
[
|
||||
'pattern' => 'plugin-janitor/(:any)/(:any)',
|
||||
'action' => function (string $job, string $page) {
|
||||
$janitor = \Bnomei\Janitor::singleton();
|
||||
$janitor->log('janitor-api-auth', 'debug');
|
||||
return $janitor->job($job, [
|
||||
'contextPage' => $page,
|
||||
]);
|
||||
},
|
||||
],
|
||||
[
|
||||
'pattern' => 'plugin-janitor/(:any)',
|
||||
'action' => function (string $job) {
|
||||
$janitor = \Bnomei\Janitor::singleton();
|
||||
$janitor->log('janitor-api-auth', 'debug');
|
||||
return $janitor->job($job);
|
||||
}
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
if (!class_exists('Bnomei\Janitor')) {
|
||||
require_once __DIR__ . '/classes/Janitor.php';
|
||||
}
|
||||
|
||||
if (!function_exists('janitor')) {
|
||||
function janitor(string $job, ?\Kirby\Cms\Page $contextPage = null, ?string $contextData = null, bool $dump = false)
|
||||
{
|
||||
$janitor = \Bnomei\Janitor::singleton();
|
||||
$janitor->log('janitor()', 'debug');
|
||||
$response = $janitor->job($job, [
|
||||
'contextPage' => $contextPage ? urlencode(str_replace('/', '+', $contextPage->uri())) : '',
|
||||
'contextData' => $contextData ? urlencode($contextData) : '',
|
||||
]);
|
||||
if ($dump) {
|
||||
return $response;
|
||||
}
|
||||
return intval(A::get($response, 'status')) === 200;
|
||||
}
|
||||
}
|
||||
190
site/plugins/kirby3-janitor/janitor
Executable file
190
site/plugins/kirby3-janitor/janitor
Executable file
@@ -0,0 +1,190 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
foreach([// janitor from zip with vendor
|
||||
__DIR__ . '/vendor/autoload.php',
|
||||
// janitor from composer in site/plugins/kirby3-janitor
|
||||
realpath(__DIR__ . '/../../../') . '/vendor/autoload.php',
|
||||
] as $file) {
|
||||
if (file_exists($file)) {
|
||||
require $file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ini_set('display_errors', '1');
|
||||
|
||||
$climate = new \League\CLImate\CLImate();
|
||||
|
||||
$climate->arguments->add([
|
||||
'help' => [
|
||||
'prefix' => 'h',
|
||||
'longPrefix' => 'help',
|
||||
'description' => 'Display the help',
|
||||
'noValue' => true,
|
||||
],
|
||||
'list' => [
|
||||
'prefix' => 'l',
|
||||
'longPrefix' => 'list',
|
||||
'description' => 'List all registered jobs',
|
||||
'noValue' => true,
|
||||
],
|
||||
'format' => [
|
||||
'prefix' => 'f',
|
||||
'longPrefix' => 'format',
|
||||
'description' => 'Format the output as "label", "table" or "json"',
|
||||
'defaultValue' => 'label',
|
||||
],
|
||||
'kirby' => [
|
||||
'prefix' => 'k',
|
||||
'longPrefix' => 'kirby',
|
||||
'description' => 'Relative path to Kirbys public folder',
|
||||
'defaultValue' => '/',
|
||||
],
|
||||
'verbose' => [
|
||||
'prefix' => 'v',
|
||||
'longPrefix' => 'verbose',
|
||||
'description' => 'Show debug info',
|
||||
'noValue' => true,
|
||||
],
|
||||
'job' => [
|
||||
'description' => 'Run a job',
|
||||
],
|
||||
'quiet' => [
|
||||
'prefix' => 'q',
|
||||
'longPrefix' => 'quiet',
|
||||
'description' => 'Run jobs silently',
|
||||
'noValue' => true,
|
||||
],
|
||||
'tinker' => [
|
||||
'prefix' => 't',
|
||||
'longPrefix' => 'tinker',
|
||||
'description' => 'Run a REPL session',
|
||||
'noValue' => true,
|
||||
],
|
||||
'down' => [
|
||||
'prefix' => 'd',
|
||||
'longPrefix' => 'down',
|
||||
'description' => 'Start maintenance mode',
|
||||
'noValue' => true,
|
||||
],
|
||||
'up' => [
|
||||
'prefix' => 'u',
|
||||
'longPrefix' => 'up',
|
||||
'description' => 'Stop maintenance mode',
|
||||
'noValue' => true,
|
||||
],
|
||||
]);
|
||||
$climate->arguments->parse();
|
||||
$verbose = $climate->arguments->defined('verbose');
|
||||
|
||||
// COMMAND: kirby
|
||||
$kirbyPublicFolder = null;
|
||||
foreach([ // site/plugins/kirby3-janitor => /index.php
|
||||
realpath(__DIR__ . '/../../../'),
|
||||
// site/plugins/kirby3-janitor => /public/index.php
|
||||
realpath(__DIR__ . '/../../../public'),
|
||||
] as $dir) {
|
||||
if ($dir !== false && file_exists($dir . '/index.php')) {
|
||||
$kirbyPublicFolder = $dir;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($climate->arguments->defined('kirby')) {
|
||||
$kirbyPublicFolder = realpath(__DIR__ . DIRECTORY_SEPARATOR . ltrim($climate->arguments->get('kirby'), DIRECTORY_SEPARATOR));
|
||||
}
|
||||
if ($kirbyPublicFolder) {
|
||||
$kirbyLoader = $kirbyPublicFolder . DIRECTORY_SEPARATOR . 'janitor-' . sha1(__DIR__) . '.php';
|
||||
if (! file_exists($kirbyLoader)) {
|
||||
file_put_contents(
|
||||
$kirbyLoader,
|
||||
str_replace('echo ', '// echo ',
|
||||
file_get_contents($kirbyPublicFolder . DIRECTORY_SEPARATOR . 'index.php')
|
||||
)
|
||||
);
|
||||
if ($verbose) {
|
||||
$climate->backgroundYellow()->out('ATTENTION! Janitor created this file to load the Kirby instance: ' . $kirbyLoader);
|
||||
}
|
||||
}
|
||||
include $kirbyLoader;
|
||||
if ($verbose) {
|
||||
$climate->backgroundCyan()->out('Using Kirby instance from: ' . $kirbyLoader);
|
||||
}
|
||||
}
|
||||
|
||||
\Bnomei\Janitor::climate($climate);
|
||||
$janitor = new \Bnomei\Janitor();
|
||||
|
||||
// COMMAND: help
|
||||
$command = $climate->arguments->defined('help');
|
||||
if ($command) {
|
||||
$climate->usage();
|
||||
}
|
||||
|
||||
// COMMAND: list
|
||||
$command = $climate->arguments->defined('list');
|
||||
if ($command) {
|
||||
foreach ($janitor->listJobs() as $key) {
|
||||
$climate->out($key);
|
||||
}
|
||||
}
|
||||
|
||||
// COMMAND: quiet
|
||||
$command = $climate->arguments->get('quiet');
|
||||
if ($command) {
|
||||
$climate->output->add('quiet', new \Bnomei\QuietWriter());
|
||||
$climate->output->defaultTo('quiet');
|
||||
}
|
||||
|
||||
// COMMAND: tinker
|
||||
$command = $climate->arguments->get('tinker');
|
||||
if ($command) {
|
||||
while (true) eval($climate->input('>>> ')->prompt());
|
||||
}
|
||||
|
||||
// COMMAND: down
|
||||
$command = $climate->arguments->get('down');
|
||||
if ($command) {
|
||||
file_put_contents(kirby()->roots()->index() . '/down', date('c'));
|
||||
}
|
||||
|
||||
// COMMAND: up
|
||||
$command = $climate->arguments->get('up');
|
||||
if ($command) {
|
||||
$down = kirby()->roots()->index() . '/down';
|
||||
if (file_exists($down)) {
|
||||
unlink($down);
|
||||
}
|
||||
}
|
||||
|
||||
// COMMAND: job
|
||||
$job = $climate->arguments->get('job');
|
||||
if ($job) {
|
||||
$format = $climate->arguments->get('format');
|
||||
if ($format === 'class') {
|
||||
$janitor->job($job, [], $climate);
|
||||
} elseif ($format === 'json') {
|
||||
$climate->json($janitor->job($job));
|
||||
} elseif ($format === 'table') {
|
||||
$table = [];
|
||||
foreach ($janitor->job($job) as $key => $value) {
|
||||
$table[] = [$key, $value];
|
||||
}
|
||||
$climate->table($table);
|
||||
} else {
|
||||
$data = $janitor->job($job);
|
||||
$status = $data['status'];
|
||||
$label = array_key_exists('label', $data) ? $data['label'] : $status;
|
||||
if ($status === 200) {
|
||||
$climate->lightGreen($label);
|
||||
} elseif ($status === 204) {
|
||||
$climate->lightRed($label);
|
||||
} elseif ($status === 404) {
|
||||
$climate->backgroundRed($label);
|
||||
} else {
|
||||
$climate->out($label);
|
||||
}
|
||||
}
|
||||
}
|
||||
6789
site/plugins/kirby3-janitor/package-lock.json
generated
Normal file
6789
site/plugins/kirby3-janitor/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
site/plugins/kirby3-janitor/package.json
Executable file
16
site/plugins/kirby3-janitor/package.json
Executable file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "kirbyup src/index.js --watch",
|
||||
"build": "kirbyup src/index.js",
|
||||
"lint": "eslint \"src/**/*.{js,vue}\"",
|
||||
"lint:fix": "npm run lint -- --fix",
|
||||
"format": "prettier --write \"src/**/*.{js,vue}\""
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.0.1",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-vue": "^7.19.1",
|
||||
"kirbyup": "^0.18.0"
|
||||
}
|
||||
}
|
||||
41
site/plugins/kirby3-janitor/snippets/maintenance.php
Normal file
41
site/plugins/kirby3-janitor/snippets/maintenance.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Maintenance</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
padding: 10%;
|
||||
text-align: center;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
a:hover,
|
||||
a:focus {
|
||||
color: #000;
|
||||
}
|
||||
p {
|
||||
max-width: 30em;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.notice {
|
||||
font-weight: bold;
|
||||
}
|
||||
.admin-advice {
|
||||
font-size: .8em;
|
||||
font-style: italic;
|
||||
color: #999;
|
||||
padding-top: 3rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p class="notice">
|
||||
This page is currently in maintenance.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
296
site/plugins/kirby3-janitor/src/components/fields/Janitor.vue
Executable file
296
site/plugins/kirby3-janitor/src/components/fields/Janitor.vue
Executable file
@@ -0,0 +1,296 @@
|
||||
<template>
|
||||
<div class="janitor-wrapper">
|
||||
<k-button
|
||||
:id="id"
|
||||
:class="['janitor', button.state]"
|
||||
:icon="currentIcon"
|
||||
:job="job"
|
||||
:disabled="!isUnsaved && hasChanges"
|
||||
@click="runJanitor"
|
||||
>
|
||||
{{ button.label || label }}
|
||||
</k-button>
|
||||
|
||||
<a
|
||||
v-show="downloadRequest"
|
||||
ref="downloadAnchor"
|
||||
class="visually-hidden"
|
||||
:href="downloadRequest"
|
||||
download
|
||||
/>
|
||||
<a
|
||||
v-show="urlRequest"
|
||||
ref="tabAnchor"
|
||||
class="visually-hidden"
|
||||
:href="urlRequest"
|
||||
target="_blank"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const STORAGE_ID = "janitor.runAfterAutosave";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
label: String,
|
||||
progress: String,
|
||||
job: String,
|
||||
cooldown: Number,
|
||||
status: String,
|
||||
data: String,
|
||||
pageURI: String,
|
||||
clipboard: Boolean,
|
||||
unsaved: Boolean,
|
||||
autosave: Boolean,
|
||||
intab: Boolean,
|
||||
confirm: String,
|
||||
icon: {
|
||||
type: [Boolean, String],
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
button: {
|
||||
label: null,
|
||||
state: null,
|
||||
},
|
||||
downloadRequest: null,
|
||||
clipboardRequest: null,
|
||||
urlRequest: null,
|
||||
isUnsaved: false,
|
||||
icons: {
|
||||
"is-running": "janitorLoader",
|
||||
"is-success": "check",
|
||||
"has-error": "alert",
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
id() {
|
||||
return (
|
||||
"janitor-" +
|
||||
this.hashCode(this.job + (this.button.label ?? "") + this.pageURI)
|
||||
);
|
||||
},
|
||||
|
||||
hasChanges() {
|
||||
return this.$store.getters["content/hasChanges"]();
|
||||
},
|
||||
|
||||
currentIcon() {
|
||||
return this.icons[this.status] ?? this.icon;
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$events.$on(
|
||||
"model.update",
|
||||
() => sessionStorage.getItem(STORAGE_ID) && location.reload()
|
||||
);
|
||||
|
||||
if (sessionStorage.getItem(STORAGE_ID) === this.id) {
|
||||
sessionStorage.removeItem(STORAGE_ID);
|
||||
this.runJanitor();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Source: https://stackoverflow.com/a/8831937
|
||||
*/
|
||||
hashCode(str) {
|
||||
let hash = 0;
|
||||
|
||||
if (str.length === 0) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
for (const i of str) {
|
||||
hash = (hash << 5) - hash + str.charCodeAt(i);
|
||||
// convert to 32bit integer
|
||||
hash = hash & hash;
|
||||
}
|
||||
|
||||
return hash;
|
||||
},
|
||||
|
||||
async runJanitor() {
|
||||
if (this.confirm && !window.confirm(this.confirm)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.autosave && this.hasChanges) {
|
||||
// lock janitor button, press save and listen to `model.update` event
|
||||
const saveButton = document.querySelector(
|
||||
".k-panel .k-form-buttons .k-view"
|
||||
).lastChild;
|
||||
|
||||
// revert & save
|
||||
if (saveButton) {
|
||||
this.isUnsaved = false;
|
||||
sessionStorage.setItem(STORAGE_ID, this.id);
|
||||
this.simulateClick(saveButton);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.clipboard) {
|
||||
this.clipboardRequest = this.data;
|
||||
this.button.label = this.progress;
|
||||
this.button.state = "is-success";
|
||||
|
||||
setTimeout(this.resetButton, this.cooldown);
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.copyToClipboard(this.data);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.clipboardRequest) {
|
||||
await this.copyToClipboard(this.clipboardRequest);
|
||||
this.resetButton();
|
||||
this.clipboardRequest = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.status) {
|
||||
return;
|
||||
}
|
||||
|
||||
let url = this.job + "/" + encodeURIComponent(this.pageURI);
|
||||
|
||||
if (this.data) {
|
||||
url = url + "/" + encodeURIComponent(this.data);
|
||||
}
|
||||
|
||||
this.getRequest(url);
|
||||
},
|
||||
|
||||
async getRequest(url) {
|
||||
this.button.label = this.progress ?? `${this.label} …`;
|
||||
this.button.state = "is-running";
|
||||
|
||||
const { label, status, reload, href, download, clipboard } =
|
||||
await this.$api.get(url);
|
||||
|
||||
if (label) {
|
||||
this.button.label = label;
|
||||
}
|
||||
|
||||
if (status) {
|
||||
this.button.state = status === 200 ? "is-success" : "has-error";
|
||||
} else {
|
||||
this.button.state = "has-response";
|
||||
}
|
||||
|
||||
if (reload) {
|
||||
location.reload();
|
||||
}
|
||||
|
||||
if (href) {
|
||||
if (this.intab) {
|
||||
this.urlRequest = href;
|
||||
this.$nextTick(() => {
|
||||
this.simulateClick(this.$refs.tabAnchor);
|
||||
});
|
||||
} else {
|
||||
location.href = href;
|
||||
}
|
||||
}
|
||||
|
||||
if (download) {
|
||||
this.downloadRequest = download;
|
||||
this.$nextTick(() => {
|
||||
this.simulateClick(this.$refs.downloadAnchor);
|
||||
});
|
||||
}
|
||||
|
||||
if (clipboard) {
|
||||
this.clipboardRequest = clipboard;
|
||||
} else {
|
||||
setTimeout(this.resetButton, this.cooldown);
|
||||
}
|
||||
},
|
||||
|
||||
resetButton() {
|
||||
this.button.label = null;
|
||||
this.button.state = null;
|
||||
},
|
||||
|
||||
simulateClick(element) {
|
||||
const evt = new MouseEvent("click", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
view: window,
|
||||
});
|
||||
|
||||
element.dispatchEvent(evt);
|
||||
},
|
||||
|
||||
async copyToClipboard(content) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(content);
|
||||
} catch (err) {
|
||||
console.error("navigator.clipboard is not available");
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.janitor {
|
||||
background-color: var(--color-text);
|
||||
color: white;
|
||||
border-radius: 3px;
|
||||
padding: 0.5rem 1rem;
|
||||
line-height: 1.25rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.janitor:hover {
|
||||
background-color: #222;
|
||||
}
|
||||
|
||||
.janitor .k-button-text {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.janitor.is-running {
|
||||
background-color: var(--color-border);
|
||||
}
|
||||
|
||||
.janitor.is-running .k-button-text {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.janitor.has-response {
|
||||
background-color: var(--color-text);
|
||||
}
|
||||
|
||||
.janitor.is-success {
|
||||
background-color: var(--color-positive);
|
||||
}
|
||||
|
||||
.janitor.has-error {
|
||||
background-color: var(--color-negative-light);
|
||||
}
|
||||
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
clip-path: inset(50%);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
11
site/plugins/kirby3-janitor/src/index.js
Executable file
11
site/plugins/kirby3-janitor/src/index.js
Executable file
@@ -0,0 +1,11 @@
|
||||
import Janitor from "./components/fields/Janitor.vue";
|
||||
|
||||
window.panel.plugin("bnomei/janitor", {
|
||||
fields: {
|
||||
janitor: Janitor,
|
||||
},
|
||||
icons: {
|
||||
janitorLoader:
|
||||
'<g fill="none" fill-rule="evenodd"><g transform="translate(1 1)" stroke-width="1.75"><circle cx="7" cy="7" r="7.2" stroke="#000" stroke-opacity=".2"/><path d="M14.2,7c0-4-3.2-7.2-7.2-7.2" stroke="#000"><animateTransform attributeName="transform" type="rotate" from="0 7 7" to="360 7 7" dur="1s" repeatCount="indefinite"/></path></g></g>',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user