Address PHPStan warning in 'classes/pref/prefs.php'.

Also update 'select_hash' and 'select_tag' values param, which can have int or string keys.
This commit is contained in:
wn_ 2021-11-14 20:12:37 +00:00
parent b8f0627a0e
commit abab2a94e8
2 changed files with 145 additions and 106 deletions

View File

@ -2,12 +2,21 @@
use chillerlan\QRCode; use chillerlan\QRCode;
class Pref_Prefs extends Handler_Protected { class Pref_Prefs extends Handler_Protected {
// TODO: class properties can be switched to PHP typing if/when the minimum PHP_VERSION is raised to 7.4.0+
/** @var array<Prefs::*, array<int, string>> */
private $pref_help = []; private $pref_help = [];
/** @var array<string, array<int, string>> pref items are Prefs::*|Pref_Prefs::BLOCK_SEPARATOR (PHPStan was complaining) */
private $pref_item_map = []; private $pref_item_map = [];
/** @var array<string, string> */
private $pref_help_bottom = []; private $pref_help_bottom = [];
/** @var array<int, string> */
private $pref_blacklist = []; private $pref_blacklist = [];
private const BLOCK_SEPARATOR = 'BLOCK_SEPARATOR';
const PI_RES_ALREADY_INSTALLED = "PI_RES_ALREADY_INSTALLED"; const PI_RES_ALREADY_INSTALLED = "PI_RES_ALREADY_INSTALLED";
const PI_RES_SUCCESS = "PI_RES_SUCCESS"; const PI_RES_SUCCESS = "PI_RES_SUCCESS";
const PI_ERR_NO_CLASS = "PI_ERR_NO_CLASS"; const PI_ERR_NO_CLASS = "PI_ERR_NO_CLASS";
@ -17,6 +26,7 @@ class Pref_Prefs extends Handler_Protected {
const PI_ERR_PLUGIN_NOT_FOUND = "PI_ERR_PLUGIN_NOT_FOUND"; const PI_ERR_PLUGIN_NOT_FOUND = "PI_ERR_PLUGIN_NOT_FOUND";
const PI_ERR_NO_WORKDIR = "PI_ERR_NO_WORKDIR"; const PI_ERR_NO_WORKDIR = "PI_ERR_NO_WORKDIR";
/** @param string $method */
function csrf_ignore($method) : bool { function csrf_ignore($method) : bool {
$csrf_ignored = array("index", "updateself", "otpqrcode"); $csrf_ignored = array("index", "updateself", "otpqrcode");
@ -30,35 +40,35 @@ class Pref_Prefs extends Handler_Protected {
__('General') => [ __('General') => [
Prefs::USER_LANGUAGE, Prefs::USER_LANGUAGE,
Prefs::USER_TIMEZONE, Prefs::USER_TIMEZONE,
'BLOCK_SEPARATOR', self::BLOCK_SEPARATOR,
Prefs::USER_CSS_THEME, Prefs::USER_CSS_THEME,
'BLOCK_SEPARATOR', self::BLOCK_SEPARATOR,
Prefs::ENABLE_API_ACCESS, Prefs::ENABLE_API_ACCESS,
], ],
__('Feeds') => [ __('Feeds') => [
Prefs::DEFAULT_UPDATE_INTERVAL, Prefs::DEFAULT_UPDATE_INTERVAL,
Prefs::FRESH_ARTICLE_MAX_AGE, Prefs::FRESH_ARTICLE_MAX_AGE,
Prefs::DEFAULT_SEARCH_LANGUAGE, Prefs::DEFAULT_SEARCH_LANGUAGE,
'BLOCK_SEPARATOR', self::BLOCK_SEPARATOR,
Prefs::ENABLE_FEED_CATS, Prefs::ENABLE_FEED_CATS,
'BLOCK_SEPARATOR', self::BLOCK_SEPARATOR,
Prefs::CONFIRM_FEED_CATCHUP, Prefs::CONFIRM_FEED_CATCHUP,
Prefs::ON_CATCHUP_SHOW_NEXT_FEED, Prefs::ON_CATCHUP_SHOW_NEXT_FEED,
'BLOCK_SEPARATOR', self::BLOCK_SEPARATOR,
Prefs::HIDE_READ_FEEDS, Prefs::HIDE_READ_FEEDS,
Prefs::HIDE_READ_SHOWS_SPECIAL, Prefs::HIDE_READ_SHOWS_SPECIAL,
], ],
__('Articles') => [ __('Articles') => [
Prefs::PURGE_OLD_DAYS, Prefs::PURGE_OLD_DAYS,
Prefs::PURGE_UNREAD_ARTICLES, Prefs::PURGE_UNREAD_ARTICLES,
'BLOCK_SEPARATOR', self::BLOCK_SEPARATOR,
Prefs::COMBINED_DISPLAY_MODE, Prefs::COMBINED_DISPLAY_MODE,
Prefs::CDM_EXPANDED, Prefs::CDM_EXPANDED,
Prefs::CDM_ENABLE_GRID, Prefs::CDM_ENABLE_GRID,
'BLOCK_SEPARATOR', self::BLOCK_SEPARATOR,
Prefs::CDM_AUTO_CATCHUP, Prefs::CDM_AUTO_CATCHUP,
Prefs::VFEED_GROUP_BY_FEED, Prefs::VFEED_GROUP_BY_FEED,
'BLOCK_SEPARATOR', self::BLOCK_SEPARATOR,
Prefs::SHOW_CONTENT_PREVIEW, Prefs::SHOW_CONTENT_PREVIEW,
Prefs::STRIP_IMAGES, Prefs::STRIP_IMAGES,
], ],
@ -69,12 +79,12 @@ class Pref_Prefs extends Handler_Protected {
], ],
__('Advanced') => [ __('Advanced') => [
Prefs::BLACKLISTED_TAGS, Prefs::BLACKLISTED_TAGS,
'BLOCK_SEPARATOR', self::BLOCK_SEPARATOR,
Prefs::LONG_DATE_FORMAT, Prefs::LONG_DATE_FORMAT,
Prefs::SHORT_DATE_FORMAT, Prefs::SHORT_DATE_FORMAT,
'BLOCK_SEPARATOR', self::BLOCK_SEPARATOR,
Prefs::SSL_CERT_SERIAL, Prefs::SSL_CERT_SERIAL,
'BLOCK_SEPARATOR', self::BLOCK_SEPARATOR,
Prefs::DISABLE_CONDITIONAL_COUNTERS, Prefs::DISABLE_CONDITIONAL_COUNTERS,
Prefs::HEADLINES_NO_DISTINCT, Prefs::HEADLINES_NO_DISTINCT,
], ],
@ -127,7 +137,7 @@ class Pref_Prefs extends Handler_Protected {
]; ];
} }
function changepassword() { function changepassword(): void {
if (Config::get(Config::FORBID_PASSWORD_CHANGES)) { if (Config::get(Config::FORBID_PASSWORD_CHANGES)) {
print "ERROR: ".format_error("Access forbidden."); print "ERROR: ".format_error("Access forbidden.");
@ -173,7 +183,7 @@ class Pref_Prefs extends Handler_Protected {
} }
} }
function saveconfig() { function saveconfig(): void {
$boolean_prefs = explode(",", clean($_POST["boolean_prefs"])); $boolean_prefs = explode(",", clean($_POST["boolean_prefs"]));
foreach ($boolean_prefs as $pref) { foreach ($boolean_prefs as $pref) {
@ -223,7 +233,7 @@ class Pref_Prefs extends Handler_Protected {
} }
} }
function changePersonalData() { function changePersonalData(): void {
$user = ORM::for_table('ttrss_users')->find_one($_SESSION['uid']); $user = ORM::for_table('ttrss_users')->find_one($_SESSION['uid']);
$new_email = clean($_POST['email']); $new_email = clean($_POST['email']);
@ -264,13 +274,13 @@ class Pref_Prefs extends Handler_Protected {
print __("Your personal data has been saved."); print __("Your personal data has been saved.");
} }
function resetconfig() { function resetconfig(): void {
Prefs::reset($_SESSION["uid"], $_SESSION["profile"]); Prefs::reset($_SESSION["uid"], $_SESSION["profile"]);
print "PREFS_NEED_RELOAD"; print "PREFS_NEED_RELOAD";
} }
private function index_auth_personal() { private function index_auth_personal(): void {
$user = ORM::for_table('ttrss_users')->find_one($_SESSION['uid']); $user = ORM::for_table('ttrss_users')->find_one($_SESSION['uid']);
@ -310,7 +320,7 @@ class Pref_Prefs extends Handler_Protected {
<?php <?php
} }
private function index_auth_password() { private function index_auth_password(): void {
if ($_SESSION["auth_module"]) { if ($_SESSION["auth_module"]) {
$authenticator = PluginHost::getInstance()->get_plugin($_SESSION["auth_module"]); $authenticator = PluginHost::getInstance()->get_plugin($_SESSION["auth_module"]);
} else { } else {
@ -385,7 +395,7 @@ class Pref_Prefs extends Handler_Protected {
} }
} }
private function index_auth_app_passwords() { private function index_auth_app_passwords(): void {
print_notice("Separate passwords used for API clients. Required if you enable OTP."); print_notice("Separate passwords used for API clients. Required if you enable OTP.");
?> ?>
@ -409,7 +419,7 @@ class Pref_Prefs extends Handler_Protected {
<?php <?php
} }
private function index_auth_2fa() { private function index_auth_2fa(): void {
$otp_enabled = UserHelper::is_otp_enabled($_SESSION["uid"]); $otp_enabled = UserHelper::is_otp_enabled($_SESSION["uid"]);
if ($_SESSION["auth_module"] == "auth_internal") { if ($_SESSION["auth_module"] == "auth_internal") {
@ -515,7 +525,7 @@ class Pref_Prefs extends Handler_Protected {
} }
} }
function index_auth() { function index_auth(): void {
?> ?>
<div dojoType='dijit.layout.TabContainer'> <div dojoType='dijit.layout.TabContainer'>
<div dojoType='dijit.layout.ContentPane' title="<?= __('Personal data') ?>"> <div dojoType='dijit.layout.ContentPane' title="<?= __('Personal data') ?>">
@ -534,35 +544,38 @@ class Pref_Prefs extends Handler_Protected {
<?php <?php
} }
private function index_prefs_list() { private function index_prefs_list(): void {
$profile = $_SESSION["profile"] ?? null; $profile = $_SESSION["profile"] ?? null;
if ($profile) { if ($profile) {
print_notice(__("Some preferences are only available in default profile.")); print_notice(__("Some preferences are only available in default profile."));
} }
/** @var array<string, array{'type_hint': Config::T_*, 'value': bool|int|string, 'help_text': string, 'short_desc': string}> */
$prefs_available = []; $prefs_available = [];
/** @var array<int, string> */
$listed_boolean_prefs = []; $listed_boolean_prefs = [];
foreach (Prefs::get_all($_SESSION["uid"], $profile) as $line) { foreach (Prefs::get_all($_SESSION["uid"], $profile) as $pref) {
if (in_array($line["pref_name"], $this->pref_blacklist)) { if (in_array($pref["pref_name"], $this->pref_blacklist)) {
continue; continue;
} }
if ($profile && in_array($line["pref_name"], Prefs::_PROFILE_BLACKLIST)) { if ($profile && in_array($pref["pref_name"], Prefs::_PROFILE_BLACKLIST)) {
continue; continue;
} }
$pref_name = $line["pref_name"]; $pref_name = $pref["pref_name"];
$short_desc = $this->_get_short_desc($pref_name); $short_desc = $this->_get_short_desc($pref_name);
if (!$short_desc) if (!$short_desc)
continue; continue;
$prefs_available[$pref_name] = [ $prefs_available[$pref_name] = [
'type_hint' => $line['type_hint'], 'type_hint' => $pref['type_hint'],
'value' => $line['value'], 'value' => $pref['value'],
'help_text' => $this->_get_help_text($pref_name), 'help_text' => $this->_get_help_text($pref_name),
'short_desc' => $short_desc 'short_desc' => $short_desc
]; ];
@ -574,12 +587,12 @@ class Pref_Prefs extends Handler_Protected {
foreach ($this->pref_item_map[$section] as $pref_name) { foreach ($this->pref_item_map[$section] as $pref_name) {
if ($pref_name == 'BLOCK_SEPARATOR' && !$profile) { if ($pref_name == self::BLOCK_SEPARATOR && !$profile) {
print "<hr/>"; print "<hr/>";
continue; continue;
} }
if ($pref_name == "DEFAULT_SEARCH_LANGUAGE" && Config::get(Config::DB_TYPE) != "pgsql") { if ($pref_name == Prefs::DEFAULT_SEARCH_LANGUAGE && Config::get(Config::DB_TYPE) != "pgsql") {
continue; continue;
} }
@ -596,17 +609,17 @@ class Pref_Prefs extends Handler_Protected {
$value = $item['value']; $value = $item['value'];
$type_hint = $item['type_hint']; $type_hint = $item['type_hint'];
if ($pref_name == "USER_LANGUAGE") { if ($pref_name == Prefs::USER_LANGUAGE) {
print \Controls\select_hash($pref_name, $value, get_translations(), print \Controls\select_hash($pref_name, $value, get_translations(),
["style" => 'width : 220px; margin : 0px']); ["style" => 'width : 220px; margin : 0px']);
} else if ($pref_name == "USER_TIMEZONE") { } else if ($pref_name == Prefs::USER_TIMEZONE) {
$timezones = explode("\n", file_get_contents("lib/timezones.txt")); $timezones = explode("\n", file_get_contents("lib/timezones.txt"));
print \Controls\select_tag($pref_name, $value, $timezones, ["dojoType" => "dijit.form.FilteringSelect"]); print \Controls\select_tag($pref_name, $value, $timezones, ["dojoType" => "dijit.form.FilteringSelect"]);
} else if ($pref_name == "BLACKLISTED_TAGS") { # TODO: other possible <textarea> prefs go here } else if ($pref_name == Prefs::BLACKLISTED_TAGS) { # TODO: other possible <textarea> prefs go here
print "<div>"; print "<div>";
@ -618,7 +631,7 @@ class Pref_Prefs extends Handler_Protected {
print "</div>"; print "</div>";
} else if ($pref_name == "USER_CSS_THEME") { } else if ($pref_name == Prefs::USER_CSS_THEME) {
$theme_files = array_map("basename", $theme_files = array_map("basename",
array_merge(glob("themes/*.php"), array_merge(glob("themes/*.php"),
@ -642,13 +655,13 @@ class Pref_Prefs extends Handler_Protected {
<?php <?php
} else if ($pref_name == "DEFAULT_UPDATE_INTERVAL") { } else if ($pref_name == Prefs::DEFAULT_UPDATE_INTERVAL) {
global $update_intervals_nodefault; global $update_intervals_nodefault;
print \Controls\select_hash($pref_name, $value, $update_intervals_nodefault); print \Controls\select_hash($pref_name, $value, $update_intervals_nodefault);
} else if ($pref_name == "DEFAULT_SEARCH_LANGUAGE") { } else if ($pref_name == Prefs::DEFAULT_SEARCH_LANGUAGE) {
print \Controls\select_tag($pref_name, $value, Pref_Feeds::get_ts_languages()); print \Controls\select_tag($pref_name, $value, Pref_Feeds::get_ts_languages());
@ -656,7 +669,7 @@ class Pref_Prefs extends Handler_Protected {
array_push($listed_boolean_prefs, $pref_name); array_push($listed_boolean_prefs, $pref_name);
if ($pref_name == "PURGE_UNREAD_ARTICLES" && Config::get(Config::FORCE_ARTICLE_PURGE) != 0) { if ($pref_name == Prefs::PURGE_UNREAD_ARTICLES && Config::get(Config::FORCE_ARTICLE_PURGE) != 0) {
$is_disabled = true; $is_disabled = true;
$is_checked = true; $is_checked = true;
} else { } else {
@ -672,10 +685,10 @@ class Pref_Prefs extends Handler_Protected {
['onclick' => 'Helpers.Digest.preview()', 'style' => 'margin-left : 10px']); ['onclick' => 'Helpers.Digest.preview()', 'style' => 'margin-left : 10px']);
} }
} else if (in_array($pref_name, ['FRESH_ARTICLE_MAX_AGE', } else if (in_array($pref_name, [Prefs::FRESH_ARTICLE_MAX_AGE,
'PURGE_OLD_DAYS', 'LONG_DATE_FORMAT', 'SHORT_DATE_FORMAT'])) { Prefs::PURGE_OLD_DAYS, Prefs::LONG_DATE_FORMAT, Prefs::SHORT_DATE_FORMAT])) {
if ($pref_name == "PURGE_OLD_DAYS" && Config::get(Config::FORCE_ARTICLE_PURGE) != 0) { if ($pref_name == Prefs::PURGE_OLD_DAYS && Config::get(Config::FORCE_ARTICLE_PURGE) != 0) {
$attributes = ["disabled" => true, "required" => true]; $attributes = ["disabled" => true, "required" => true];
$value = Config::get(Config::FORCE_ARTICLE_PURGE); $value = Config::get(Config::FORCE_ARTICLE_PURGE);
} else { } else {
@ -687,7 +700,7 @@ class Pref_Prefs extends Handler_Protected {
else else
print \Controls\input_tag($pref_name, $value, "text", $attributes); print \Controls\input_tag($pref_name, $value, "text", $attributes);
} else if ($pref_name == "SSL_CERT_SERIAL") { } else if ($pref_name == Prefs::SSL_CERT_SERIAL) {
print \Controls\input_tag($pref_name, $value, "text", ["readonly" => true], "SSL_CERT_SERIAL"); print \Controls\input_tag($pref_name, $value, "text", ["readonly" => true], "SSL_CERT_SERIAL");
@ -727,7 +740,7 @@ class Pref_Prefs extends Handler_Protected {
print \Controls\hidden_tag("boolean_prefs", htmlspecialchars(join(",", $listed_boolean_prefs))); print \Controls\hidden_tag("boolean_prefs", htmlspecialchars(join(",", $listed_boolean_prefs)));
} }
private function index_prefs() { private function index_prefs(): void {
?> ?>
<form dojoType='dijit.form.Form' id='changeSettingsForm'> <form dojoType='dijit.form.Form' id='changeSettingsForm'>
<?= \Controls\hidden_tag("op", "pref-prefs") ?> <?= \Controls\hidden_tag("op", "pref-prefs") ?>
@ -783,7 +796,7 @@ class Pref_Prefs extends Handler_Protected {
<?php <?php
} }
function getPluginsList() { function getPluginsList(): void {
$system_enabled = array_map("trim", explode(",", (string)Config::get(Config::PLUGINS))); $system_enabled = array_map("trim", explode(",", (string)Config::get(Config::PLUGINS)));
$user_enabled = array_map("trim", explode(",", get_pref(Prefs::_ENABLED_PLUGINS))); $user_enabled = array_map("trim", explode(",", get_pref(Prefs::_ENABLED_PLUGINS)));
@ -816,7 +829,7 @@ class Pref_Prefs extends Handler_Protected {
print json_encode(['plugins' => $rv, 'is_admin' => $_SESSION['access_level'] >= UserHelper::ACCESS_LEVEL_ADMIN]); print json_encode(['plugins' => $rv, 'is_admin' => $_SESSION['access_level'] >= UserHelper::ACCESS_LEVEL_ADMIN]);
} }
function index_plugins() { function index_plugins(): void {
?> ?>
<form dojoType="dijit.form.Form" id="changePluginsForm"> <form dojoType="dijit.form.Form" id="changePluginsForm">
@ -912,7 +925,7 @@ class Pref_Prefs extends Handler_Protected {
<?php <?php
} }
function index() { function index(): void {
?> ?>
<div dojoType='dijit.layout.AccordionContainer' region='center'> <div dojoType='dijit.layout.AccordionContainer' region='center'>
<div dojoType='dijit.layout.AccordionPane' title="<i class='material-icons'>person</i> <?= __('Personal data / Authentication')?>"> <div dojoType='dijit.layout.AccordionPane' title="<i class='material-icons'>person</i> <?= __('Personal data / Authentication')?>">
@ -937,7 +950,7 @@ class Pref_Prefs extends Handler_Protected {
<?php <?php
} }
function _get_otp_qrcode_img() { function _get_otp_qrcode_img(): ?string {
$secret = UserHelper::get_otp_secret($_SESSION["uid"]); $secret = UserHelper::get_otp_secret($_SESSION["uid"]);
$login = UserHelper::get_login_by_id($_SESSION["uid"]); $login = UserHelper::get_login_by_id($_SESSION["uid"]);
@ -949,15 +962,16 @@ class Pref_Prefs extends Handler_Protected {
return $qrcode->render($otpurl); return $qrcode->render($otpurl);
} }
return false; return null;
} }
function otpenable() { function otpenable(): void {
$password = clean($_REQUEST["password"]); $password = clean($_REQUEST["password"]);
$otp_check = clean($_REQUEST["otp"]); $otp_check = clean($_REQUEST["otp"]);
$authenticator = PluginHost::getInstance()->get_plugin($_SESSION["auth_module"]); $authenticator = PluginHost::getInstance()->get_plugin($_SESSION["auth_module"]);
/** @var Auth_Internal|false $authenticator -- this is only here to make check_password() visible to static analyzer */
if ($authenticator->check_password($_SESSION["uid"], $password)) { if ($authenticator->check_password($_SESSION["uid"], $password)) {
if (UserHelper::enable_otp($_SESSION["uid"], $otp_check)) { if (UserHelper::enable_otp($_SESSION["uid"], $otp_check)) {
print "OK"; print "OK";
@ -969,9 +983,10 @@ class Pref_Prefs extends Handler_Protected {
} }
} }
function otpdisable() { function otpdisable(): void {
$password = clean($_REQUEST["password"]); $password = clean($_REQUEST["password"]);
/** @var Auth_Internal|false $authenticator -- this is only here to make check_password() visible to static analyzer */
$authenticator = PluginHost::getInstance()->get_plugin($_SESSION["auth_module"]); $authenticator = PluginHost::getInstance()->get_plugin($_SESSION["auth_module"]);
if ($authenticator->check_password($_SESSION["uid"], $password)) { if ($authenticator->check_password($_SESSION["uid"], $password)) {
@ -1008,38 +1023,42 @@ class Pref_Prefs extends Handler_Protected {
} }
function setplugins() { function setplugins(): void {
$plugins = array_filter($_REQUEST["plugins"], 'clean') ?? []; $plugins = array_filter($_REQUEST["plugins"], 'clean') ?? [];
set_pref(Prefs::_ENABLED_PLUGINS, implode(",", $plugins)); set_pref(Prefs::_ENABLED_PLUGINS, implode(",", $plugins));
} }
function _get_plugin_version(Plugin $plugin) { function _get_plugin_version(Plugin $plugin): string {
$about = $plugin->about(); $about = $plugin->about();
if (!empty($about[0])) { if (!empty($about[0])) {
return T_sprintf("v%.2f, by %s", $about[0], $about[2]); return T_sprintf("v%.2f, by %s", $about[0], $about[2]);
} else {
$ref = new ReflectionClass(get_class($plugin));
$plugin_dir = dirname($ref->getFileName());
if (basename($plugin_dir) == "plugins") {
return "";
}
if (is_dir("$plugin_dir/.git")) {
$ver = Config::get_version_from_git($plugin_dir);
return $ver["status"] == 0 ? T_sprintf("v%s, by %s", $ver["version"], $about[2]) : $ver["version"];
}
} }
$ref = new ReflectionClass(get_class($plugin));
$plugin_dir = dirname($ref->getFileName());
if (basename($plugin_dir) == "plugins") {
return "";
}
if (is_dir("$plugin_dir/.git")) {
$ver = Config::get_version_from_git($plugin_dir);
return $ver["status"] == 0 ? T_sprintf("v%s, by %s", $ver["version"], $about[2]) : $ver["version"];
}
return "";
} }
static function _get_updated_plugins() { /**
* @return array<int, array{'plugin': string, 'rv': array{'stdout': false|string, 'stderr': false|string, 'git_status': int, 'need_update': bool}|null}>
*/
static function _get_updated_plugins(): array {
$root_dir = dirname(dirname(__DIR__)); # we're in classes/pref/ $root_dir = dirname(dirname(__DIR__)); # we're in classes/pref/
$plugin_dirs = array_filter(glob("$root_dir/plugins.local/*"), "is_dir"); $plugin_dirs = array_filter(glob("$root_dir/plugins.local/*"), "is_dir");
$rv = []; $rv = [];
foreach ($plugin_dirs as $dir) { foreach ($plugin_dirs as $dir) {
@ -1057,7 +1076,10 @@ class Pref_Prefs extends Handler_Protected {
return $rv; return $rv;
} }
private static function _plugin_needs_update($root_dir, $plugin_name) { /**
* @return array{'stdout': false|string, 'stderr': false|string, 'git_status': int, 'need_update': bool}|null
*/
private static function _plugin_needs_update(string $root_dir, string $plugin_name): ?array {
$plugin_dir = "$root_dir/plugins.local/" . basename($plugin_name); $plugin_dir = "$root_dir/plugins.local/" . basename($plugin_name);
$rv = null; $rv = null;
@ -1086,7 +1108,10 @@ class Pref_Prefs extends Handler_Protected {
} }
private function _update_plugin($root_dir, $plugin_name) { /**
* @return array{'stdout': false|string, 'stderr': false|string, 'git_status': int}
*/
private function _update_plugin(string $root_dir, string $plugin_name): array {
$plugin_dir = "$root_dir/plugins.local/" . basename($plugin_name); $plugin_dir = "$root_dir/plugins.local/" . basename($plugin_name);
$rv = []; $rv = [];
@ -1112,7 +1137,7 @@ class Pref_Prefs extends Handler_Protected {
} }
// https://gist.github.com/mindplay-dk/a4aad91f5a4f1283a5e2#gistcomment-2036828 // https://gist.github.com/mindplay-dk/a4aad91f5a4f1283a5e2#gistcomment-2036828
private function _recursive_rmdir(string $dir, bool $keep_root = false) { private function _recursive_rmdir(string $dir, bool $keep_root = false): bool {
// Handle bad arguments. // Handle bad arguments.
if (empty($dir) || !file_exists($dir)) { if (empty($dir) || !file_exists($dir)) {
return true; // No such file/dir$dir exists. return true; // No such file/dir$dir exists.
@ -1137,7 +1162,7 @@ class Pref_Prefs extends Handler_Protected {
} }
// https://stackoverflow.com/questions/7153000/get-class-name-from-file // https://stackoverflow.com/questions/7153000/get-class-name-from-file
private function _get_class_name_from_file($file) { private function _get_class_name_from_file(string $file): string {
$tokens = token_get_all(file_get_contents($file)); $tokens = token_get_all(file_get_contents($file));
for ($i = 0; $i < count($tokens); $i++) { for ($i = 0; $i < count($tokens); $i++) {
@ -1149,9 +1174,11 @@ class Pref_Prefs extends Handler_Protected {
} }
} }
} }
return "";
} }
function uninstallPlugin() { function uninstallPlugin(): void {
if ($_SESSION["access_level"] >= UserHelper::ACCESS_LEVEL_ADMIN) { if ($_SESSION["access_level"] >= UserHelper::ACCESS_LEVEL_ADMIN) {
$plugin_name = basename(clean($_REQUEST['plugin'])); $plugin_name = basename(clean($_REQUEST['plugin']));
$status = 0; $status = 0;
@ -1166,7 +1193,7 @@ class Pref_Prefs extends Handler_Protected {
} }
} }
function installPlugin() { function installPlugin(): void {
if ($_SESSION["access_level"] >= UserHelper::ACCESS_LEVEL_ADMIN && Config::get(Config::ENABLE_PLUGIN_INSTALLER)) { if ($_SESSION["access_level"] >= UserHelper::ACCESS_LEVEL_ADMIN && Config::get(Config::ENABLE_PLUGIN_INSTALLER)) {
$plugin_name = basename(clean($_REQUEST['plugin'])); $plugin_name = basename(clean($_REQUEST['plugin']));
$all_plugins = $this->_get_available_plugins(); $all_plugins = $this->_get_available_plugins();
@ -1251,47 +1278,59 @@ class Pref_Prefs extends Handler_Protected {
} }
} }
private function _get_available_plugins() { /**
* @return array<int, array{'name': string, 'description': string, 'topics': array<int, string>, 'html_url': string, 'clone_url': string, 'last_update': string}>
*/
private function _get_available_plugins(): array {
if ($_SESSION["access_level"] >= UserHelper::ACCESS_LEVEL_ADMIN && Config::get(Config::ENABLE_PLUGIN_INSTALLER)) { if ($_SESSION["access_level"] >= UserHelper::ACCESS_LEVEL_ADMIN && Config::get(Config::ENABLE_PLUGIN_INSTALLER)) {
return json_decode(UrlHelper::fetch(['url' => 'https://tt-rss.org/plugins.json']), true); $content = json_decode(UrlHelper::fetch(['url' => 'https://tt-rss.org/plugins.json']), true);
if ($content) {
return $content;
}
} }
return [];
} }
function getAvailablePlugins() {
function getAvailablePlugins(): void {
if ($_SESSION["access_level"] >= UserHelper::ACCESS_LEVEL_ADMIN) { if ($_SESSION["access_level"] >= UserHelper::ACCESS_LEVEL_ADMIN) {
print json_encode($this->_get_available_plugins()); print json_encode($this->_get_available_plugins());
} else {
print "[]";
} }
} }
function checkForPluginUpdates() { function checkForPluginUpdates(): void {
if ($_SESSION["access_level"] >= UserHelper::ACCESS_LEVEL_ADMIN && Config::get(Config::CHECK_FOR_UPDATES) && Config::get(Config::CHECK_FOR_PLUGIN_UPDATES)) { if ($_SESSION["access_level"] >= UserHelper::ACCESS_LEVEL_ADMIN && Config::get(Config::CHECK_FOR_UPDATES) && Config::get(Config::CHECK_FOR_PLUGIN_UPDATES)) {
$plugin_name = $_REQUEST["name"] ?? ""; $plugin_name = $_REQUEST["name"] ?? "";
$root_dir = dirname(dirname(__DIR__)); # we're in classes/pref/ $root_dir = dirname(dirname(__DIR__)); # we're in classes/pref/
if (!empty($plugin_name)) { $rv = empty($plugin_name) ? self::_get_updated_plugins() : [
$rv = [["plugin" => $plugin_name, "rv" => self::_plugin_needs_update($root_dir, $plugin_name)]]; ["plugin" => $plugin_name, "rv" => self::_plugin_needs_update($root_dir, $plugin_name)],
} else { ];
$rv = self::_get_updated_plugins();
}
print json_encode($rv); print json_encode($rv);
} }
} }
function updateLocalPlugins() { function updateLocalPlugins(): void {
if ($_SESSION["access_level"] >= UserHelper::ACCESS_LEVEL_ADMIN) { if ($_SESSION["access_level"] >= UserHelper::ACCESS_LEVEL_ADMIN) {
$plugins = explode(",", $_REQUEST["plugins"] ?? ""); $plugins = explode(",", $_REQUEST["plugins"] ?? "");
if ($plugins !== false) {
$plugins = array_filter($plugins, 'strlen');
}
# we're in classes/pref/ # we're in classes/pref/
$root_dir = dirname(dirname(__DIR__)); $root_dir = dirname(dirname(__DIR__));
$rv = []; $rv = [];
if (count($plugins) > 0) { if ($plugins) {
foreach ($plugins as $plugin_name) { foreach ($plugins as $plugin_name) {
array_push($rv, ["plugin" => $plugin_name, "rv" => $this->_update_plugin($root_dir, $plugin_name)]); array_push($rv, ["plugin" => $plugin_name, "rv" => $this->_update_plugin($root_dir, $plugin_name)]);
} }
// @phpstan-ignore-next-line
} else { } else {
$plugin_dirs = array_filter(glob("$root_dir/plugins.local/*"), "is_dir"); $plugin_dirs = array_filter(glob("$root_dir/plugins.local/*"), "is_dir");
@ -1301,7 +1340,7 @@ class Pref_Prefs extends Handler_Protected {
$test = self::_plugin_needs_update($root_dir, $plugin_name); $test = self::_plugin_needs_update($root_dir, $plugin_name);
if (!empty($test["o"])) if (!empty($test["stdout"]))
array_push($rv, ["plugin" => $plugin_name, "rv" => $this->_update_plugin($root_dir, $plugin_name)]); array_push($rv, ["plugin" => $plugin_name, "rv" => $this->_update_plugin($root_dir, $plugin_name)]);
} }
} }
@ -1311,20 +1350,20 @@ class Pref_Prefs extends Handler_Protected {
} }
} }
function clearplugindata() { function clearplugindata(): void {
$name = clean($_REQUEST["name"]); $name = clean($_REQUEST["name"]);
PluginHost::getInstance()->clear_data(PluginHost::getInstance()->get_plugin($name)); PluginHost::getInstance()->clear_data(PluginHost::getInstance()->get_plugin($name));
} }
function customizeCSS() { function customizeCSS(): void {
$value = get_pref(Prefs::USER_STYLESHEET); $value = get_pref(Prefs::USER_STYLESHEET);
$value = str_replace("<br/>", "\n", $value); $value = str_replace("<br/>", "\n", $value);
print json_encode(["value" => $value]); print json_encode(["value" => $value]);
} }
function activateprofile() { function activateprofile(): void {
$id = (int) ($_REQUEST['id'] ?? 0); $id = (int) ($_REQUEST['id'] ?? 0);
$profile = ORM::for_table('ttrss_settings_profiles') $profile = ORM::for_table('ttrss_settings_profiles')
@ -1338,7 +1377,7 @@ class Pref_Prefs extends Handler_Protected {
} }
} }
function cloneprofile() { function cloneprofile(): void {
$old_profile = $_REQUEST["old_profile"] ?? 0; $old_profile = $_REQUEST["old_profile"] ?? 0;
$new_title = clean($_REQUEST["new_title"]); $new_title = clean($_REQUEST["new_title"]);
@ -1367,7 +1406,7 @@ class Pref_Prefs extends Handler_Protected {
} }
} }
function remprofiles() { function remprofiles(): void {
$ids = $_REQUEST["ids"] ?? []; $ids = $_REQUEST["ids"] ?? [];
ORM::for_table('ttrss_settings_profiles') ORM::for_table('ttrss_settings_profiles')
@ -1377,7 +1416,7 @@ class Pref_Prefs extends Handler_Protected {
->delete_many(); ->delete_many();
} }
function addprofile() { function addprofile(): void {
$title = clean($_REQUEST["title"]); $title = clean($_REQUEST["title"]);
if ($title) { if ($title) {
@ -1396,7 +1435,7 @@ class Pref_Prefs extends Handler_Protected {
} }
} }
function saveprofile() { function saveprofile(): void {
$id = (int)$_REQUEST["id"]; $id = (int)$_REQUEST["id"];
$title = clean($_REQUEST["value"]); $title = clean($_REQUEST["value"]);
@ -1413,7 +1452,7 @@ class Pref_Prefs extends Handler_Protected {
} }
// TODO: this maybe needs to be unified with Public::getProfiles() // TODO: this maybe needs to be unified with Public::getProfiles()
function getProfiles() { function getProfiles(): void {
$rv = []; $rv = [];
$profiles = ORM::for_table('ttrss_settings_profiles') $profiles = ORM::for_table('ttrss_settings_profiles')
@ -1442,21 +1481,21 @@ class Pref_Prefs extends Handler_Protected {
print json_encode($rv); print json_encode($rv);
} }
private function _get_short_desc($pref_name) { private function _get_short_desc(string $pref_name): string {
if (isset($this->pref_help[$pref_name][0])) { if (isset($this->pref_help[$pref_name][0])) {
return $this->pref_help[$pref_name][0]; return $this->pref_help[$pref_name][0];
} }
return ""; return "";
} }
private function _get_help_text($pref_name) { private function _get_help_text(string $pref_name): string {
if (isset($this->pref_help[$pref_name][1])) { if (isset($this->pref_help[$pref_name][1])) {
return $this->pref_help[$pref_name][1]; return $this->pref_help[$pref_name][1];
} }
return ""; return "";
} }
private function appPasswordList() { private function appPasswordList(): void {
?> ?>
<div dojoType='fox.Toolbar'> <div dojoType='fox.Toolbar'>
<div dojoType='fox.form.DropDownButton'> <div dojoType='fox.form.DropDownButton'>
@ -1506,7 +1545,7 @@ class Pref_Prefs extends Handler_Protected {
<?php <?php
} }
function deleteAppPasswords() { function deleteAppPasswords(): void {
$passwords = ORM::for_table('ttrss_app_passwords') $passwords = ORM::for_table('ttrss_app_passwords')
->where('owner_uid', $_SESSION['uid']) ->where('owner_uid', $_SESSION['uid'])
->where_in('id', $_REQUEST['ids'] ?? []) ->where_in('id', $_REQUEST['ids'] ?? [])
@ -1515,7 +1554,7 @@ class Pref_Prefs extends Handler_Protected {
$this->appPasswordList(); $this->appPasswordList();
} }
function generateAppPassword() { function generateAppPassword(): void {
$title = clean($_REQUEST['title']); $title = clean($_REQUEST['title']);
$new_password = make_password(16); $new_password = make_password(16);
$new_salt = UserHelper::get_salt(); $new_salt = UserHelper::get_salt();
@ -1536,11 +1575,11 @@ class Pref_Prefs extends Handler_Protected {
$this->appPasswordList(); $this->appPasswordList();
} }
function previewDigest() { function previewDigest(): void {
print json_encode(Digest::prepare_headlines_digest($_SESSION["uid"], 1, 16)); print json_encode(Digest::prepare_headlines_digest($_SESSION["uid"], 1, 16));
} }
static function _get_ssl_certificate_id() { static function _get_ssl_certificate_id(): string {
if ($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"] ?? false) { if ($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"] ?? false) {
return sha1($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"] . return sha1($_SERVER["REDIRECT_SSL_CLIENT_M_SERIAL"] .
$_SERVER["REDIRECT_SSL_CLIENT_V_START"] . $_SERVER["REDIRECT_SSL_CLIENT_V_START"] .
@ -1556,7 +1595,7 @@ class Pref_Prefs extends Handler_Protected {
return ""; return "";
} }
private function format_otp_secret($secret) { private function format_otp_secret(string $secret): string {
return implode(" ", str_split($secret, 4)); return implode(" ", str_split($secret, 4));
} }
} }

View File

@ -82,7 +82,7 @@
/** /**
* @param mixed $value * @param mixed $value
* @param array<int, string> $values * @param array<int|string, string> $values
* @param array<string, mixed> $attributes * @param array<string, mixed> $attributes
*/ */
function select_tag(string $name, $value, array $values, array $attributes = [], string $id = ""): string { function select_tag(string $name, $value, array $values, array $attributes = [], string $id = ""): string {
@ -110,8 +110,8 @@
}*/ }*/
/** /**
* @param mixed $value * @param mixed $value
* @param array<int, string> $values * @param array<int|string, string> $values
* @param array<string, mixed> $attributes * @param array<string, mixed> $attributes
*/ */
function select_hash(string $name, $value, array $values, array $attributes = [], string $id = ""): string { function select_hash(string $name, $value, array $values, array $attributes = [], string $id = ""): string {