improve/inc/core/improve.php

350 lines
10 KiB
PHP
Raw Normal View History

2021-09-01 23:35:23 +00:00
<?php
/**
* @brief improve, a plugin for Dotclear 2
2021-11-01 21:17:44 +00:00
*
2021-09-01 23:35:23 +00:00
* @package Dotclear
2021-09-06 21:36:50 +00:00
* @subpackage Plugin
2021-11-01 21:17:44 +00:00
*
2021-09-01 23:35:23 +00:00
* @author Jean-Christian Denis and contributors
2021-11-01 21:17:44 +00:00
*
2021-09-01 23:35:23 +00:00
* @copyright Jean-Christian Denis
* @copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
*/
declare(strict_types=1);
namespace plugins\improve;
/* dotclear */
use dcCore;
2022-12-01 10:09:24 +00:00
use dcLog;
/* clearbricks */
use path;
use files;
/* php */
2022-12-01 10:09:24 +00:00
use ArrayObject;
use Exception;
2021-09-01 23:35:23 +00:00
/**
* Improve main class
2021-09-01 23:35:23 +00:00
*/
class improve
2021-09-01 23:35:23 +00:00
{
2021-11-07 23:59:52 +00:00
/** @var array Allowed file extensions to open */
private static $readfile_extensions = [
'php', 'xml', 'js', 'css', 'csv', 'html', 'htm', 'txt', 'md',
2021-09-01 23:35:23 +00:00
];
2021-11-07 23:59:52 +00:00
/** @var array<action> $actions Loaded actions modules */
2021-11-07 23:59:52 +00:00
private $actions = [];
/** @var array<string> $disabled Disabled actions modules */
2021-11-05 21:52:46 +00:00
private $disabled = [];
2021-09-01 23:35:23 +00:00
/** @var array<string, array> $logs Logs by actions modules */
2021-11-07 23:59:52 +00:00
private $logs = [];
/** @var array<string, boolean> $has_log Has log of given type */
2021-11-07 23:59:52 +00:00
private $has_log = ['success' => false, 'warning' => false, 'error' => false];
/**
* Constructor
*/
public function __construct()
2021-09-01 23:35:23 +00:00
{
$disabled = explode(';', (string) dcCore::app()->blog->settings->improve->disabled);
2022-12-01 10:09:24 +00:00
$list = new ArrayObject();
2021-09-01 23:35:23 +00:00
try {
dcCore::app()->callBehavior('improveAddAction', $list);
2021-09-01 23:35:23 +00:00
2021-11-01 21:17:44 +00:00
foreach ($list as $action) {
if ($action instanceof action && !isset($this->actions[$action->id()])) {
if (in_array($action->id(), $disabled)) {
$this->disabled[$action->id()] = $action->name();
2021-11-05 21:52:46 +00:00
} else {
$this->actions[$action->id()] = $action;
2021-11-05 21:52:46 +00:00
}
2021-09-01 23:35:23 +00:00
}
}
} catch (Exception $e) {
dcCore::app()->error->add($e->getMessage());
2021-09-01 23:35:23 +00:00
}
uasort($this->actions, [$this, 'sortModules']);
}
public function getLogs(): array
{
return $this->logs;
}
public function hasLog(string $type): bool
{
return array_key_exists($type, $this->has_log) && $this->has_log[$type];
}
public function writeLogs(): int
{
if (empty($this->logs)) {
return 0;
}
2022-12-01 09:54:01 +00:00
$cur = dcCore::app()->con->openCursor(dcCore::app()->prefix . dcLog::LOG_TABLE_NAME);
2021-11-01 21:17:44 +00:00
$cur->log_msg = serialize($this->logs);
$cur->log_table = 'improve';
$id = dcCore::app()->log->addLog($cur);
return $id;
}
2021-11-01 21:17:44 +00:00
public function readLogs(int $id): array
{
$rs = dcCore::app()->log->getLogs(['log_table' => 'improve', 'log_id' => $id, 'limit' => 1]);
if ($rs->isEmpty()) {
return [];
}
2022-12-01 10:09:24 +00:00
dcCore::app()->log->delLogs($rs->__get('log_id'));
2021-11-01 21:17:44 +00:00
2022-12-01 10:09:24 +00:00
$res = unserialize($rs->__get('log_msg'));
return is_array($res) ? $res : [];
}
public function parselogs(int $id): array
{
$logs = $this->readLogs($id);
if (empty($logs)) {
return [];
}
$lines = [];
2021-11-01 21:17:44 +00:00
foreach ($logs['improve'] as $path => $tools) {
$l_types = [];
2021-11-01 21:17:44 +00:00
foreach (['success', 'warning', 'error'] as $type) {
$l_tools = [];
2021-11-01 21:17:44 +00:00
foreach ($tools as $tool) {
$l_msg = [];
if (!empty($logs[$tool][$type][$path])) {
2021-11-01 21:17:44 +00:00
foreach ($logs[$tool][$type][$path] as $msg) {
$l_msg[] = $msg;
}
}
if (!empty($l_msg)) {
$l_tools[$tool] = $l_msg;
}
}
if (!empty($l_tools)) {
$l_types[$type] = $l_tools;
}
}
if (!empty($l_types)) {
$lines[$path] = $l_types;
}
}
2021-11-01 21:17:44 +00:00
return $lines;
}
2021-11-07 23:59:52 +00:00
/**
* Get a loaded action module
*
* @param string $id Module id
*
* @return action action instance
2021-11-07 23:59:52 +00:00
*/
public function module(string $id): ?action
2021-09-01 23:35:23 +00:00
{
if (empty($id)) {
2021-11-07 23:59:52 +00:00
return null;
2021-09-01 23:35:23 +00:00
}
2021-11-01 21:17:44 +00:00
2021-09-01 23:35:23 +00:00
return $this->actions[$id] ?? null;
}
2021-11-07 23:59:52 +00:00
/**
* Get all loaded action modules
*
* @return action[] action instance
2021-11-07 23:59:52 +00:00
*/
public function modules(): array
2021-09-01 23:35:23 +00:00
{
2021-11-07 23:59:52 +00:00
return $this->actions;
2021-09-01 23:35:23 +00:00
}
2021-11-07 23:59:52 +00:00
/**
* Get disabled action modules
*
* @return array Array of id/name modules
*/
2021-11-05 21:52:46 +00:00
public function disabled(): array
{
return $this->disabled;
}
public function fixModule(string $type, string $id, array $properties, array $actions): float
2021-09-01 23:35:23 +00:00
{
$time_start = microtime(true);
$module = module::clean($type, $id, $properties);
2021-09-01 23:35:23 +00:00
$workers = [];
2021-11-01 21:17:44 +00:00
foreach ($actions as $action) {
2021-09-01 23:35:23 +00:00
if (isset($this->actions[$action]) && $this->actions[$action]->isConfigured()) {
$workers[] = $this->actions[$action];
}
}
2021-11-01 21:17:44 +00:00
foreach ($workers as $action) {
// trace all path and action in logs
$this->logs['improve'][__('Begin')][] = $action->id();
// info: set current module
$action->setModule($module);
$action->setPath(__('Begin'), '', true);
// action: open module
$action->openModule();
2021-09-01 23:35:23 +00:00
}
if (!isset($module['sroot']) || !$module['root_writable'] || !is_writable($module['sroot'])) {
throw new Exception(__('Module path is not writable'));
}
$tree = self::getModuleFiles($module['sroot']);
2021-11-01 21:17:44 +00:00
foreach ($tree as $file) {
2021-09-01 23:35:23 +00:00
if (!file_exists($file[0])) {
continue;
}
2021-11-01 21:17:44 +00:00
foreach ($workers as $action) {
// trace all path and action in logs
$this->logs['improve'][$file[0]][] = $action->id();
// info: set current path
$action->setPath($file[0], $file[1], $file[2]);
}
2021-09-01 23:35:23 +00:00
if (!$file[2]) {
2021-11-01 21:17:44 +00:00
foreach ($workers as $action) {
// action: open a directory. full path
$action->openDirectory();
2021-09-01 23:35:23 +00:00
}
} else {
2021-11-01 21:17:44 +00:00
foreach ($workers as $action) {
// action: before openning a file. full path, extension
$action->openFile();
2021-09-01 23:35:23 +00:00
}
if (in_array($file[1], self::$readfile_extensions)) {
if (false !== ($content = file_get_contents($file[0]))) {
$no_content = empty($content);
2021-11-01 21:17:44 +00:00
foreach ($workers as $action) {
// action: read a file content. full path, extension, content
$action->readFile($content);
2021-09-01 23:35:23 +00:00
if (empty($content) && !$no_content) {
throw new Exception(sprintf(
2021-11-01 21:17:44 +00:00
__('File content has been removed: %s by %s'),
$file[0],
$action->name()
2021-09-01 23:35:23 +00:00
));
}
}
files::putContent($file[0], $content);
}
2021-11-01 21:17:44 +00:00
foreach ($workers as $action) {
// action: after closing a file. full path, extension
$action->closeFile();
2021-09-01 23:35:23 +00:00
}
}
}
}
2021-11-01 21:17:44 +00:00
foreach ($workers as $action) {
// trace all path and action in logs
$this->logs['improve'][__('End')][] = $action->id();
// info: set current module
$action->setPath(__('End'), '', true);
// action: close module
$action->closeModule();
2021-09-01 23:35:23 +00:00
}
// info: get acions reports
2021-11-01 21:17:44 +00:00
foreach ($workers as $action) {
$this->logs[$action->id()] = $action->getLogs();
2021-11-01 21:17:44 +00:00
foreach ($this->has_log as $type => $v) {
if ($action->hasLog($type)) {
$this->has_log[$type] = true;
}
2021-09-01 23:35:23 +00:00
}
}
2021-11-07 23:59:52 +00:00
return round(microtime(true) - $time_start, 5);
2021-09-01 23:35:23 +00:00
}
private static function getModuleFiles(string $path, string $dir = '', array $res = []): array
{
$path = path::real($path);
2021-11-07 23:59:52 +00:00
if (!$path) {
return [];
}
2021-09-01 23:35:23 +00:00
if (!is_dir($path) || !is_readable($path)) {
return [];
}
if (!$dir) {
$dir = $path;
}
$res[] = [$dir, '', false];
$files = files::scandir($path);
2021-11-01 21:17:44 +00:00
foreach ($files as $file) {
2021-09-01 23:35:23 +00:00
if (substr($file, 0, 1) == '.') {
continue;
}
if (is_dir($path . '/' . $file)) {
$res = self::getModuleFiles(
2021-11-01 21:17:44 +00:00
$path . '/' . $file,
$dir . '/' . $file,
2021-09-01 23:35:23 +00:00
$res
);
} else {
2021-11-07 23:59:52 +00:00
$res[] = [$dir . '/' . $file, files::getExtension($file), true];
2021-09-01 23:35:23 +00:00
}
}
2021-11-01 21:17:44 +00:00
2021-09-01 23:35:23 +00:00
return $res;
}
public function getURL(array $params = []): string
{
return dcCore::app()->adminurl->get('admin.plugin.improve', $params, '&');
2021-09-01 23:35:23 +00:00
}
/**
* Check and clean file extension
*
* @param string|array $in Extension(s) to clean
* @return array Cleaned extension(s)
*/
public static function cleanExtensions($in): array
2021-09-01 23:35:23 +00:00
{
$out = [];
if (!is_array($in)) {
$in = explode(',', $in);
}
if (!empty($in)) {
2021-11-01 21:17:44 +00:00
foreach ($in as $v) {
2021-09-01 23:35:23 +00:00
$v = trim(files::getExtension('a.' . $v));
if (!empty($v)) {
$out[] = $v;
}
}
}
2021-11-01 21:17:44 +00:00
2021-09-01 23:35:23 +00:00
return $out;
}
2021-11-07 23:59:52 +00:00
/**
* Sort modules by priority then name
*
* @param action $a ImproveAction instance
* @param action $b ImproveAction instance
2021-11-07 23:59:52 +00:00
*
* @return integer Is higher
*/
private function sortModules(action $a, action $b): int
2021-09-01 23:35:23 +00:00
{
if ($a->priority() == $b->priority()) {
return strcasecmp($a->name(), $b->name());
2021-09-01 23:35:23 +00:00
}
2021-11-01 21:17:44 +00:00
return $a->priority() < $b->priority() ? -1 : 1;
2021-09-01 23:35:23 +00:00
}
}