aboutsummaryrefslogtreecommitdiff
path: root/app/Controllers
diff options
context:
space:
mode:
Diffstat (limited to 'app/Controllers')
-rw-r--r--app/Controllers/authController.php6
-rw-r--r--app/Controllers/categoryController.php8
-rw-r--r--app/Controllers/configureController.php26
-rw-r--r--app/Controllers/extensionController.php2
-rw-r--r--app/Controllers/feedController.php10
-rw-r--r--app/Controllers/importExportController.php2
-rw-r--r--app/Controllers/indexController.php2
-rw-r--r--app/Controllers/javascriptController.php2
-rw-r--r--app/Controllers/subscriptionController.php6
-rw-r--r--app/Controllers/updateController.php77
-rw-r--r--app/Controllers/userController.php63
11 files changed, 171 insertions, 33 deletions
diff --git a/app/Controllers/authController.php b/app/Controllers/authController.php
index 64526a884..61f4f5aaf 100644
--- a/app/Controllers/authController.php
+++ b/app/Controllers/authController.php
@@ -85,8 +85,8 @@ class FreshRSS_auth_Controller extends FreshRSS_ActionController {
'http_auth' => Minz_Error::error(403, [
'error' => [
_t('feedback.access.denied'),
- ' [HTTP Remote-User=' . htmlspecialchars(httpAuthUser(false), ENT_NOQUOTES, 'UTF-8') .
- ' ; Remote IP address=' . connectionRemoteAddress() . ']'
+ ' [HTTP Remote-User=' . htmlspecialchars(FreshRSS_http_Util::httpAuthUser(onlyTrusted: false), ENT_NOQUOTES, 'UTF-8') .
+ ' ; Remote IP address=' . Minz_Request::connectionRemoteAddress() . ']'
]
], false),
'none' => Minz_Error::error(404), // It should not happen!
@@ -297,7 +297,7 @@ class FreshRSS_auth_Controller extends FreshRSS_ActionController {
Minz_Request::forward(['c' => 'index', 'a' => 'index'], true);
}
- if (max_registrations_reached()) {
+ if (FreshRSS_user_Controller::max_registrations_reached()) {
Minz_Error::error(403);
}
diff --git a/app/Controllers/categoryController.php b/app/Controllers/categoryController.php
index 2212a158b..5b1dd9d17 100644
--- a/app/Controllers/categoryController.php
+++ b/app/Controllers/categoryController.php
@@ -59,7 +59,7 @@ class FreshRSS_category_Controller extends FreshRSS_ActionController {
Minz_Request::bad(_t('feedback.tag.name_exists', $cat->name()), $url_redirect);
}
- $opml_url = checkUrl(Minz_Request::paramString('opml_url', plaintext: true));
+ $opml_url = FreshRSS_http_Util::checkUrl(Minz_Request::paramString('opml_url', plaintext: true));
if ($opml_url != '') {
$cat->_kind(FreshRSS_Category::KIND_DYNAMIC_OPML);
$cat->_attribute('opml_url', $opml_url);
@@ -141,7 +141,7 @@ class FreshRSS_category_Controller extends FreshRSS_ActionController {
$position = Minz_Request::paramInt('position') ?: null;
$category->_attribute('position', $position);
- $opml_url = checkUrl(Minz_Request::paramString('opml_url', plaintext: true));
+ $opml_url = FreshRSS_http_Util::checkUrl(Minz_Request::paramString('opml_url', plaintext: true));
if ($opml_url != '') {
$category->_kind(FreshRSS_Category::KIND_DYNAMIC_OPML);
$category->_attribute('opml_url', $opml_url);
@@ -229,7 +229,7 @@ class FreshRSS_category_Controller extends FreshRSS_ActionController {
}
// Remove related queries.
- $queries = remove_query_by_get('c_' . $id, FreshRSS_Context::userConf()->queries);
+ $queries = FreshRSS_UserQuery::remove_query_by_get('c_' . $id, FreshRSS_Context::userConf()->queries);
FreshRSS_Context::userConf()->queries = $queries;
FreshRSS_Context::userConf()->save();
@@ -274,7 +274,7 @@ class FreshRSS_category_Controller extends FreshRSS_ActionController {
// Remove related queries
foreach ($feeds as $feed) {
- $queries = remove_query_by_get('f_' . $feed->id(), FreshRSS_Context::userConf()->queries);
+ $queries = FreshRSS_UserQuery::remove_query_by_get('f_' . $feed->id(), FreshRSS_Context::userConf()->queries);
FreshRSS_Context::userConf()->queries = $queries;
}
FreshRSS_Context::userConf()->save();
diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php
index 17c6c20bd..451e98a8b 100644
--- a/app/Controllers/configureController.php
+++ b/app/Controllers/configureController.php
@@ -236,6 +236,30 @@ class FreshRSS_configure_Controller extends FreshRSS_ActionController {
FreshRSS_View::prependTitle(_t('conf.sharing.title') . ' · ');
}
+ private const SHORTCUT_KEYS = [
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12',
+ 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'Backspace', 'Delete',
+ 'End', 'Enter', 'Escape', 'Home', 'Insert', 'PageDown', 'PageUp', 'Space', 'Tab',
+ ];
+
+ /**
+ * @param array<string> $shortcuts
+ * @return list<string>
+ */
+ public static function getNonStandardShortcuts(array $shortcuts): array {
+ $standard = strtolower(implode(' ', self::SHORTCUT_KEYS));
+
+ $nonStandard = array_filter($shortcuts, static function (string $shortcut) use ($standard) {
+ $shortcut = trim($shortcut);
+ return $shortcut !== '' && stripos($standard, $shortcut) === false;
+ });
+
+ return array_values($nonStandard);
+ }
+
/**
* This action handles the shortcut configuration page.
*
@@ -249,7 +273,7 @@ class FreshRSS_configure_Controller extends FreshRSS_ActionController {
* tab and up.
*/
public function shortcutAction(): void {
- $this->view->list_keys = SHORTCUT_KEYS;
+ $this->view->list_keys = self::SHORTCUT_KEYS;
if (Minz_Request::isPost()) {
$shortcuts = Minz_Request::paramArray('shortcuts', plaintext: true);
diff --git a/app/Controllers/extensionController.php b/app/Controllers/extensionController.php
index 315d37101..9b52cd168 100644
--- a/app/Controllers/extensionController.php
+++ b/app/Controllers/extensionController.php
@@ -50,7 +50,7 @@ class FreshRSS_extension_Controller extends FreshRSS_ActionController {
$cacheFile = CACHE_PATH . '/extension_list.json';
if (FreshRSS_Context::userConf()->retrieve_extension_list === true) {
if (!file_exists($cacheFile) || (time() - (filemtime($cacheFile) ?: 0) > 86400)) {
- $json = httpGet($extensionListUrl, $cacheFile, 'json')['body'];
+ $json = FreshRSS_http_Util::httpGet($extensionListUrl, $cacheFile, 'json')['body'];
} else {
$json = @file_get_contents($cacheFile) ?: '';
}
diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php
index 678388cbb..b6ecbeec2 100644
--- a/app/Controllers/feedController.php
+++ b/app/Controllers/feedController.php
@@ -417,14 +417,14 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
}
/**
- * @param \SimplePie\SimplePie|null $simplePiePush Used by WebSub (PubSubHubbub) to push updates
+ * @param FreshRSS_SimplePieCustom|null $simplePiePush Used by WebSub (PubSubHubbub) to push updates
* @param string $selfUrl Used by WebSub (PubSubHubbub) to override the feed URL
* @return array{0:int,1:FreshRSS_Feed|null,2:int,3:array<FreshRSS_Feed>} Number of updated feeds, first feed or null, number of new articles,
* list of feeds for which a cache refresh is needed
* @throws FreshRSS_BadUrl_Exception
*/
public static function actualizeFeeds(?int $feed_id = null, ?string $feed_url = null, ?int $maxFeeds = null,
- ?\SimplePie\SimplePie $simplePiePush = null, string $selfUrl = ''): array {
+ ?FreshRSS_SimplePieCustom $simplePiePush = null, string $selfUrl = ''): array {
if (function_exists('set_time_limit')) {
@set_time_limit(300);
}
@@ -866,14 +866,14 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
}
/**
- * @param \SimplePie\SimplePie|null $simplePiePush Used by WebSub (PubSubHubbub) to push updates
+ * @param FreshRSS_SimplePieCustom|null $simplePiePush Used by WebSub (PubSubHubbub) to push updates
* @param string $selfUrl Used by WebSub (PubSubHubbub) to override the feed URL
* @return array{0:int,1:FreshRSS_Feed|null,2:int,3:array<FreshRSS_Feed>} Number of updated feeds, first feed or null, number of new articles,
* list of feeds for which a cache refresh is needed
* @throws FreshRSS_BadUrl_Exception
*/
public static function actualizeFeedsAndCommit(?int $feed_id = null, ?string $feed_url = null, ?int $maxFeeds = null,
- ?SimplePie\SimplePie $simplePiePush = null, string $selfUrl = ''): array {
+ ?FreshRSS_SimplePieCustom $simplePiePush = null, string $selfUrl = ''): array {
$entryDAO = FreshRSS_Factory::createEntryDao();
[$nbUpdatedFeeds, $feed, $nbNewArticles, $feedsCacheToRefresh] =
FreshRSS_feed_Controller::actualizeFeeds($feed_id, $feed_url, $maxFeeds, $simplePiePush, $selfUrl);
@@ -1066,7 +1066,7 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
}
// Remove related queries
- $queries = remove_query_by_get('f_' . $feed_id, FreshRSS_Context::userConf()->queries);
+ $queries = FreshRSS_UserQuery::remove_query_by_get('f_' . $feed_id, FreshRSS_Context::userConf()->queries);
FreshRSS_Context::userConf()->queries = $queries;
FreshRSS_Context::userConf()->save();
return true;
diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php
index 7dc825b9e..d1635a046 100644
--- a/app/Controllers/importExportController.php
+++ b/app/Controllers/importExportController.php
@@ -448,7 +448,7 @@ class FreshRSS_importExport_Controller extends FreshRSS_ActionController {
} else {
$content = '';
}
- $content = sanitizeHTML($content, $url);
+ $content = FreshRSS_SimplePieCustom::sanitizeHTML($content, $url);
if (is_int($item['published'] ?? null) || is_string($item['published'] ?? null)) {
$published = (string)$item['published'];
diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php
index 9005dff93..59302d3f1 100644
--- a/app/Controllers/indexController.php
+++ b/app/Controllers/indexController.php
@@ -383,7 +383,7 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
}
$this->view->terms_of_service = $terms_of_service;
- $this->view->can_register = !max_registrations_reached();
+ $this->view->can_register = !FreshRSS_user_Controller::max_registrations_reached();
FreshRSS_View::prependTitle(_t('index.tos.title') . ' · ');
}
diff --git a/app/Controllers/javascriptController.php b/app/Controllers/javascriptController.php
index eda468dff..32bdee305 100644
--- a/app/Controllers/javascriptController.php
+++ b/app/Controllers/javascriptController.php
@@ -71,7 +71,7 @@ class FreshRSS_javascript_Controller extends FreshRSS_ActionController {
Minz_Error::error(400);
return;
}
- $user_conf = get_user_configuration($user);
+ $user_conf = FreshRSS_UserConfiguration::getForUser($user);
if ($user_conf !== null) {
try {
$s = $user_conf->passwordHash;
diff --git a/app/Controllers/subscriptionController.php b/app/Controllers/subscriptionController.php
index 244a16671..a2d1c1d07 100644
--- a/app/Controllers/subscriptionController.php
+++ b/app/Controllers/subscriptionController.php
@@ -337,9 +337,9 @@ class FreshRSS_subscription_Controller extends FreshRSS_ActionController {
$values = [
'name' => Minz_Request::paramString('name'),
'kind' => $feed->kind(),
- 'description' => sanitizeHTML(Minz_Request::paramString('description', true)),
- 'website' => checkUrl(Minz_Request::paramString('website')) ?: '',
- 'url' => checkUrl(Minz_Request::paramString('url')) ?: '',
+ 'description' => FreshRSS_SimplePieCustom::sanitizeHTML(Minz_Request::paramString('description', true)),
+ 'website' => FreshRSS_http_Util::checkUrl(Minz_Request::paramString('website')) ?: '',
+ 'url' => FreshRSS_http_Util::checkUrl(Minz_Request::paramString('url')) ?: '',
'category' => Minz_Request::paramInt('category'),
'pathEntries' => Minz_Request::paramString('path_entries'),
'priority' => Minz_Request::paramTernary('priority') === null ? FreshRSS_Feed::PRIORITY_MAIN_STREAM : Minz_Request::paramInt('priority'),
diff --git a/app/Controllers/updateController.php b/app/Controllers/updateController.php
index 7c204de8c..98d552688 100644
--- a/app/Controllers/updateController.php
+++ b/app/Controllers/updateController.php
@@ -335,13 +335,84 @@ class FreshRSS_update_Controller extends FreshRSS_ActionController {
}
/**
+ * Check PHP and its extensions are well-installed.
+ *
+ * @return array<string,bool> of tested values.
+ */
+ private static function check_install_php(): array {
+ $pdo_mysql = extension_loaded('pdo_mysql');
+ $pdo_pgsql = extension_loaded('pdo_pgsql');
+ $pdo_sqlite = extension_loaded('pdo_sqlite');
+ return [
+ 'php' => version_compare(PHP_VERSION, FRESHRSS_MIN_PHP_VERSION) >= 0,
+ 'curl' => extension_loaded('curl'),
+ 'pdo' => $pdo_mysql || $pdo_sqlite || $pdo_pgsql,
+ 'pcre' => extension_loaded('pcre'),
+ 'ctype' => extension_loaded('ctype'),
+ 'fileinfo' => extension_loaded('fileinfo'),
+ 'dom' => class_exists('DOMDocument'),
+ 'json' => extension_loaded('json'),
+ 'mbstring' => extension_loaded('mbstring'),
+ 'zip' => extension_loaded('zip'),
+ ];
+ }
+
+ /**
+ * Check different data files and directories exist.
+ * @return array<string,bool> of tested values.
+ */
+ private static function check_install_files(): array {
+ return [
+ 'data' => is_dir(DATA_PATH) && touch(DATA_PATH . '/index.html'), // is_writable() is not reliable for a folder on NFS
+ 'cache' => is_dir(CACHE_PATH) && touch(CACHE_PATH . '/index.html'),
+ 'users' => is_dir(USERS_PATH) && touch(USERS_PATH . '/index.html'),
+ 'favicons' => is_dir(DATA_PATH) && touch(DATA_PATH . '/favicons/index.html'),
+ 'tokens' => is_dir(DATA_PATH) && touch(DATA_PATH . '/tokens/index.html'),
+ ];
+ }
+
+ /**
+ * Check database is well-installed.
+ *
+ * @return array<string,bool> of tested values.
+ */
+ private static function check_install_database(): array {
+ $status = [
+ 'connection' => true,
+ 'tables' => false,
+ 'categories' => false,
+ 'feeds' => false,
+ 'entries' => false,
+ 'entrytmp' => false,
+ 'tag' => false,
+ 'entrytag' => false,
+ ];
+
+ try {
+ $dbDAO = FreshRSS_Factory::createDatabaseDAO();
+
+ $status['tables'] = $dbDAO->tablesAreCorrect();
+ $status['categories'] = $dbDAO->categoryIsCorrect();
+ $status['feeds'] = $dbDAO->feedIsCorrect();
+ $status['entries'] = $dbDAO->entryIsCorrect();
+ $status['entrytmp'] = $dbDAO->entrytmpIsCorrect();
+ $status['tag'] = $dbDAO->tagIsCorrect();
+ $status['entrytag'] = $dbDAO->entrytagIsCorrect();
+ } catch (Minz_PDOConnectionException $e) {
+ $status['connection'] = false;
+ }
+
+ return $status;
+ }
+
+ /**
* This action displays information about installation.
*/
public function checkInstallAction(): void {
FreshRSS_View::prependTitle(_t('admin.check_install.title') . ' · ');
- $this->view->status_php = check_install_php();
- $this->view->status_files = check_install_files();
- $this->view->status_database = check_install_database();
+ $this->view->status_php = self::check_install_php();
+ $this->view->status_files = self::check_install_files();
+ $this->view->status_database = self::check_install_database();
}
}
diff --git a/app/Controllers/userController.php b/app/Controllers/userController.php
index a7a79b067..50a89eb3c 100644
--- a/app/Controllers/userController.php
+++ b/app/Controllers/userController.php
@@ -15,6 +15,37 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
return preg_match('/^' . self::USERNAME_PATTERN . '$/', $username) === 1;
}
+ /**
+ * Validate an email address, supports internationalized addresses.
+ *
+ * @param string $email The address to validate
+ * @return bool true if email is valid, else false
+ */
+ private static function validateEmailAddress(string $email): bool {
+ $mailer = new PHPMailer\PHPMailer\PHPMailer();
+ $mailer->CharSet = 'utf-8';
+ $punyemail = $mailer->punyencodeAddress($email);
+ return PHPMailer\PHPMailer\PHPMailer::validateAddress($punyemail, 'html5');
+ }
+
+ /**
+ * @return list<string>
+ */
+ public static function listUsers(): array {
+ $final_list = [];
+ $base_path = join_path(DATA_PATH, 'users');
+ $dir_list = array_values(array_diff(
+ scandir($base_path) ?: [],
+ ['..', '.', Minz_User::INTERNAL_USER]
+ ));
+ foreach ($dir_list as $file) {
+ if ($file[0] !== '.' && is_dir(join_path($base_path, $file)) && file_exists(join_path($base_path, $file, 'config.php'))) {
+ $final_list[] = $file;
+ }
+ }
+ return $final_list;
+ }
+
public static function userExists(string $username): bool {
$config_path = USERS_PATH . '/' . $username . '/config.php';
if (@file_exists($config_path)) {
@@ -30,9 +61,21 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
return false;
}
+ /**
+ * Return if the maximum number of registrations has been reached.
+ * Note a max_registrations of 0 means there is no limit.
+ *
+ * @return bool true if number of users >= max registrations, false else.
+ */
+ public static function max_registrations_reached(): bool {
+ $limit_registrations = FreshRSS_Context::systemConf()->limits['max_registrations'];
+ $number_accounts = count(self::listUsers());
+ return $limit_registrations > 0 && $number_accounts >= $limit_registrations;
+ }
+
/** @param array<string,mixed> $userConfigUpdated */
public static function updateUser(string $user, ?string $email, string $passwordPlain, array $userConfigUpdated = []): bool {
- $userConfig = get_user_configuration($user);
+ $userConfig = FreshRSS_UserConfiguration::getForUser($user);
if ($userConfig === null) {
return false;
}
@@ -166,7 +209,7 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
);
}
- if (!empty($email) && !validateEmailAddress($email)) {
+ if (!empty($email) && !self::validateEmailAddress($email)) {
Minz_Request::bad(
_t('user.email.feedback.invalid'),
['c' => 'user', 'a' => 'profile']
@@ -285,7 +328,7 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
$this->view->show_email_field = FreshRSS_Context::systemConf()->force_email_validation;
$this->view->current_user = Minz_Request::paramString('u');
- foreach (listUsers() as $user) {
+ foreach (self::listUsers() as $user) {
$this->view->users[$user] = $this->retrieveUserDetails($user);
}
}
@@ -322,7 +365,7 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
$userConfig['language'] = Minz_Translate::DEFAULT_LANGUAGE;
}
- $ok &= !in_array(strtoupper($new_user_name), array_map('strtoupper', listUsers()), true); //Not an existing user, case-insensitive
+ $ok &= !in_array(strtoupper($new_user_name), array_map('strtoupper', self::listUsers()), true); //Not an existing user, case-insensitive
$configPath = join_path($homeDir, 'config.php');
$ok &= !file_exists($configPath);
@@ -370,7 +413,7 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
* @todo clean up this method. Idea: write a method to init a user with basic information.
*/
public function createAction(): void {
- if (!FreshRSS_Auth::hasAccess('admin') && max_registrations_reached()) {
+ if (!FreshRSS_Auth::hasAccess('admin') && self::max_registrations_reached()) {
Minz_Error::error(403);
}
@@ -424,7 +467,7 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
);
}
- if (!empty($email) && !validateEmailAddress($email)) {
+ if (!empty($email) && !self::validateEmailAddress($email)) {
Minz_Request::bad(
_t('user.email.feedback.invalid'),
$badRedirectUrl
@@ -451,7 +494,7 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
// user just created its account himself so he probably wants to
// get started immediately.
if ($ok && !FreshRSS_Auth::hasAccess('admin')) {
- $user_conf = get_user_configuration($new_user_name);
+ $user_conf = FreshRSS_UserConfiguration::getForUser($new_user_name);
if ($user_conf !== null) {
Minz_Session::_params([
Minz_User::CURRENT_USER => $new_user_name,
@@ -531,7 +574,7 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
$token = Minz_Request::paramString('token');
if ($username !== '') {
- $user_config = get_user_configuration($username);
+ $user_config = FreshRSS_UserConfiguration::getForUser($username);
} elseif (FreshRSS_Auth::hasAccess()) {
$user_config = FreshRSS_Context::userConf();
} else {
@@ -711,7 +754,7 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
Minz_Error::error(404);
}
- if (null === $userConfig = get_user_configuration($username)) {
+ if (null === $userConfig = FreshRSS_UserConfiguration::getForUser($username)) {
Minz_Error::error(500);
return;
}
@@ -769,7 +812,7 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
$entryDAO = FreshRSS_Factory::createEntryDao($username);
$databaseDAO = FreshRSS_Factory::createDatabaseDAO($username);
- $userConfiguration = get_user_configuration($username);
+ $userConfiguration = FreshRSS_UserConfiguration::getForUser($username);
if ($userConfiguration === null) {
throw new Exception('Error loading user configuration!');
}