Fix errors from phpstan analyze
This commit is contained in:
parent
3b8651b1cb
commit
5417d20742
12 changed files with 380 additions and 199 deletions
|
@ -37,7 +37,7 @@ $_menu['Plugins']->addItem(
|
|||
|
||||
class ImproveBehaviors
|
||||
{
|
||||
public static function adminDashboardFavorites($core, $favs)
|
||||
public static function adminDashboardFavorites(dcCore $core, dcFavorites $favs): void
|
||||
{
|
||||
$favs->register(
|
||||
'improve',
|
||||
|
|
|
@ -21,7 +21,7 @@ $improve = new Improve($core);
|
|||
|
||||
$combo_actions = [];
|
||||
foreach ($improve->modules() as $action) {
|
||||
$combo_actions[$action->name] = $action->id;
|
||||
$combo_actions[$action->get('name')] = $action->get('id');
|
||||
}
|
||||
$disabled = $improve->disabled();
|
||||
if (!empty($disabled)) {
|
||||
|
|
|
@ -28,14 +28,31 @@
|
|||
*/
|
||||
abstract class ImproveAction
|
||||
{
|
||||
/** @var dcCore dcCore instance */
|
||||
protected $core;
|
||||
protected $module = [];
|
||||
protected $path_full = '';
|
||||
protected $path_extension = '';
|
||||
protected $path_is_dir = null;
|
||||
|
||||
private $logs = ['success' => [], 'warning' => [], 'error' => []];
|
||||
private $settings = [];
|
||||
/** @var array<string> Current module */
|
||||
protected $module = [];
|
||||
|
||||
/** @var string Current full path */
|
||||
protected $path_full = '';
|
||||
|
||||
/** @var string Current file extension */
|
||||
protected $path_extension = '';
|
||||
|
||||
/** @var boolean Current path is directory */
|
||||
protected $path_is_dir = null;
|
||||
|
||||
/** @var string The child class name */
|
||||
private $class_name = '';
|
||||
|
||||
/** @var array<string, array> Messages logs */
|
||||
private $logs = ['success' => [], 'warning' => [], 'error' => []];
|
||||
|
||||
/** @var array<string> Action module settings */
|
||||
private $settings = [];
|
||||
|
||||
/** @var array<mixed> Action module properties */
|
||||
private $properties = [
|
||||
'id' => '',
|
||||
'name' => '',
|
||||
|
@ -48,33 +65,35 @@ abstract class ImproveAction
|
|||
/**
|
||||
* ImproveAction constructor inits properpties and settings of a child class.
|
||||
*
|
||||
* @param string $core dcCore instance
|
||||
* @param dcCore $core dcCore instance
|
||||
*/
|
||||
final public function __construct(dcCore $core)
|
||||
{
|
||||
$this->core = $core;
|
||||
$this->core = $core;
|
||||
$this->class_name = get_called_class();
|
||||
|
||||
$settings = @unserialize($core->blog->settings->improve->get('settings_' . get_called_class()));
|
||||
$settings = @unserialize($core->blog->settings->improve->get('settings_' . $this->class_name));
|
||||
$this->settings = is_array($settings) ? $settings : [];
|
||||
|
||||
$this->init();
|
||||
|
||||
// can overload priority by settings
|
||||
if (1 < ($p = (int) $core->blog->settings->improve->get('priority_' . get_called_class()))) {
|
||||
$this->priority = $p;
|
||||
if (1 < ($p = (int) $core->blog->settings->improve->get('priority_' . $this->class_name))) {
|
||||
$this->properties['priority'] = $p;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create an instance of a ImproveAction child class.
|
||||
*
|
||||
* @param string $o ArrayObject of actions list
|
||||
* @param string $core dcCore instance
|
||||
* @param ArrayObject $list ArrayObject of actions list
|
||||
* @param dcCore $core dcCore instance
|
||||
*/
|
||||
public static function create(arrayObject $o, dcCore $core)
|
||||
final public static function create(arrayObject $list, dcCore $core): void
|
||||
{
|
||||
$c = get_called_class();
|
||||
$o->append(new $c($core));
|
||||
$child = static::class;
|
||||
$class = new $child($core);
|
||||
$list->append($class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -90,11 +109,21 @@ abstract class ImproveAction
|
|||
/// @name Properties methods
|
||||
//@{
|
||||
/**
|
||||
* @see getProperty();
|
||||
* Get a definition property of action class
|
||||
*
|
||||
* @param string $key a property or setting id
|
||||
*
|
||||
* @return mixed Value of property or setting of action.
|
||||
*/
|
||||
final public function __get(string $property)
|
||||
final public function get(string $key)
|
||||
{
|
||||
return $this->getProperty($property);
|
||||
if (isset($this->properties[$key])) {
|
||||
return $this->properties[$key];
|
||||
} elseif (isset($this->settings[$key])) {
|
||||
return $this->settings[$key];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -118,10 +147,10 @@ abstract class ImproveAction
|
|||
* - config : as configuration gui, false = none, true = internal, string = ext url
|
||||
* - types : array of supported type of module, can : be plugins and/or themes
|
||||
*
|
||||
* @param mixed $property one or more definition
|
||||
* @param dtring $value value for a single property
|
||||
* @param mixed $property one or more definition
|
||||
* @param mixed $value value for a single property
|
||||
*
|
||||
* @return mixed A property of action definition.
|
||||
* @return boolean Success
|
||||
*/
|
||||
final protected function setProperties($property, $value = null): bool
|
||||
{
|
||||
|
@ -157,7 +186,7 @@ abstract class ImproveAction
|
|||
* Set one or more setting of action class
|
||||
*
|
||||
* @param mixed $settings one or more settings
|
||||
* @param string $value value for a single setting
|
||||
* @param mixed $value value for a single setting
|
||||
*
|
||||
* @return mixed A setting of action.
|
||||
*/
|
||||
|
@ -178,10 +207,10 @@ abstract class ImproveAction
|
|||
*
|
||||
* @param string $url redirect url after settings update
|
||||
*/
|
||||
final protected function redirect(string $url)
|
||||
final protected function redirect(string $url): bool
|
||||
{
|
||||
$this->core->blog->settings->improve->put(
|
||||
'settings_' . get_called_class(),
|
||||
'settings_' . $this->class_name,
|
||||
serialize($this->settings),
|
||||
'string',
|
||||
null,
|
||||
|
@ -191,6 +220,8 @@ abstract class ImproveAction
|
|||
$this->core->blog->triggerBlog();
|
||||
dcPage::addSuccessNotice(__('Configuration successfully updated'));
|
||||
http::redirect($url);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -219,9 +250,9 @@ abstract class ImproveAction
|
|||
* This function is also called to redirect form
|
||||
* after validation with $this->redirect($url);
|
||||
*
|
||||
* @param string $url post form redirect url
|
||||
* @param string $url post form redirect url
|
||||
*
|
||||
* @return mixed A setting of action.
|
||||
* @return string|null A setting of action.
|
||||
*/
|
||||
public function configure(string $url): ?string
|
||||
{
|
||||
|
@ -234,25 +265,29 @@ abstract class ImproveAction
|
|||
*
|
||||
* @see Improve::sanitizeModule()
|
||||
*
|
||||
* @param array $module Full array of module definitons
|
||||
* @param array<string> $module Full array of module definitons
|
||||
*/
|
||||
final public function setModule(array $module)
|
||||
final public function setModule(array $module): bool
|
||||
{
|
||||
$this->module = $module;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set in class var current path definitons.
|
||||
*
|
||||
* @param string $path_full Full path
|
||||
* @param string $path_extension Path extension (if it is a file)
|
||||
* @param string $path_is_dir True if path is a directory
|
||||
* @param string $path_full Full path
|
||||
* @param string $path_extension Path extension (if it is a file)
|
||||
* @param boolean $path_is_dir True if path is a directory
|
||||
*/
|
||||
final public function setPath(string $path_full, string $path_extension, bool $path_is_dir)
|
||||
final public function setPath(string $path_full, string $path_extension, bool $path_is_dir): bool
|
||||
{
|
||||
$this->path_full = $path_full;
|
||||
$this->path_extension = $path_extension;
|
||||
$this->path_is_dir = $path_is_dir;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @name Fix methods
|
||||
|
@ -372,9 +407,9 @@ abstract class ImproveAction
|
|||
/**
|
||||
* Set a log of type error.
|
||||
*/
|
||||
final public function setError(string $message)
|
||||
final public function setError(string $message): bool
|
||||
{
|
||||
$this->setLog('error', $message);
|
||||
return $this->setLog('error', $message);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -396,9 +431,9 @@ abstract class ImproveAction
|
|||
/**
|
||||
* Set a log of type warning.
|
||||
*/
|
||||
final public function setWarning(string $message)
|
||||
final public function setWarning(string $message): bool
|
||||
{
|
||||
$this->setLog('warning', $message);
|
||||
return $this->setLog('warning', $message);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -420,9 +455,9 @@ abstract class ImproveAction
|
|||
/**
|
||||
* Set a log of type success.
|
||||
*/
|
||||
final public function setSuccess(string $message)
|
||||
final public function setSuccess(string $message): bool
|
||||
{
|
||||
$this->setLog('success', $message);
|
||||
return $this->setLog('success', $message);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,31 +15,47 @@
|
|||
*/
|
||||
class Improve
|
||||
{
|
||||
/** @var array Allowed file extensions to open */
|
||||
public static $readfile_extensions = [
|
||||
'php', 'xml', 'js', 'css', 'csv', 'html', 'htm', 'txt', 'md'
|
||||
];
|
||||
private $core;
|
||||
private $actions = [];
|
||||
private $disabled = [];
|
||||
private $logs = [];
|
||||
private $has_log = ['success' => false, 'warning' => false, 'error' => false];
|
||||
|
||||
/** @var dcCore dcCore instance */
|
||||
private $core;
|
||||
|
||||
/** @var ImproveAction[] Loaded actions modules */
|
||||
private $actions = [];
|
||||
|
||||
/** @var array<string> Disabled actions modules */
|
||||
private $disabled = [];
|
||||
|
||||
/** @var array<string, array> Logs by actions modules */
|
||||
private $logs = [];
|
||||
|
||||
/** @var array<string, boolean> Has log of given type */
|
||||
private $has_log = ['success' => false, 'warning' => false, 'error' => false];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param dcCore $core dcCore instance
|
||||
*/
|
||||
public function __construct(dcCore $core)
|
||||
{
|
||||
$this->core = &$core;
|
||||
$core->blog->settings->addNamespace('improve');
|
||||
$disabled = explode(';', (string) $core->blog->settings->improve->disabled);
|
||||
$list = new arrayObject();
|
||||
$this->core = &$core;
|
||||
$disabled = explode(';', (string) $core->blog->settings->improve->disabled);
|
||||
$list = new arrayObject();
|
||||
|
||||
try {
|
||||
$this->core->callBehavior('improveAddAction', $list, $this->core);
|
||||
|
||||
foreach ($list as $action) {
|
||||
if ($action instanceof ImproveAction && !isset($this->actions[$action->id])) {
|
||||
if (in_array($action->id, $disabled)) {
|
||||
$this->disabled[$action->id] = $action->name;
|
||||
if (is_a($action, 'ImproveAction') && !isset($this->actions[$action->get('id')])) {
|
||||
if (in_array($action->get('id'), $disabled)) {
|
||||
$this->disabled[$action->get('id')] = $action->get('name');
|
||||
} else {
|
||||
$this->actions[$action->id] = $action;
|
||||
$this->actions[$action->get('id')] = $action;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -117,24 +133,37 @@ class Improve
|
|||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a loaded action module
|
||||
*
|
||||
* @param string $id Module id
|
||||
*
|
||||
* @return ImproveAction ImproveAction instance
|
||||
*/
|
||||
public function module(string $id): ?ImproveAction
|
||||
{
|
||||
if (empty($id)) {
|
||||
return $this->actions;
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->actions[$id] ?? null;
|
||||
}
|
||||
|
||||
public function modules(): ?array
|
||||
/**
|
||||
* Get all loaded action modules
|
||||
*
|
||||
* @return ImproveAction[] ImproveAction instance
|
||||
*/
|
||||
public function modules(): array
|
||||
{
|
||||
if (empty($id)) {
|
||||
return $this->actions;
|
||||
}
|
||||
|
||||
return $this->actions[$id] ?? null;
|
||||
return $this->actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get disabled action modules
|
||||
*
|
||||
* @return array Array of id/name modules
|
||||
*/
|
||||
public function disabled(): array
|
||||
{
|
||||
return $this->disabled;
|
||||
|
@ -153,7 +182,7 @@ class Improve
|
|||
}
|
||||
foreach ($workers as $action) {
|
||||
// trace all path and action in logs
|
||||
$this->logs['improve'][__('Begin')][] = $action->id;
|
||||
$this->logs['improve'][__('Begin')][] = $action->get('id');
|
||||
// info: set current module
|
||||
$action->setModule($module);
|
||||
$action->setPath(__('Begin'), '', true);
|
||||
|
@ -170,7 +199,7 @@ class Improve
|
|||
}
|
||||
foreach ($workers as $action) {
|
||||
// trace all path and action in logs
|
||||
$this->logs['improve'][$file[0]][] = $action->id;
|
||||
$this->logs['improve'][$file[0]][] = $action->get('id');
|
||||
// info: set current path
|
||||
$action->setPath($file[0], $file[1], $file[2]);
|
||||
}
|
||||
|
@ -194,7 +223,7 @@ class Improve
|
|||
throw new Exception(sprintf(
|
||||
__('File content has been removed: %s by %s'),
|
||||
$file[0],
|
||||
$action->name
|
||||
$action->get('name')
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -209,7 +238,7 @@ class Improve
|
|||
}
|
||||
foreach ($workers as $action) {
|
||||
// trace all path and action in logs
|
||||
$this->logs['improve'][__('End')][] = $action->id;
|
||||
$this->logs['improve'][__('End')][] = $action->get('id');
|
||||
// info: set current module
|
||||
$action->setPath(__('End'), '', true);
|
||||
// action: close module
|
||||
|
@ -217,7 +246,7 @@ class Improve
|
|||
}
|
||||
// info: get acions reports
|
||||
foreach ($workers as $action) {
|
||||
$this->logs[$action->id] = $action->getLogs();
|
||||
$this->logs[$action->get('id')] = $action->getLogs();
|
||||
foreach ($this->has_log as $type => $v) {
|
||||
if ($action->hasLog($type)) {
|
||||
$this->has_log[$type] = true;
|
||||
|
@ -225,12 +254,15 @@ class Improve
|
|||
}
|
||||
}
|
||||
|
||||
return substr(microtime(true) - $time_start, 0, 5);
|
||||
return round(microtime(true) - $time_start, 5);
|
||||
}
|
||||
|
||||
private static function getModuleFiles(string $path, string $dir = '', array $res = []): array
|
||||
{
|
||||
$path = path::real($path);
|
||||
if (!$path) {
|
||||
return [];
|
||||
}
|
||||
if (!is_dir($path) || !is_readable($path)) {
|
||||
return [];
|
||||
}
|
||||
|
@ -251,7 +283,7 @@ class Improve
|
|||
$res
|
||||
);
|
||||
} else {
|
||||
$res[] = [empty($dir) ? $file : $dir . '/' . $file, files::getExtension($file), true];
|
||||
$res[] = [$dir . '/' . $file, files::getExtension($file), true];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -263,7 +295,7 @@ class Improve
|
|||
return $this->core->adminurl->get('admin.plugin.improve', $params, '&');
|
||||
}
|
||||
|
||||
public static function cleanExtensions($in): array
|
||||
public static function cleanExtensions(string|array $in): array
|
||||
{
|
||||
$out = [];
|
||||
if (!is_array($in)) {
|
||||
|
@ -281,32 +313,61 @@ class Improve
|
|||
return $out;
|
||||
}
|
||||
|
||||
private function sortModules(improveAction $a, improveAction $b): int
|
||||
/**
|
||||
* Sort modules by priority then name
|
||||
*
|
||||
* @param ImproveAction $a ImproveAction instance
|
||||
* @param ImproveAction $b ImproveAction instance
|
||||
*
|
||||
* @return integer Is higher
|
||||
*/
|
||||
private function sortModules(ImproveAction $a, ImproveAction $b): int
|
||||
{
|
||||
if ($a->priority == $b->priority) {
|
||||
return strcasecmp($a->name, $b->name);
|
||||
if ($a->get('priority') == $b->get('priority')) {
|
||||
return strcasecmp($a->get('name'), $b->get('name'));
|
||||
}
|
||||
|
||||
return $a->priority < $b->priority ? -1 : 1;
|
||||
return $a->get('priority') < $b->get('priority') ? -1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
class ImproveDefinition
|
||||
{
|
||||
/** @var array Current module properties */
|
||||
private $properties = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $type Module type, plugin or theme
|
||||
* @param string $id Module id
|
||||
* @param array $properties Module properties
|
||||
*/
|
||||
public function __construct(string $type, string $id, array $properties = [])
|
||||
{
|
||||
$this->loadDefine($id, $properties['root']);
|
||||
|
||||
$this->properties = array_merge($this->properties, self::sanitizeModule($type, $id, $properties));
|
||||
}
|
||||
|
||||
public function get()
|
||||
/**
|
||||
* Get module properties
|
||||
*
|
||||
* @return array The properties
|
||||
*/
|
||||
public function get(): array
|
||||
{
|
||||
return $this->properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get clean properties of registered module
|
||||
*
|
||||
* @param string $type Module type, plugin or theme
|
||||
* @param string $id Module id
|
||||
* @param array $properties Module properties
|
||||
*
|
||||
* @return array Module properties
|
||||
*/
|
||||
public static function clean(string $type, string $id, array $properties): array
|
||||
{
|
||||
$p = new self($type, $id, $properties);
|
||||
|
@ -314,19 +375,37 @@ class ImproveDefinition
|
|||
return $p->get();
|
||||
}
|
||||
|
||||
private function loadDefine($id, $root)
|
||||
/**
|
||||
* Replicate dcModule::loadDefine
|
||||
*
|
||||
* @param string $id Module id
|
||||
* @param string $root Module path
|
||||
*
|
||||
* @return boolean Success
|
||||
*/
|
||||
private function loadDefine(string $id, string $root): bool
|
||||
{
|
||||
if (file_exists($root . '/_define.php')) {
|
||||
$this->id = $id;
|
||||
$this->mroot = $root;
|
||||
ob_start();
|
||||
require $root . '/_define.php';
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
# adapt from class.dc.modules.php
|
||||
private function registerModule($name, $desc, $author, $version, $properties = [])
|
||||
/**
|
||||
* Replicate dcModule::registerModule
|
||||
*
|
||||
* @param string $name The module name
|
||||
* @param string $desc The module description
|
||||
* @param string $author The module author
|
||||
* @param string $version The module version
|
||||
* @param string|array $properties The properties
|
||||
*
|
||||
* @return boolean Success
|
||||
*/
|
||||
private function registerModule(string $name, string $desc, string $author, string $version, string|array $properties = []): bool // @phpstan-ignore-line
|
||||
{
|
||||
if (!is_array($properties)) {
|
||||
$args = func_get_args();
|
||||
|
@ -352,9 +431,19 @@ class ImproveDefinition
|
|||
],
|
||||
$properties
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
# adapt from lib.moduleslist.php
|
||||
/**
|
||||
* Replicate adminModulesList::sanitizeModule
|
||||
*
|
||||
* @param string $type Module type
|
||||
* @param string $id Module id
|
||||
* @param array $properties Module properties
|
||||
*
|
||||
* @return array Sanitized module properties
|
||||
*/
|
||||
public static function sanitizeModule(string $type, string $id, array $properties): array
|
||||
{
|
||||
$label = empty($properties['label']) ? $id : $properties['label'];
|
||||
|
@ -402,9 +491,15 @@ class ImproveDefinition
|
|||
);
|
||||
}
|
||||
|
||||
# taken from lib.moduleslist.php
|
||||
/**
|
||||
* Replicate adminModulesList::sanitizeString
|
||||
*
|
||||
* @param string $str String to sanitize
|
||||
*
|
||||
* @return string Sanitized string
|
||||
*/
|
||||
public static function sanitizeString(string $str): string
|
||||
{
|
||||
return preg_replace('/[^A-Za-z0-9\@\#+_-]/', '', strtolower($str));
|
||||
return (string) preg_replace('/[^A-Za-z0-9\@\#+_-]/', '', strtolower($str));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ class ImproveActionDcstore extends ImproveAction
|
|||
return true;
|
||||
}
|
||||
|
||||
public function generateXML()
|
||||
public function generateXML(): string
|
||||
{
|
||||
$xml = ['<modules xmlns:da="http://dotaddict.org/da/">'];
|
||||
$rsp = new xmlTag('module');
|
||||
|
@ -179,13 +179,13 @@ class ImproveActionDcstore extends ImproveAction
|
|||
$dom->formatOutput = true;
|
||||
$dom->loadXML($str);
|
||||
|
||||
return $dom->saveXML();
|
||||
return (string) $dom->saveXML();
|
||||
}
|
||||
|
||||
return str_replace('><', ">\n<", $str);
|
||||
}
|
||||
|
||||
private function parseFilePattern()
|
||||
private function parseFilePattern(): string
|
||||
{
|
||||
return text::tidyURL(str_replace(
|
||||
[
|
||||
|
|
|
@ -12,11 +12,19 @@
|
|||
*/
|
||||
class ImproveActionGitshields extends ImproveAction
|
||||
{
|
||||
private $stop_scan = false;
|
||||
/** @var boolean Stop scaning files */
|
||||
private $stop_scan = false;
|
||||
|
||||
/** @var array Parsed bloc */
|
||||
private $blocs = [];
|
||||
|
||||
/** @var array Search patterns */
|
||||
protected $bloc_pattern = [
|
||||
'remove' => '/\[!\[Release(.*)LICENSE\)/ms',
|
||||
'target' => '/^([^\n]+)[\r\n|\n]{1,}/ms'
|
||||
];
|
||||
|
||||
/** @var array Shields patterns */
|
||||
protected $bloc_content = [
|
||||
'release' => '[![Release](https://img.shields.io/github/v/release/%username%/%module%)](https://github.com/%username%/%module%/releases)',
|
||||
'date' => '[![Date](https://img.shields.io/github/release-date/%username%/%module%)](https://github.com/%username%/%module%/releases)',
|
||||
|
@ -87,14 +95,14 @@ class ImproveActionGitshields extends ImproveAction
|
|||
return true;
|
||||
}
|
||||
|
||||
private function replaceInfo()
|
||||
private function replaceInfo(): void
|
||||
{
|
||||
$bloc = [];
|
||||
$blocs = [];
|
||||
foreach ($this->bloc_content as $k => $v) {
|
||||
if ($k == 'dotaddict' && empty($this->getSetting('dotaddict'))) {
|
||||
continue;
|
||||
}
|
||||
$bloc[$k] = trim(str_replace(
|
||||
$blocs[$k] = trim(str_replace(
|
||||
[
|
||||
'%username%',
|
||||
'%module%',
|
||||
|
@ -112,11 +120,11 @@ class ImproveActionGitshields extends ImproveAction
|
|||
$v
|
||||
));
|
||||
}
|
||||
$this->bloc = $bloc;
|
||||
$this->blocs = $blocs;
|
||||
$this->setSuccess(__('Prepare custom shield info'));
|
||||
}
|
||||
|
||||
private function getDotclearVersion()
|
||||
private function getDotclearVersion(): string
|
||||
{
|
||||
$version = null;
|
||||
if (!empty($this->module['requires']) && is_array($this->module['requires'])) {
|
||||
|
@ -137,23 +145,23 @@ class ImproveActionGitshields extends ImproveAction
|
|||
return $version ?: $this->core->getVersion('core');
|
||||
}
|
||||
|
||||
private function writeShieldsBloc($content)
|
||||
private function writeShieldsBloc(string $content): string
|
||||
{
|
||||
$res = preg_replace(
|
||||
$this->bloc_pattern['target'],
|
||||
'$1' . "\n\n" . trim(implode("\n", $this->bloc)) . "\n\n",
|
||||
'$1' . "\n\n" . trim(implode("\n", $this->blocs)) . "\n\n",
|
||||
$content,
|
||||
1,
|
||||
$count
|
||||
);
|
||||
if ($count) {
|
||||
if ($count && $res) {
|
||||
$this->setSuccess(__('Write new shield bloc'));
|
||||
}
|
||||
|
||||
return $res;
|
||||
return (string) $res;
|
||||
}
|
||||
|
||||
private function deleteShieldsBloc($content)
|
||||
private function deleteShieldsBloc(string $content): string
|
||||
{
|
||||
$res = preg_replace(
|
||||
$this->bloc_pattern['remove'],
|
||||
|
@ -162,10 +170,10 @@ class ImproveActionGitshields extends ImproveAction
|
|||
1,
|
||||
$count
|
||||
);
|
||||
if ($count) {
|
||||
if ($count && $res) {
|
||||
$this->setSuccess(__('Delete old shield bloc'));
|
||||
}
|
||||
|
||||
return $res;
|
||||
return (string) $res;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,14 +12,18 @@
|
|||
*/
|
||||
class ImproveActionLicensefile extends ImproveAction
|
||||
{
|
||||
/** @var array Possible license filenames */
|
||||
protected static $license_filenames = [
|
||||
'license',
|
||||
'license.md',
|
||||
'license.txt'
|
||||
];
|
||||
|
||||
/** @var array Possible license names */
|
||||
private $action_version = [];
|
||||
private $action_full = [];
|
||||
private $stop_scan = false;
|
||||
|
||||
/** @var array Action */
|
||||
private $action_full = [];
|
||||
|
||||
protected function init(): bool
|
||||
{
|
||||
|
@ -90,7 +94,7 @@ class ImproveActionLicensefile extends ImproveAction
|
|||
return null;
|
||||
}
|
||||
|
||||
private function writeFullLicense()
|
||||
private function writeFullLicense(): ?bool
|
||||
{
|
||||
try {
|
||||
$full = file_get_contents(dirname(__FILE__) . '/license/' . $this->getSetting('action_version') . '.full.txt');
|
||||
|
@ -110,7 +114,7 @@ class ImproveActionLicensefile extends ImproveAction
|
|||
return true;
|
||||
}
|
||||
|
||||
private function deleteFullLicense($only_one = false)
|
||||
private function deleteFullLicense(bool $only_one = false): bool
|
||||
{
|
||||
foreach (self::fileExists($this->module['root']) as $file) {
|
||||
if ($only_one && $file != 'LICENSE') {
|
||||
|
@ -128,7 +132,7 @@ class ImproveActionLicensefile extends ImproveAction
|
|||
return true;
|
||||
}
|
||||
|
||||
private static function fileExists($root)
|
||||
private static function fileExists(string $root): array
|
||||
{
|
||||
$existing = [];
|
||||
foreach (self::$license_filenames as $file) {
|
||||
|
|
|
@ -47,8 +47,6 @@ class ImproveActionTab extends ImproveAction
|
|||
|
||||
class ImproveActionNewline extends ImproveAction
|
||||
{
|
||||
private $extensions = ['php', 'js', 'xml', 'md', 'txt'];
|
||||
|
||||
protected function init(): bool
|
||||
{
|
||||
$this->setProperties([
|
||||
|
@ -98,19 +96,19 @@ class ImproveActionNewline extends ImproveAction
|
|||
'</p>';
|
||||
}
|
||||
|
||||
public function readFile(&$content): ?bool
|
||||
public function readFile(string &$content): ?bool
|
||||
{
|
||||
$ext = $this->getSetting('extensions');
|
||||
if (!is_array($ext) || !in_array($this->path_extension, $ext)) {
|
||||
return null;
|
||||
}
|
||||
$clean = preg_replace(
|
||||
$clean = (string) preg_replace(
|
||||
'/(\n\s+\n)/',
|
||||
"\n\n",
|
||||
preg_replace(
|
||||
(string) preg_replace(
|
||||
'/(\n\n+)/',
|
||||
"\n\n",
|
||||
str_replace(
|
||||
(string) str_replace(
|
||||
["\r\n", "\r"],
|
||||
"\n",
|
||||
$content
|
||||
|
@ -128,8 +126,6 @@ class ImproveActionNewline extends ImproveAction
|
|||
|
||||
class ImproveActionEndoffile extends ImproveAction
|
||||
{
|
||||
private $psr2 = false;
|
||||
|
||||
protected function init(): bool
|
||||
{
|
||||
$this->setProperties([
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
*/
|
||||
class ImproveActionPhpcsfixer extends ImproveAction
|
||||
{
|
||||
/** @var array<int,string> Type of runtime errors */
|
||||
protected static $errors = [
|
||||
0 => 'OK.',
|
||||
1 => 'General error (or PHP minimal requirement not matched).',
|
||||
|
@ -21,7 +22,11 @@ class ImproveActionPhpcsfixer extends ImproveAction
|
|||
32 => 'Configuration error of a Fixer.',
|
||||
64 => 'Exception raised within the application'
|
||||
];
|
||||
protected static $user_ui_colorsyntax = false;
|
||||
|
||||
/** @var boolean User pref to use colored synthax */
|
||||
protected static $user_ui_colorsyntax = false;
|
||||
|
||||
/** @var string User pref for colored synthax theme */
|
||||
protected static $user_ui_colorsyntax_theme = 'default';
|
||||
|
||||
protected function init(): bool
|
||||
|
@ -64,7 +69,7 @@ class ImproveActionPhpcsfixer extends ImproveAction
|
|||
]);
|
||||
$this->redirect($url);
|
||||
}
|
||||
$content = file_get_contents(dirname(__FILE__) . '/libs/dc.phpcsfixer.rules.php');
|
||||
$content = (string) file_get_contents(dirname(__FILE__) . '/libs/dc.phpcsfixer.rules.php');
|
||||
|
||||
return
|
||||
'<p><label class="classic" for="phpexe_path">' .
|
||||
|
@ -124,13 +129,18 @@ class ImproveActionPhpcsfixer extends ImproveAction
|
|||
}
|
||||
}
|
||||
|
||||
private function getPhpPath()
|
||||
/**
|
||||
* Get php executable path
|
||||
*
|
||||
* @return string The path
|
||||
*/
|
||||
private function getPhpPath(): string
|
||||
{
|
||||
$phpexe_path = $this->getSetting('phpexe_path');
|
||||
if (empty($phpexe_path) && !empty(PHP_BINDIR)) {
|
||||
$phpexe_path = PHP_BINDIR;
|
||||
}
|
||||
|
||||
return path::real($phpexe_path);
|
||||
return (string) path::real($phpexe_path);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
*/
|
||||
class ImproveActionPhpheader extends ImproveAction
|
||||
{
|
||||
private static $exemple = "
|
||||
/** @var string Exemple of header */
|
||||
private static $exemple = <<<EOF
|
||||
@brief %module_id%, a %module_type% for Dotclear 2
|
||||
|
||||
@package Dotclear
|
||||
|
@ -21,8 +22,10 @@ class ImproveActionPhpheader extends ImproveAction
|
|||
@author %module_author%
|
||||
|
||||
@copyright %user_cn%
|
||||
@copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html";
|
||||
@copyright GPL-2.0 https://www.gnu.org/licenses/gpl-2.0.html
|
||||
EOF;
|
||||
|
||||
/** @var array<string> Allowed bloc replacement */
|
||||
private $bloc_wildcards = [
|
||||
'%year%',
|
||||
'%module_id%',
|
||||
|
@ -34,10 +37,15 @@ class ImproveActionPhpheader extends ImproveAction
|
|||
'%user_email%',
|
||||
'%user_url%'
|
||||
];
|
||||
private $bloc_action = [];
|
||||
|
||||
private $bloc_content = '';
|
||||
private $stop_scan = false;
|
||||
/** @var array Allowed action for header */
|
||||
private $action_bloc = [];
|
||||
|
||||
/** @var string Parsed bloc */
|
||||
private $bloc = '';
|
||||
|
||||
/** @var boolean Stop parsing files */
|
||||
private $stop_scan = false;
|
||||
|
||||
protected function init(): bool
|
||||
{
|
||||
|
@ -106,9 +114,47 @@ class ImproveActionPhpheader extends ImproveAction
|
|||
|
||||
public function openModule(): ?bool
|
||||
{
|
||||
$this->replaceInfo();
|
||||
$bloc = trim($this->getSetting('bloc_content'));
|
||||
|
||||
return null;
|
||||
if (empty($bloc)) {
|
||||
$this->setWarning(__('bloc is empty'));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$bloc = trim(str_replace("\r\n", "\n", $bloc));
|
||||
|
||||
try {
|
||||
$this->bloc = (string) preg_replace_callback(
|
||||
// use \u in bloc content for first_upper_case
|
||||
'/(\\\u([a-z]{1}))/',
|
||||
function ($str) {
|
||||
return ucfirst($str[2]);
|
||||
},
|
||||
str_replace(
|
||||
$this->bloc_wildcards,
|
||||
[
|
||||
date('Y'),
|
||||
$this->module['id'],
|
||||
$this->module['name'],
|
||||
$this->module['author'],
|
||||
$this->module['type'],
|
||||
$this->core->auth->getInfo('user_cn'),
|
||||
$this->core->auth->getinfo('user_name'),
|
||||
$this->core->auth->getInfo('user_email'),
|
||||
$this->core->auth->getInfo('user_url')
|
||||
],
|
||||
(string) $bloc
|
||||
)
|
||||
);
|
||||
$this->setSuccess(__('Prepare header info'));
|
||||
|
||||
return null;
|
||||
} catch (Exception $e) {
|
||||
$this->setError(__('Failed to parse bloc'));
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function openDirectory(): ?bool
|
||||
|
@ -155,50 +201,13 @@ class ImproveActionPhpheader extends ImproveAction
|
|||
return true;
|
||||
}
|
||||
|
||||
private function replaceInfo()
|
||||
{
|
||||
$bloc = trim($this->getSetting('bloc_content'));
|
||||
|
||||
if (empty($bloc)) {
|
||||
$this->setWarning(__('bloc is empty'));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$bloc = trim(str_replace("\r\n", "\n", $bloc));
|
||||
|
||||
try {
|
||||
$this->bloc = preg_replace_callback(
|
||||
// use \u in bloc content for first_upper_case
|
||||
'/(\\\u([a-z]{1}))/',
|
||||
function ($str) {
|
||||
return ucfirst($str[2]);
|
||||
},
|
||||
str_replace(
|
||||
$this->bloc_wildcards,
|
||||
[
|
||||
date('Y'),
|
||||
$this->module['id'],
|
||||
$this->module['name'],
|
||||
$this->module['author'],
|
||||
$this->module['type'],
|
||||
$this->core->auth->getInfo('user_cn'),
|
||||
$this->core->auth->getinfo('user_name'),
|
||||
$this->core->auth->getInfo('user_email'),
|
||||
$this->core->auth->getInfo('user_url')
|
||||
],
|
||||
$bloc
|
||||
)
|
||||
);
|
||||
$this->setSuccess(__('Prepare header info'));
|
||||
} catch (Exception $e) {
|
||||
$this->setError(__('Failed to parse bloc'));
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function writeDocBloc($content)
|
||||
/**
|
||||
* Write bloc content in file content
|
||||
*
|
||||
* @param string $content Old content
|
||||
* @return string New content
|
||||
*/
|
||||
private function writeDocBloc(string $content): string
|
||||
{
|
||||
$res = preg_replace(
|
||||
'/^(\<\?php[\n|\r\n]+)/',
|
||||
|
@ -207,15 +216,21 @@ class ImproveActionPhpheader extends ImproveAction
|
|||
1,
|
||||
$count
|
||||
);
|
||||
if ($count) {
|
||||
if ($count && $res) {
|
||||
$res = str_replace("\n * \n", "\n *\n", $res);
|
||||
$this->setSuccess(__('Write new doc bloc content'));
|
||||
}
|
||||
|
||||
return $res;
|
||||
return (string) $res;
|
||||
}
|
||||
|
||||
private function deleteDocBloc($content)
|
||||
/**
|
||||
* Delete bloc content in file content
|
||||
*
|
||||
* @param string $content Old content
|
||||
* @return string New content
|
||||
*/
|
||||
private function deleteDocBloc(string $content): string
|
||||
{
|
||||
$res = preg_replace(
|
||||
'/^(\<\?php\s*[\n|\r\n]{0,1}\s*\/\*\*.*?\s*\*\/\s*[\n|\r\n]+)/msi',
|
||||
|
@ -228,10 +243,16 @@ class ImproveActionPhpheader extends ImproveAction
|
|||
$this->setSuccess(__('Delete old doc bloc content'));
|
||||
}
|
||||
|
||||
return $res;
|
||||
return (string) $res;
|
||||
}
|
||||
|
||||
private function deleteOldBloc($content)
|
||||
/**
|
||||
* Delete old style bloc content in file content
|
||||
*
|
||||
* @param string $content Old content
|
||||
* @return string New content
|
||||
*/
|
||||
private function deleteOldBloc(string $content): string
|
||||
{
|
||||
$res = preg_replace(
|
||||
'/((# -- BEGIN LICENSE BLOCK ([-]+))(.*?)(# -- END LICENSE BLOCK ([-]+))([\n|\r\n]{1,}))/msi',
|
||||
|
@ -244,6 +265,6 @@ class ImproveActionPhpheader extends ImproveAction
|
|||
$this->setSuccess(__('Delete old style bloc content'));
|
||||
}
|
||||
|
||||
return $res;
|
||||
return (string) $res;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
*/
|
||||
class ImproveActionZip extends ImproveAction
|
||||
{
|
||||
/** @var array List of excluded file pattern */
|
||||
public static $exclude = [
|
||||
'.',
|
||||
'..',
|
||||
|
@ -24,6 +25,8 @@ class ImproveActionZip extends ImproveAction
|
|||
'Thumbs.db',
|
||||
'_disabled'
|
||||
];
|
||||
|
||||
/** @var array Replacement wildcards */
|
||||
public static $filename_wildcards = [
|
||||
'%type%',
|
||||
'%id%',
|
||||
|
@ -32,8 +35,6 @@ class ImproveActionZip extends ImproveAction
|
|||
'%time%'
|
||||
];
|
||||
|
||||
private $package = '';
|
||||
|
||||
protected function init(): bool
|
||||
{
|
||||
$this->setProperties([
|
||||
|
@ -137,7 +138,7 @@ class ImproveActionZip extends ImproveAction
|
|||
return null;
|
||||
}
|
||||
|
||||
private function zipModule(string $file, array $exclude)
|
||||
private function zipModule(string $file, array $exclude): void
|
||||
{
|
||||
$file = str_replace(
|
||||
self::$filename_wildcards,
|
||||
|
@ -158,12 +159,12 @@ class ImproveActionZip extends ImproveAction
|
|||
if (file_exists($path) && empty($this->getSetting('pack_overwrite'))) {
|
||||
$this->setWarning(__('Destination filename already exists'));
|
||||
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
if (!is_dir(dirname($path)) || !is_writable(dirname($path))) {
|
||||
$this->setError(__('Destination path is not writable'));
|
||||
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
@set_time_limit(300);
|
||||
$fp = fopen($path, 'wb');
|
||||
|
@ -187,24 +188,34 @@ class ImproveActionZip extends ImproveAction
|
|||
|
||||
$this->setSuccess(sprintf(__('Zip module into "%s"'), $path));
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class ImproveZipFileZip extends fileZip
|
||||
{
|
||||
/** @var boolean Should remove comments from files */
|
||||
public static $remove_comment = false;
|
||||
|
||||
/**
|
||||
* Replace clearbrick fileZip::writeFile
|
||||
*
|
||||
* @param string $name Name
|
||||
* @param string $file File
|
||||
* @param int $size Size
|
||||
* @param int $mtime Mtime
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function writeFile($name, $file, $size, $mtime)
|
||||
{
|
||||
if (!isset($this->entries[$name])) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
$size = filesize($file);
|
||||
$this->memoryAllocate($size * 3);
|
||||
|
||||
$content = file_get_contents($file);
|
||||
$content = (string) file_get_contents($file);
|
||||
|
||||
if (self::$remove_comment && substr($file, -4) == '.php') {
|
||||
$content = self::removePHPComment($content);
|
||||
|
@ -212,7 +223,7 @@ class ImproveZipFileZip extends fileZip
|
|||
|
||||
$unc_len = strlen($content);
|
||||
$crc = crc32($content);
|
||||
$zdata = gzdeflate($content);
|
||||
$zdata = (string) gzdeflate($content);
|
||||
$c_len = strlen($zdata);
|
||||
|
||||
unset($content);
|
||||
|
@ -267,7 +278,7 @@ class ImproveZipFileZip extends fileZip
|
|||
$this->ctrl_dir[] = $cdrec;
|
||||
}
|
||||
|
||||
protected static function removePHPComment($content)
|
||||
protected static function removePHPComment(string $content): string
|
||||
{
|
||||
$comment = [T_COMMENT];
|
||||
if (defined('T_DOC_COMMENT')) {
|
||||
|
|
35
index.php
35
index.php
|
@ -31,8 +31,8 @@ if (!empty($_POST['save_preferences'])) {
|
|||
$preferences[$type] = [];
|
||||
if (!empty($_POST['actions'])) {
|
||||
foreach ($improve->modules() as $action) {
|
||||
if (in_array($type, $action->types) && in_array($action->id, $_POST['actions'])) {
|
||||
$preferences[$type][] = $action->id;
|
||||
if (in_array($type, $action->get('types')) && in_array($action->get('id'), $_POST['actions'])) {
|
||||
$preferences[$type][] = $action->get('id');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -141,18 +141,18 @@ if (!empty($_REQUEST['config'])) {
|
|||
$back_url = $_REQUEST['redir'] ?? $core->adminurl->get('admin.plugin.improve', ['type' => $type]);
|
||||
|
||||
if (null !== $action) {
|
||||
$redir = $_REQUEST['redir'] ?? $core->adminurl->get('admin.plugin.improve', ['type' => $type, 'config' => $action->id]);
|
||||
$redir = $_REQUEST['redir'] ?? $core->adminurl->get('admin.plugin.improve', ['type' => $type, 'config' => $action->get('id')]);
|
||||
$res = $action->configure($redir);
|
||||
|
||||
echo '
|
||||
<h3>' . sprintf(__('Configure module "%s"'), $action->name) . '</h3>
|
||||
<h3>' . sprintf(__('Configure module "%s"'), $action->get('name')) . '</h3>
|
||||
<p><a class="back" href="' . $back_url . '">' . __('Back') . '</a></p>
|
||||
<p class="info">' . html::escapeHTML($action->desc) . '</p>
|
||||
<p class="info">' . html::escapeHTML($action->get('desc')) . '</p>
|
||||
<form action="' . $core->adminurl->get('admin.plugin.improve') . '" method="post" id="form-actions">' .
|
||||
(empty($res) ? '<p class="message">' . __('Nothing to configure') . '</p>' : $res) . '
|
||||
<p class="clear"><input type="submit" name="save" value="' . __('Save') . '" />' .
|
||||
form::hidden('type', $type) .
|
||||
form::hidden('config', $action->id) .
|
||||
form::hidden('config', $action->get('id')) .
|
||||
form::hidden('redir', $redir) .
|
||||
$core->formNonce() . '</p>' .
|
||||
'</form>';
|
||||
|
@ -173,31 +173,31 @@ if (!empty($_REQUEST['config'])) {
|
|||
(DC_DEBUG ? '<th scope="col">' . __('Priority') . '</td>' : '') .
|
||||
'</tr></thead><tbody>';
|
||||
foreach ($improve->modules() as $action) {
|
||||
if (!in_array($type, $action->types)) {
|
||||
if (!in_array($type, $action->get('types'))) {
|
||||
continue;
|
||||
}
|
||||
echo
|
||||
'<tr class="line' . ($action->isConfigured() ? '' : ' offline') . '">' .
|
||||
'<td class="minimal">' . form::checkbox(
|
||||
['actions[]',
|
||||
'action_' . $action->id],
|
||||
$action->id,
|
||||
in_array($action->id, $preferences[$type]) && $action->isConfigured(),
|
||||
'action_' . $action->get('id')],
|
||||
$action->get('id'),
|
||||
in_array($action->get('id'), $preferences[$type]) && $action->isConfigured(),
|
||||
'',
|
||||
'',
|
||||
!$action->isConfigured()
|
||||
) . '</td>' .
|
||||
'<td class="minimal nowrap">' .
|
||||
'<label for="action_' . $action->id . '" class="classic">' . html::escapeHTML($action->name) . '</label>' .
|
||||
'<label for="action_' . $action->get('id') . '" class="classic">' . html::escapeHTML($action->get('name')) . '</label>' .
|
||||
'</td>' .
|
||||
'<td class="maximal">' . $action->desc . '</td>' .
|
||||
'<td class="maximal">' . $action->get('desc') . '</td>' .
|
||||
'<td class="minimal nowrap modules">' . (
|
||||
false === $action->config ? '' :
|
||||
false === $action->get('config') ? '' :
|
||||
'<a class="module-config" href="' .
|
||||
(true === $action->config ? $core->adminurl->get('admin.plugin.improve', ['type' => $type, 'config' => $action->id]) : $action->config) .
|
||||
'" title="' . sprintf(__("Configure action '%s'"), $action->name) . '">' . __('Configure') . '</a>'
|
||||
(true === $action->get('config') ? $core->adminurl->get('admin.plugin.improve', ['type' => $type, 'config' => $action->get('id')]) : $action->get('config')) .
|
||||
'" title="' . sprintf(__("Configure action '%s'"), $action->get('name')) . '">' . __('Configure') . '</a>'
|
||||
) . '</td>' .
|
||||
(DC_DEBUG ? '<td class="minimal"><span class="debug">' . $action->priority . '</span></td>' : '') .
|
||||
(DC_DEBUG ? '<td class="minimal"><span class="debug">' . $action->get('priority') . '</span></td>' : '') .
|
||||
'</tr>';
|
||||
}
|
||||
|
||||
|
@ -226,7 +226,8 @@ if (!empty($_REQUEST['config'])) {
|
|||
foreach ($types as $type => $tools) {
|
||||
echo '<div class="' . $type . '"><ul>';
|
||||
foreach ($tools as $tool => $msgs) {
|
||||
echo '<li>' . $improve->module($tool)->name . '<ul>';
|
||||
$a = $improve->module($tool);
|
||||
echo '<li>' . ($a !== null ? $a->get('name') : 'unknow') . '<ul>';
|
||||
foreach ($msgs as $msg) {
|
||||
echo '<li>' . $msg . '</li>';
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue