diff --git a/.gitignore b/.gitignore index 0d18ab0bdc..49d08ba71e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ favicon.* -.htconfig.php -.htpreconfig.php +/.htconfig.php +/.htpreconfig.php \#* *.log *.out diff --git a/CHANGELOG b/CHANGELOG index 0e4a2616e5..337cca4289 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,13 +5,16 @@ Version 2019.06 (UNRELEASED) (2019-06-?) Fixed the notification order [JeroenED] Fixed the timezone of Friendica logs [nupplaphil] Fixed tag completion painfully slow [AlfredSK] + Fixed a regression in notifications [MrPetovan] + Fixed an issue with smilies and code blocks [MrPetovan] General Code cleaning and restructuring [nupplaphil] Added frio color scheme sharing [JeroenED] Added syslog and stream Logger [nupplaphil] Added storage move cronjob [MrPetovan] + Added collapsible panel for connector permission fields [MrPetovan] Closed Issues: - 6303, 6478, 6319 + 6303, 6478, 6319, 6921, 6903 Version 2019.03 (2019-03-22) Friendica Core: diff --git a/config/dbstructure.config.php b/config/dbstructure.config.php old mode 100644 new mode 100755 index 7488787a6a..4c2afc7f07 --- a/config/dbstructure.config.php +++ b/config/dbstructure.config.php @@ -34,7 +34,7 @@ use Friendica\Database\DBA; if (!defined('DB_UPDATE_VERSION')) { - define('DB_UPDATE_VERSION', 1305); + define('DB_UPDATE_VERSION', 1308); } return [ @@ -529,6 +529,21 @@ return [ "hook_file_function" => ["UNIQUE", "hook", "file", "function"], ] ], + "inbox-status" => [ + "comment" => "Status of ActivityPub inboxes", + "fields" => [ + "url" => ["type" => "varbinary(255)", "not null" => "1", "primary" => "1", "comment" => "URL of the inbox"], + "created" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Creation date of this entry"], + "success" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Date of the last successful delivery"], + "failure" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Date of the last failed delivery"], + "previous" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Previous delivery date"], + "archive" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Is the inbox archived?"], + "shared" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Is it a shared inbox?"] + ], + "indexes" => [ + "PRIMARY" => ["url"] + ] + ], "intro" => [ "comment" => "", "fields" => [ @@ -1195,7 +1210,7 @@ return [ ], "indexes" => [ "PRIMARY" => ["tid"], - "term_type" => ["term", "type"], + "term_type" => ["term(64)", "type"], "oid_otype_type_term" => ["oid", "otype", "type", "term(32)"], "uid_otype_type_term_global_created" => ["uid", "otype", "type", "term(32)", "global", "created"], "uid_otype_type_url" => ["uid", "otype", "type", "url(64)"], diff --git a/database.sql b/database.sql index 0e7e5a3437..5ba700bd87 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ --- Friendica 2019.03-dev (The Tazmans Flax-lily) --- DB_UPDATE_VERSION 1300 +-- Friendica 2019.06-dev (Dalmatian Bellflower) +-- DB_UPDATE_VERSION 1308 -- ------------------------------------------ @@ -470,6 +470,20 @@ CREATE TABLE IF NOT EXISTS `hook` ( UNIQUE INDEX `hook_file_function` (`hook`,`file`,`function`) ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='addon hook registry'; +-- +-- TABLE inbox-status +-- +CREATE TABLE IF NOT EXISTS `inbox-status` ( + `url` varbinary(255) NOT NULL COMMENT 'URL of the inbox', + `created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Creation date of this entry', + `success` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Date of the last successful delivery', + `failure` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Date of the last failed delivery', + `previous` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Previous delivery date', + `archive` boolean NOT NULL DEFAULT '0' COMMENT 'Is the inbox archived?', + `shared` boolean NOT NULL DEFAULT '0' COMMENT 'Is it a shared inbox?', + PRIMARY KEY(`url`) +) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Status of ActivityPub inboxes'; + -- -- TABLE intro -- @@ -879,7 +893,7 @@ CREATE TABLE IF NOT EXISTS `photo` ( `deny_gid` mediumtext COMMENT 'Access Control - list of denied groups', `backend-class` tinytext COMMENT 'Storage backend class', `backend-ref` text COMMENT 'Storage backend data reference', - `updated` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'edited timestamp', + `updated` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '', PRIMARY KEY(`id`), INDEX `contactid` (`contact-id`), INDEX `uid_contactid` (`uid`,`contact-id`), @@ -1099,7 +1113,7 @@ CREATE TABLE IF NOT EXISTS `term` ( `global` boolean NOT NULL DEFAULT '0' COMMENT '', `uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'User id', PRIMARY KEY(`tid`), - INDEX `term_type` (`term`, `type`), + INDEX `term_type` (`term`(64),`type`), INDEX `oid_otype_type_term` (`oid`,`otype`,`type`,`term`(32)), INDEX `uid_otype_type_term_global_created` (`uid`,`otype`,`type`,`term`(32),`global`,`created`), INDEX `uid_otype_type_url` (`uid`,`otype`,`type`,`url`(64)), @@ -1271,13 +1285,12 @@ CREATE TABLE IF NOT EXISTS `workerqueue` ( `retrial` tinyint NOT NULL DEFAULT 0 COMMENT 'Retrial counter', `done` boolean NOT NULL DEFAULT '0' COMMENT 'Marked 1 when the task was done - will be deleted later', PRIMARY KEY(`id`), - INDEX `pid` (`pid`), - INDEX `parameter` (`parameter`(64)), - INDEX `priority_created_next_try` (`priority`,`created`,`next_try`), - INDEX `done_priority_executed_next_try` (`done`,`priority`,`executed`,`next_try`), - INDEX `done_executed_next_try` (`done`,`executed`,`next_try`), + INDEX `done_parameter` (`done`,`parameter`(64)), + INDEX `done_executed` (`done`,`executed`), + INDEX `done_priority_created` (`done`,`priority`,`created`), INDEX `done_priority_next_try` (`done`,`priority`,`next_try`), - INDEX `done_next_try` (`done`,`next_try`) + INDEX `done_pid_next_try` (`done`,`pid`,`next_try`), + INDEX `done_pid_priority_created` (`done`,`pid`,`priority`,`created`) ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Background tasks queue entries'; -- diff --git a/doc/Addons.md b/doc/Addons.md index 29cf22bfe9..5d4be6db16 100644 --- a/doc/Addons.md +++ b/doc/Addons.md @@ -411,6 +411,30 @@ Hook data: visitor => array with the contact record of the visitor url => the query string +### jot_networks +Called when displaying the post permission screen. +Hook data is a list of form fields that need to be displayed along the ACL. +Form field array structure is: + +- **type**: `checkbox` or `select`. +- **field**: Standard field data structure to be used by `field_checkbox.tpl` and `field_select.tpl`. + +For `checkbox`, **field** is: + - [0] (String): Form field name; Mandatory. + - [1]: (String): Form field label; Optional, default is none. + - [2]: (Boolean): Whether the checkbox should be checked by default; Optional, default is false. + - [3]: (String): Additional help text; Optional, default is none. + - [4]: (String): Additional HTML attributes; Optional, default is none. + +For `select`, **field** is: + - [0] (String): Form field name; Mandatory. + - [1] (String): Form field label; Optional, default is none. + - [2] (Boolean): Default value to be selected by default; Optional, default is none. + - [3] (String): Additional help text; Optional, default is none. + - [4] (Array): Associative array of options. Item key is option value, item value is option label; Mandatory. + + + ## Complete list of hook callbacks Here is a complete list of all hook callbacks with file locations (as of 24-Sep-2018). Please see the source for details of any hooks not documented above. diff --git a/mod/admin.php b/mod/admin.php index f8a75b7a2d..7f8e12e0c7 100644 --- a/mod/admin.php +++ b/mod/admin.php @@ -923,6 +923,10 @@ function admin_page_summary(App $a) $showwarning = true; $warningtext[] = L10n::t('The database update failed. Please run "php bin/console.php dbstructure update" from the command line and have a look at the errors that might appear.'); } + if (Config::get('system', 'update') == Update::FAILED) { + $showwarning = true; + $warningtext[] = L10n::t('The last update failed. Please run "php bin/console.php dbstructure update" from the command line and have a look at the errors that might appear. (Some of the errors are possibly inside the logfile.)'); + } $last_worker_call = Config::get('system', 'last_worker_execution', false); if (!$last_worker_call) { @@ -1087,7 +1091,9 @@ function admin_page_site_post(App $a) update_table($a, "gcontact", ['connect', 'addr'], $old_host, $new_host); // update config - Config::set('system', 'hostname', parse_url($new_url, PHP_URL_HOST)); + $configFileSaver = new \Friendica\Util\Config\ConfigFileSaver($a->getBasePath()); + $configFileSaver->addConfigValue('config', 'hostname', parse_url($new_url, PHP_URL_HOST)); + $configFileSaver->saveToConfigFile(); Config::set('system', 'url', $new_url); $a->setBaseURL($new_url); @@ -1105,7 +1111,6 @@ function admin_page_site_post(App $a) // end relocate $sitename = (!empty($_POST['sitename']) ? Strings::escapeTags(trim($_POST['sitename'])) : ''); - $hostname = (!empty($_POST['hostname']) ? Strings::escapeTags(trim($_POST['hostname'])) : ''); $sender_email = (!empty($_POST['sender_email']) ? Strings::escapeTags(trim($_POST['sender_email'])) : ''); $banner = (!empty($_POST['banner']) ? trim($_POST['banner']) : false); $shortcut_icon = (!empty($_POST['shortcut_icon']) ? Strings::escapeTags(trim($_POST['shortcut_icon'])) : ''); @@ -1176,7 +1181,6 @@ function admin_page_site_post(App $a) $itemcache_duration = (!empty($_POST['itemcache_duration']) ? intval($_POST['itemcache_duration']) : 0); $max_comments = (!empty($_POST['max_comments']) ? intval($_POST['max_comments']) : 0); $temppath = (!empty($_POST['temppath']) ? Strings::escapeTags(trim($_POST['temppath'])) : ''); - $basepath = (!empty($_POST['basepath']) ? Strings::escapeTags(trim($_POST['basepath'])) : ''); $singleuser = (!empty($_POST['singleuser']) ? Strings::escapeTags(trim($_POST['singleuser'])) : ''); $proxy_disabled = !empty($_POST['proxy_disabled']); $only_tag_search = !empty($_POST['only_tag_search']); @@ -1296,7 +1300,6 @@ function admin_page_site_post(App $a) Config::set('system', 'poco_local_search' , $poco_local_search); Config::set('system', 'nodeinfo' , $nodeinfo); Config::set('config', 'sitename' , $sitename); - Config::set('config', 'hostname' , $hostname); Config::set('config', 'sender_email' , $sender_email); Config::set('system', 'suppress_tags' , $suppress_tags); Config::set('system', 'shortcut_icon' , $shortcut_icon); @@ -1392,11 +1395,6 @@ function admin_page_site_post(App $a) Config::set('system', 'temppath', $temppath); - if ($basepath != '') { - $basepath = BasePath::getRealPath($basepath); - } - - Config::set('system', 'basepath' , $basepath); Config::set('system', 'proxy_disabled' , $proxy_disabled); Config::set('system', 'only_tag_search' , $only_tag_search); @@ -1536,9 +1534,6 @@ function admin_page_site(App $a) "develop" => L10n::t("check the development version") ]; - if (empty(Config::get('config', 'hostname'))) { - Config::set('config', 'hostname', $a->getHostName()); - } $diaspora_able = ($a->getURLPath() == ""); $optimize_max_tablesize = Config::get('system', 'optimize_max_tablesize', -1); @@ -1597,7 +1592,6 @@ function admin_page_site(App $a) // name, label, value, help string, extra data... '$sitename' => ['sitename', L10n::t("Site name"), Config::get('config', 'sitename'), ''], - '$hostname' => ['hostname', L10n::t("Host name"), Config::get('config', 'hostname'), ""], '$sender_email' => ['sender_email', L10n::t("Sender Email"), Config::get('config', 'sender_email'), L10n::t("The email address your server shall use to send notification emails from."), "", "", "email"], '$banner' => ['banner', L10n::t("Banner/Logo"), $banner, ""], '$shortcut_icon' => ['shortcut_icon', L10n::t("Shortcut icon"), Config::get('system', 'shortcut_icon'), L10n::t("Link to an icon that will be used for browsers.")], @@ -1675,7 +1669,6 @@ function admin_page_site(App $a) '$itemcache_duration' => ['itemcache_duration', L10n::t("Cache duration in seconds"), Config::get('system', 'itemcache_duration'), L10n::t("How long should the cache files be hold? Default value is 86400 seconds \x28One day\x29. To disable the item cache, set the value to -1.")], '$max_comments' => ['max_comments', L10n::t("Maximum numbers of comments per post"), Config::get('system', 'max_comments'), L10n::t("How much comments should be shown for each post? Default value is 100.")], '$temppath' => ['temppath', L10n::t("Temp path"), Config::get('system', 'temppath'), L10n::t("If you have a restricted system where the webserver can't access the system temp path, enter another path here.")], - '$basepath' => ['basepath', L10n::t("Base path to installation"), Config::get('system', 'basepath'), L10n::t("If the system cannot detect the correct path to your installation, enter the correct path here. This setting should only be set if you are using a restricted system and symbolic links to your webroot.")], '$proxy_disabled' => ['proxy_disabled', L10n::t("Disable picture proxy"), Config::get('system', 'proxy_disabled'), L10n::t("The picture proxy increases performance and privacy. It shouldn't be used on systems with very low bandwidth.")], '$only_tag_search' => ['only_tag_search', L10n::t("Only search in tags"), Config::get('system', 'only_tag_search'), L10n::t("On large systems the text search can slow down the system extremely.")], diff --git a/src/App.php b/src/App.php index 4f55b558c0..c0afd514d0 100644 --- a/src/App.php +++ b/src/App.php @@ -8,12 +8,12 @@ use Detection\MobileDetect; use DOMDocument; use DOMXPath; use Exception; -use Friendica\Core\Config\Cache\ConfigCacheLoader; use Friendica\Core\Config\Cache\IConfigCache; use Friendica\Core\Config\Configuration; use Friendica\Database\DBA; use Friendica\Model\Profile; use Friendica\Network\HTTPException\InternalServerErrorException; +use Friendica\Util\Config\ConfigFileLoader; use Friendica\Util\HTTPSignature; use Friendica\Util\Profiler; use Psr\Log\LoggerInterface; @@ -75,11 +75,6 @@ class App */ private $mode; - /** - * @var string The App base path - */ - private $basePath; - /** * @var string The App URL path */ @@ -142,7 +137,7 @@ class App */ public function getBasePath() { - return $this->basePath; + return $this->config->get('system', 'basepath'); } /** @@ -187,7 +182,7 @@ class App */ public function registerStylesheet($path) { - $url = str_replace($this->basePath . DIRECTORY_SEPARATOR, '', $path); + $url = str_replace($this->getBasePath() . DIRECTORY_SEPARATOR, '', $path); $this->stylesheets[] = trim($url, '/'); } @@ -204,7 +199,7 @@ class App */ public function registerFooterScript($path) { - $url = str_replace($this->basePath . DIRECTORY_SEPARATOR, '', $path); + $url = str_replace($this->getBasePath() . DIRECTORY_SEPARATOR, '', $path); $this->footerScripts[] = trim($url, '/'); } @@ -216,7 +211,6 @@ class App /** * @brief App constructor. * - * @param string $basePath The basedir of the app * @param Configuration $config The Configuration * @param App\Mode $mode The mode of this Friendica app * @param LoggerInterface $logger The current app logger @@ -225,7 +219,7 @@ class App * * @throws Exception if the Basepath is not usable */ - public function __construct($basePath, Configuration $config, App\Mode $mode, LoggerInterface $logger, Profiler $profiler, $isBackend = true) + public function __construct(Configuration $config, App\Mode $mode, LoggerInterface $logger, Profiler $profiler, $isBackend = true) { BaseObject::setApp($this); @@ -233,13 +227,6 @@ class App $this->config = $config; $this->profiler = $profiler; $this->mode = $mode; - $cfgBasePath = $this->config->get('system', 'basepath'); - $this->basePath = !empty($cfgBasePath) ? $cfgBasePath : $basePath; - - if (!Core\System::isDirectoryUsable($this->basePath, false)) { - throw new Exception('Basepath \'' . $this->basePath . '\' isn\'t usable.'); - } - $this->basePath = rtrim($this->basePath, DIRECTORY_SEPARATOR); $this->checkBackend($isBackend); $this->checkFriendicaApp(); @@ -275,9 +262,9 @@ class App set_include_path( get_include_path() . PATH_SEPARATOR - . $this->basePath . DIRECTORY_SEPARATOR . 'include' . PATH_SEPARATOR - . $this->basePath . DIRECTORY_SEPARATOR . 'library' . PATH_SEPARATOR - . $this->basePath); + . $this->getBasePath() . DIRECTORY_SEPARATOR . 'include' . PATH_SEPARATOR + . $this->getBasePath() . DIRECTORY_SEPARATOR . 'library' . PATH_SEPARATOR + . $this->getBasePath()); if (!empty($_SERVER['QUERY_STRING']) && strpos($_SERVER['QUERY_STRING'], 'pagename=') === 0) { $this->query_string = substr($_SERVER['QUERY_STRING'], 9); @@ -352,10 +339,10 @@ class App { $this->determineURLPath(); - $this->getMode()->determine($this->basePath); + $this->getMode()->determine($this->getBasePath()); if ($this->getMode()->has(App\Mode::DBAVAILABLE)) { - $loader = new ConfigCacheLoader($this->basePath, $this->getMode()); + $loader = new ConfigFileLoader($this->getBasePath(), $this->getMode()); $this->config->getCache()->load($loader->loadCoreConfig('addon'), true); $this->profiler->update( @@ -363,7 +350,7 @@ class App $this->config->get('rendertime', 'callstack', false)); Core\Hook::loadHooks(); - $loader = new ConfigCacheLoader($this->basePath, $this->mode); + $loader = new ConfigFileLoader($this->getBasePath(), $this->mode); Core\Hook::callAll('load_config', $loader); } @@ -465,14 +452,14 @@ class App { $scheme = $this->scheme; - if (Core\Config::get('system', 'ssl_policy') == SSL_POLICY_FULL) { + if ($this->config->get('system', 'ssl_policy') == SSL_POLICY_FULL) { $scheme = 'https'; } // Basically, we have $ssl = true on any links which can only be seen by a logged in user // (and also the login link). Anything seen by an outsider will have it turned off. - if (Core\Config::get('system', 'ssl_policy') == SSL_POLICY_SELFSIGN) { + if ($this->config->get('system', 'ssl_policy') == SSL_POLICY_SELFSIGN) { if ($ssl) { $scheme = 'https'; } else { @@ -480,8 +467,8 @@ class App } } - if (Core\Config::get('config', 'hostname') != '') { - $this->hostname = Core\Config::get('config', 'hostname'); + if ($this->config->get('config', 'hostname') != '') { + $this->hostname = $this->config->get('config', 'hostname'); } return $scheme . '://' . $this->hostname . (!empty($this->getURLPath()) ? '/' . $this->getURLPath() : '' ); @@ -516,12 +503,12 @@ class App $this->urlPath = trim($parsed['path'], '\\/'); } - if (file_exists($this->basePath . '/.htpreconfig.php')) { - include $this->basePath . '/.htpreconfig.php'; + if (file_exists($this->getBasePath() . '/.htpreconfig.php')) { + include $this->getBasePath() . '/.htpreconfig.php'; } - if (Core\Config::get('config', 'hostname') != '') { - $this->hostname = Core\Config::get('config', 'hostname'); + if ($this->config->get('config', 'hostname') != '') { + $this->hostname = $this->config->get('config', 'hostname'); } if (!isset($this->hostname) || ($this->hostname == '')) { @@ -532,8 +519,8 @@ class App public function getHostName() { - if (Core\Config::get('config', 'hostname') != '') { - $this->hostname = Core\Config::get('config', 'hostname'); + if ($this->config->get('config', 'hostname') != '') { + $this->hostname = $this->config->get('config', 'hostname'); } return $this->hostname; @@ -583,12 +570,12 @@ class App $this->registerStylesheet($stylesheet); - $shortcut_icon = Core\Config::get('system', 'shortcut_icon'); + $shortcut_icon = $this->config->get('system', 'shortcut_icon'); if ($shortcut_icon == '') { $shortcut_icon = 'images/friendica-32.png'; } - $touch_icon = Core\Config::get('system', 'touch_icon'); + $touch_icon = $this->config->get('system', 'touch_icon'); if ($touch_icon == '') { $touch_icon = 'images/friendica-128.png'; } @@ -608,7 +595,7 @@ class App '$update_interval' => $interval, '$shortcut_icon' => $shortcut_icon, '$touch_icon' => $touch_icon, - '$block_public' => intval(Core\Config::get('system', 'block_public')), + '$block_public' => intval($this->config->get('system', 'block_public')), '$stylesheets' => $this->stylesheets, ]) . $this->page['htmlhead']; } @@ -737,6 +724,7 @@ class App 'fetch', 'hcard', 'hostxrd', + 'manifest', 'nodeinfo', 'noscrape', 'p', @@ -781,13 +769,13 @@ class App * if ($this->is_backend()) { $process = 'backend'; - $max_processes = Core\Config::get('system', 'max_processes_backend'); + $max_processes = $this->config->get('system', 'max_processes_backend'); if (intval($max_processes) == 0) { $max_processes = 5; } } else { $process = 'frontend'; - $max_processes = Core\Config::get('system', 'max_processes_frontend'); + $max_processes = $this->config->get('system', 'max_processes_frontend'); if (intval($max_processes) == 0) { $max_processes = 20; } @@ -814,7 +802,7 @@ class App */ public function isMinMemoryReached() { - $min_memory = Core\Config::get('system', 'min_memory', 0); + $min_memory = $this->config->get('system', 'min_memory', 0); if ($min_memory == 0) { return false; } @@ -861,13 +849,13 @@ class App { if ($this->isBackend()) { $process = 'backend'; - $maxsysload = intval(Core\Config::get('system', 'maxloadavg')); + $maxsysload = intval($this->config->get('system', 'maxloadavg')); if ($maxsysload < 1) { $maxsysload = 50; } } else { $process = 'frontend'; - $maxsysload = intval(Core\Config::get('system', 'maxloadavg_frontend')); + $maxsysload = intval($this->config->get('system', 'maxloadavg_frontend')); if ($maxsysload < 1) { $maxsysload = 50; } @@ -914,9 +902,9 @@ class App } if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { - $resource = proc_open('cmd /c start /b ' . $cmdline, [], $foo, $this->basePath); + $resource = proc_open('cmd /c start /b ' . $cmdline, [], $foo, $this->getBasePath()); } else { - $resource = proc_open($cmdline . ' &', [], $foo, $this->basePath); + $resource = proc_open($cmdline . ' &', [], $foo, $this->getBasePath()); } if (!is_resource($resource)) { Core\Logger::log('We got no resource for command ' . $cmdline, Core\Logger::DEBUG); @@ -933,7 +921,7 @@ class App */ public function getSenderEmailAddress() { - $sender_email = Core\Config::get('config', 'sender_email'); + $sender_email = $this->config->get('config', 'sender_email'); if (empty($sender_email)) { $hostname = $this->getHostName(); if (strpos($hostname, ':')) { @@ -977,7 +965,7 @@ class App */ private function computeCurrentTheme() { - $system_theme = Core\Config::get('system', 'theme'); + $system_theme = $this->config->get('system', 'theme'); if (!$system_theme) { throw new Exception(Core\L10n::t('No system theme config value set.')); } @@ -985,7 +973,7 @@ class App // Sane default $this->currentTheme = $system_theme; - $allowed_themes = explode(',', Core\Config::get('system', 'allowed_themes', $system_theme)); + $allowed_themes = explode(',', $this->config->get('system', 'allowed_themes', $system_theme)); $page_theme = null; // Find the theme that belongs to the user whose stuff we are looking at @@ -1002,7 +990,7 @@ class App // Specific mobile theme override if (($this->is_mobile || $this->is_tablet) && Core\Session::get('show-mobile', true)) { - $system_mobile_theme = Core\Config::get('system', 'mobile-theme'); + $system_mobile_theme = $this->config->get('system', 'mobile-theme'); $user_mobile_theme = Core\Session::get('mobile-theme', $system_mobile_theme); // --- means same mobile theme as desktop @@ -1073,7 +1061,7 @@ class App */ public function checkURL() { - $url = Core\Config::get('system', 'url'); + $url = $this->config->get('system', 'url'); // if the url isn't set or the stored url is radically different // than the currently visited url, store the current value accordingly. @@ -1082,7 +1070,7 @@ class App // We will only change the url to an ip address if there is no existing setting if (empty($url) || (!Util\Strings::compareLink($url, $this->getBaseURL())) && (!preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/", $this->getHostName()))) { - Core\Config::set('system', 'url', $this->getBaseURL()); + $this->config->set('system', 'url', $this->getBaseURL()); } } @@ -1115,8 +1103,8 @@ class App if (!$this->getMode()->isInstall()) { // Force SSL redirection - if (Core\Config::get('system', 'force_ssl') && ($this->getScheme() == "http") - && intval(Core\Config::get('system', 'ssl_policy')) == SSL_POLICY_FULL + if ($this->config->get('system', 'force_ssl') && ($this->getScheme() == "http") + && intval($this->config->get('system', 'ssl_policy')) == SSL_POLICY_FULL && strpos($this->getBaseURL(), 'https://') === 0 && $_SERVER['REQUEST_METHOD'] == 'GET') { header('HTTP/1.1 302 Moved Temporarily'); @@ -1199,7 +1187,7 @@ class App $this->module = 'maintenance'; } else { $this->checkURL(); - Core\Update::check($this->basePath, false); + Core\Update::check($this->getBasePath(), false); Core\Addon::loadAddons(); Core\Hook::loadHooks(); } @@ -1256,7 +1244,7 @@ class App $this->module = "login"; } - $privateapps = Core\Config::get('config', 'private_addons', false); + $privateapps = $this->config->get('config', 'private_addons', false); if (Core\Addon::isEnabled($this->module) && file_exists("addon/{$this->module}/{$this->module}.php")) { //Check if module is an app and if public access to apps is allowed or not if ((!local_user()) && Core\Hook::isAddonApp($this->module) && $privateapps) { @@ -1441,7 +1429,7 @@ class App header("X-Friendica-Version: " . FRIENDICA_VERSION); header("Content-type: text/html; charset=utf-8"); - if (Core\Config::get('system', 'hsts') && (Core\Config::get('system', 'ssl_policy') == SSL_POLICY_FULL)) { + if ($this->config->get('system', 'hsts') && ($this->config->get('system', 'ssl_policy') == SSL_POLICY_FULL)) { header("Strict-Transport-Security: max-age=31536000"); } diff --git a/src/Content/ForumManager.php b/src/Content/ForumManager.php index e9dab41ef9..af2c3725c4 100644 --- a/src/Content/ForumManager.php +++ b/src/Content/ForumManager.php @@ -199,13 +199,13 @@ class ForumManager $stmtContacts = DBA::p( "SELECT `contact`.`id`, `contact`.`name`, COUNT(*) AS `count` FROM `item` INNER JOIN `contact` ON `item`.`contact-id` = `contact`.`id` - WHERE `item`.`uid` = %d AND `item`.`visible` AND NOT `item`.`deleted` AND `item`.`unseen` + WHERE `item`.`uid` = ? AND `item`.`visible` AND NOT `item`.`deleted` AND `item`.`unseen` AND `contact`.`network`= 'dfrn' AND (`contact`.`forum` OR `contact`.`prv`) AND NOT `contact`.`blocked` AND NOT `contact`.`hidden` AND NOT `contact`.`pending` AND NOT `contact`.`archive` AND `contact`.`success_update` > `failure_update` GROUP BY `contact`.`id` ", - intval(local_user()) + local_user() ); return DBA::toArray($stmtContacts); diff --git a/src/Content/Smilies.php b/src/Content/Smilies.php index 9023959978..9fbfd2d629 100644 --- a/src/Content/Smilies.php +++ b/src/Content/Smilies.php @@ -213,7 +213,6 @@ class Smilies return $text; } - $text = preg_replace_callback('/
(.*?)<\/pre>/ism'  , 'self::encode', $text);
 		$text = preg_replace_callback('/(.*?)<\/code>/ism', 'self::encode', $text);
 
 		if ($no_images) {
@@ -231,7 +230,6 @@ class Smilies
 		$text = preg_replace_callback('/<(3+)/', 'self::pregHeart', $text);
 		$text = self::strOrigReplace($smilies['texts'], $smilies['icons'], $text);
 
-		$text = preg_replace_callback('/
(.*?)<\/pre>/ism', 'self::decode', $text);
 		$text = preg_replace_callback('/(.*?)<\/code>/ism', 'self::decode', $text);
 
 		return $text;
@@ -244,7 +242,7 @@ class Smilies
 	 */
 	private static function encode($m)
 	{
-		return(str_replace($m[1], Strings::base64UrlEncode($m[1]), $m[0]));
+		return '' . Strings::base64UrlEncode($m[1]) . '';
 	}
 
 	/**
@@ -255,7 +253,7 @@ class Smilies
 	 */
 	private static function decode($m)
 	{
-		return(str_replace($m[1], Strings::base64UrlDecode($m[1]), $m[0]));
+		return '' . Strings::base64UrlDecode($m[1]) . '';
 	}
 
 
diff --git a/src/Core/ACL.php b/src/Core/ACL.php
index 19015714e9..e6c82fd4bf 100644
--- a/src/Core/ACL.php
+++ b/src/Core/ACL.php
@@ -266,14 +266,12 @@ class ACL extends BaseObject
 			$default_permissions = self::getDefaultUserPermissions($user);
 		}
 
-		$jotnets = '';
+		$jotnets_fields = [];
 		if ($show_jotnets) {
-			$imap_disabled = !function_exists('imap_open') || Config::get('system', 'imap_disabled');
-
 			$mail_enabled = false;
 			$pubmail_enabled = false;
 
-			if (!$imap_disabled) {
+			if (function_exists('imap_open') && !Config::get('system', 'imap_disabled')) {
 				$mailacct = DBA::selectFirst('mailacct', ['pubmail'], ['`uid` = ? AND `server` != ""', local_user()]);
 				if (DBA::isResult($mailacct)) {
 					$mail_enabled = true;
@@ -283,17 +281,20 @@ class ACL extends BaseObject
 
 			if (empty($default_permissions['hidewall'])) {
 				if ($mail_enabled) {
-					$selected = $pubmail_enabled ? ' checked="checked"' : '';
-					$jotnets .= '
' . L10n::t("Post to Email") . '
'; + $jotnets_fields[] = [ + 'type' => 'checkbox', + 'field' => [ + 'pubmail_enable', + L10n::t('Post to Email'), + $pubmail_enabled + ] + ]; } - Hook::callAll('jot_networks', $jotnets); - } else { - $jotnets .= L10n::t('Connectors disabled, since "%s" is enabled.', - L10n::t('Hide your profile details from unknown viewers?')); + Hook::callAll('jot_networks', $jotnets_fields); } } - + $tpl = Renderer::getMarkupTemplate('acl_selector.tpl'); $o = Renderer::replaceMacros($tpl, [ '$showall' => L10n::t('Visible to everybody'), @@ -306,7 +307,10 @@ class ACL extends BaseObject '$networks' => $show_jotnets, '$emailcc' => L10n::t('CC: email addresses'), '$emtitle' => L10n::t('Example: bob@example.com, mary@example.com'), - '$jotnets' => $jotnets, + '$jotnets_enabled' => empty($default_permissions['hidewall']), + '$jotnets_summary' => L10n::t('Connectors'), + '$jotnets_fields' => $jotnets_fields, + '$jotnets_disabled_label' => L10n::t('Connectors disabled, since "%s" is enabled.', L10n::t('Hide your profile details from unknown viewers?')), '$aclModalTitle' => L10n::t('Permissions'), '$aclModalDismiss' => L10n::t('Close'), '$features' => [ diff --git a/src/Core/Config/Cache/ConfigCache.php b/src/Core/Config/Cache/ConfigCache.php index cb299eb330..f61865cee6 100644 --- a/src/Core/Config/Cache/ConfigCache.php +++ b/src/Core/Config/Cache/ConfigCache.php @@ -5,7 +5,7 @@ namespace Friendica\Core\Config\Cache; /** * The Friendica config cache for the application * Initial, all *.config.php files are loaded into this cache with the - * ConfigCacheLoader ( @see ConfigCacheLoader ) + * ConfigFileLoader ( @see ConfigFileLoader ) */ class ConfigCache implements IConfigCache, IPConfigCache { diff --git a/src/Core/Config/Configuration.php b/src/Core/Config/Configuration.php index 532ed982a9..c6fe626d91 100644 --- a/src/Core/Config/Configuration.php +++ b/src/Core/Config/Configuration.php @@ -10,6 +10,16 @@ namespace Friendica\Core\Config; */ class Configuration { + /** + * The blacklist of configuration settings, which should not get saved to the backend + * @var array + */ + private $configSaveBlacklist = [ + 'config' => [ + 'hostname' => true, + ] + ]; + /** * @var Cache\IConfigCache */ @@ -117,7 +127,7 @@ class Configuration $cached = $this->configCache->set($cat, $key, $value); // If there is no connected adapter, we're finished - if (!$this->configAdapter->isConnected()) { + if (!$this->configAdapter->isConnected() || !empty($this->configSaveBlacklist[$cat][$key])) { return $cached; } diff --git a/src/Core/Console/AutomaticInstallation.php b/src/Core/Console/AutomaticInstallation.php index 280e3d1071..911c1c00a8 100644 --- a/src/Core/Console/AutomaticInstallation.php +++ b/src/Core/Console/AutomaticInstallation.php @@ -7,6 +7,7 @@ use Friendica\BaseObject; use Friendica\Core\Config; use Friendica\Core\Installer; use Friendica\Core\Theme; +use Friendica\Util\Config\ConfigFileLoader; use RuntimeException; class AutomaticInstallation extends Console @@ -103,8 +104,8 @@ HELP; } //reload the config cache - $loader = new Config\Cache\ConfigCacheLoader($a->getBasePath(), $a->getMode()); - $loader->loadConfigFiles($configCache); + $loader = new ConfigFileLoader($a->getBasePath(), $a->getMode()); + $loader->setupCache($configCache); } else { // Creating config file diff --git a/src/Core/NotificationsManager.php b/src/Core/NotificationsManager.php index f9a286b0e7..5b2e97005a 100644 --- a/src/Core/NotificationsManager.php +++ b/src/Core/NotificationsManager.php @@ -108,7 +108,13 @@ class NotificationsManager extends BaseObject */ public function setSeen($note, $seen = true) { - return DBA::update('notify', ['seen' => $seen], ['link' => $note['link'], 'parent' => $note['parent'], 'otype' => $note['otype'], 'uid' => local_user()]); + return DBA::update('notify', ['seen' => $seen], [ + '(`link` = ? OR (`parent` != 0 AND `parent` = ? AND `otype` = ?)) AND `uid` = ?', + $note['link'], + $note['parent'], + $note['otype'], + local_user() + ]); } /** @@ -557,7 +563,7 @@ class NotificationsManager extends BaseObject $sql_extra = ""; if (!$all) { - $sql_extra = " AND `ignore` = 0 "; + $sql_extra = " AND NOT `ignore` "; } /// @todo Fetch contact details by "Contact::getDetailsByUrl" instead of queries to contact, fcontact and gcontact @@ -572,11 +578,11 @@ class NotificationsManager extends BaseObject LEFT JOIN `contact` ON `contact`.`id` = `intro`.`contact-id` LEFT JOIN `gcontact` ON `gcontact`.`nurl` = `contact`.`nurl` LEFT JOIN `fcontact` ON `intro`.`fid` = `fcontact`.`id` - WHERE `intro`.`uid` = %d $sql_extra AND `intro`.`blocked` = 0 - LIMIT %d, %d", - intval($_SESSION['uid']), - intval($start), - intval($limit) + WHERE `intro`.`uid` = ? $sql_extra AND `intro`.`blocked` = 0 + LIMIT ?, ?", + $_SESSION['uid'], + $start, + $limit ); if (DBA::isResult($stmtNotifies)) { $notifs = $this->formatIntros(DBA::toArray($stmtNotifies)); diff --git a/src/Core/Update.php b/src/Core/Update.php index bb2513d388..0d7b348b42 100644 --- a/src/Core/Update.php +++ b/src/Core/Update.php @@ -2,8 +2,13 @@ namespace Friendica\Core; +use Friendica\App; +use Friendica\Core\Config\Cache\IConfigCache; use Friendica\Database\DBA; use Friendica\Database\DBStructure; +use Friendica\Util\BasePath; +use Friendica\Util\Config\ConfigFileLoader; +use Friendica\Util\Config\ConfigFileSaver; use Friendica\Util\Strings; class Update @@ -24,6 +29,11 @@ class Update return; } + // Don't check the status if the last update was failed + if (Config::get('system', 'update', Update::SUCCESS, true) == Update::FAILED) { + return; + } + $build = Config::get('system', 'build'); if (empty($build)) { @@ -101,7 +111,9 @@ class Update for ($x = $stored + 1; $x <= $current; $x++) { $r = self::runUpdateFunction($x, 'pre_update'); if (!$r) { - break; + Config::set('system', 'update', Update::FAILED); + Lock::release('dbupdate'); + return $r; } } @@ -115,6 +127,7 @@ class Update ); } Logger::error('Update ERROR.', ['from' => $stored, 'to' => $current, 'retval' => $retval]); + Config::set('system', 'update', Update::FAILED); Lock::release('dbupdate'); return $retval; } else { @@ -127,7 +140,9 @@ class Update for ($x = $stored + 1; $x <= $current; $x++) { $r = self::runUpdateFunction($x, 'update'); if (!$r) { - break; + Config::set('system', 'update', Update::FAILED); + Lock::release('dbupdate'); + return $r; } } @@ -136,6 +151,7 @@ class Update self::updateSuccessfull($stored, $current); } + Config::set('system', 'update', Update::SUCCESS); Lock::release('dbupdate'); } } @@ -208,6 +224,93 @@ class Update } } + /** + * Checks the config settings and saves given config values into the config file + * + * @param string $basePath The basepath of Friendica + * @param App\Mode $mode The Application mode + * + * @return bool True, if something has been saved + */ + public static function saveConfigToFile($basePath, App\Mode $mode) + { + $configFileLoader = new ConfigFileLoader($basePath, $mode); + $configCache = new Config\Cache\ConfigCache(); + $configFileLoader->setupCache($configCache, true); + $configFileSaver = new ConfigFileSaver($basePath); + + $updated = false; + + if (self::updateConfigEntry($configCache, $configFileSaver,'config', 'hostname')) { + $updated = true; + }; + + if (self::updateConfigEntry($configCache, $configFileSaver,'system', 'basepath', BasePath::create(dirname(__DIR__) . '/../'))) { + $updated = true; + } + + // In case there is nothing to do, skip the update + if (!$updated) { + return true; + } + + if (!$configFileSaver->saveToConfigFile()) { + Logger::alert('Config entry update failed - maybe wrong permission?'); + return false; + } + + DBA::delete('config', ['cat' => 'config', 'k' => 'hostname']); + DBA::delete('config', ['cat' => 'system', 'k' => 'basepath']); + + return true; + } + + /** + * Adds a value to the ConfigFileSave in case it isn't already updated + * + * @param IConfigCache $configCache The cached config file + * @param ConfigFileSaver $configFileSaver The config file saver + * @param string $cat The config category + * @param string $key The config key + * @param string $default A default value, if none of the settings are valid + * + * @return boolean True, if a value was updated + * + * @throws \Exception if DBA or Logger doesn't work + */ + private static function updateConfigEntry(IConfigCache $configCache, ConfigFileSaver $configFileSaver, $cat, $key, $default = '') + { + // check if the config file differs from the whole configuration (= The db contains other values) + $fileConfig = $configCache->get($cat, $key); + + $savedConfig = DBA::selectFirst('config', ['v'], ['cat' => $cat, 'k' => $key]); + + if (DBA::isResult($savedConfig)) { + $savedValue = $savedConfig['v']; + } else { + $savedValue = null; + } + + // If the db contains a config value, check it + if (isset($savedValue) && $fileConfig !== $savedValue) { + Logger::info('Difference in config found', ['cat' => $cat, 'key' => $key, 'file' => $fileConfig, 'saved' => $savedValue]); + $configFileSaver->addConfigValue($cat, $key, $savedValue); + return true; + + // If both config values are not set, use the default value + } elseif (!isset($fileConfig) && !isset($savedValue)) { + Logger::info('Using default for config', ['cat' => $cat, 'key' => $key, 'value' => $default]); + $configFileSaver->addConfigValue($cat, $key, $default); + return true; + + // If either the file config value isn't empty or the db value is the same as the + // file config value, skip it + } else { + Logger::info('No Difference in config found', ['cat' => $cat, 'key' => $key, 'value' => $fileConfig, 'saved' => $savedValue]); + return false; + } + } + /** * send the email and do what is needed to do on update fails * diff --git a/src/Core/Worker.php b/src/Core/Worker.php index ac5c12fdd2..0f4a527f6b 100644 --- a/src/Core/Worker.php +++ b/src/Core/Worker.php @@ -27,6 +27,9 @@ class Worker const STATE_REFETCH = 3; // Worker had refetched jobs in the execution loop. const STATE_SHORT_LOOP = 4; // Worker is processing preassigned jobs, thus saving much time. + const FAST_COMMANDS = ['APDelivery', 'Delivery', 'CreateShadowEntry']; + + private static $up_start; private static $db_duration = 0; private static $db_duration_count = 0; @@ -783,23 +786,24 @@ class Worker return []; } - if ($priority <= PRIORITY_MEDIUM) { - $limit = Config::get('system', 'worker_fetch_limit', 1); - } else { - $limit = 1; - } + $limit = Config::get('system', 'worker_fetch_limit', 1); $ids = []; $stamp = (float)microtime(true); $condition = ["`priority` = ? AND `pid` = 0 AND NOT `done` AND `next_try` < ?", $priority, DateTimeFormat::utcNow()]; - $tasks = DBA::select('workerqueue', ['id'], $condition, ['limit' => $limit, 'order' => ['created']]); + $tasks = DBA::select('workerqueue', ['id', 'parameter'], $condition, ['limit' => $limit, 'order' => ['created']]); self::$db_duration += (microtime(true) - $stamp); while ($task = DBA::fetch($tasks)) { $ids[] = $task['id']; + // Only continue that loop while we are storing commands that can be processed quickly + $command = json_decode($task['parameter'])[0]; + if (!in_array($command, self::FAST_COMMANDS)) { + break; + } } DBA::close($tasks); - Logger::info('Found:', ['id' => $ids, 'priority' => $priority]); + Logger::info('Found:', ['priority' => $priority, 'id' => $ids]); return $ids; } @@ -890,15 +894,22 @@ class Worker // If there is no result we check without priority limit if (empty($ids)) { + $limit = Config::get('system', 'worker_fetch_limit', 1); + $stamp = (float)microtime(true); $condition = ["`pid` = 0 AND NOT `done` AND `next_try` < ?", DateTimeFormat::utcNow()]; - $result = DBA::select('workerqueue', ['id'], $condition, ['limit' => 1, 'order' => ['priority', 'created']]); + $tasks = DBA::select('workerqueue', ['id', 'parameter'], $condition, ['limit' => $limit, 'order' => ['priority', 'created']]); self::$db_duration += (microtime(true) - $stamp); - while ($id = DBA::fetch($result)) { - $ids[] = $id["id"]; + while ($task = DBA::fetch($tasks)) { + $ids[] = $task['id']; + // Only continue that loop while we are storing commands that can be processed quickly + $command = json_decode($task['parameter'])[0]; + if (!in_array($command, self::FAST_COMMANDS)) { + break; + } } - DBA::close($result); + DBA::close($tasks); } if (!empty($ids)) { diff --git a/src/Factory/ConfigFactory.php b/src/Factory/ConfigFactory.php index 6a30cf0e05..1f9662bddb 100644 --- a/src/Factory/ConfigFactory.php +++ b/src/Factory/ConfigFactory.php @@ -6,18 +6,19 @@ use Friendica\Core; use Friendica\Core\Config; use Friendica\Core\Config\Adapter; use Friendica\Core\Config\Cache; +use Friendica\Util\Config\ConfigFileLoader; class ConfigFactory { /** - * @param Cache\ConfigCacheLoader $loader The Config Cache loader (INI/config/.htconfig) + * @param ConfigFileLoader $loader The Config Cache loader (INI/config/.htconfig) * * @return Cache\ConfigCache */ - public static function createCache(Cache\ConfigCacheLoader $loader) + public static function createCache(ConfigFileLoader $loader) { $configCache = new Cache\ConfigCache(); - $loader->loadConfigFiles($configCache); + $loader->setupCache($configCache); return $configCache; } diff --git a/src/Factory/DependencyFactory.php b/src/Factory/DependencyFactory.php index 265dca88ef..63defd95f5 100644 --- a/src/Factory/DependencyFactory.php +++ b/src/Factory/DependencyFactory.php @@ -3,9 +3,9 @@ namespace Friendica\Factory; use Friendica\App; -use Friendica\Core\Config\Cache; use Friendica\Factory; use Friendica\Util\BasePath; +use Friendica\Util\Config; class DependencyFactory { @@ -24,7 +24,7 @@ class DependencyFactory { $basePath = BasePath::create($directory, $_SERVER); $mode = new App\Mode($basePath); - $configLoader = new Cache\ConfigCacheLoader($basePath, $mode); + $configLoader = new Config\ConfigFileLoader($basePath, $mode); $configCache = Factory\ConfigFactory::createCache($configLoader); $profiler = Factory\ProfilerFactory::create($configCache); Factory\DBFactory::init($basePath, $configCache, $profiler, $_SERVER); @@ -34,6 +34,6 @@ class DependencyFactory $logger = Factory\LoggerFactory::create($channel, $config, $profiler); Factory\LoggerFactory::createDev($channel, $config, $profiler); - return new App($basePath, $config, $mode, $logger, $profiler, $isBackend); + return new App($config, $mode, $logger, $profiler, $isBackend); } } diff --git a/src/Model/APContact.php b/src/Model/APContact.php index bf36306b41..97178f697c 100644 --- a/src/Model/APContact.php +++ b/src/Model/APContact.php @@ -123,11 +123,14 @@ class APContact extends BaseObject $apcontact['following'] = JsonLD::fetchElement($compacted, 'as:following', '@id'); $apcontact['followers'] = JsonLD::fetchElement($compacted, 'as:followers', '@id'); $apcontact['inbox'] = JsonLD::fetchElement($compacted, 'ldp:inbox', '@id'); + self::unarchiveInbox($apcontact['inbox'], false); + $apcontact['outbox'] = JsonLD::fetchElement($compacted, 'as:outbox', '@id'); $apcontact['sharedinbox'] = ''; if (!empty($compacted['as:endpoints'])) { $apcontact['sharedinbox'] = JsonLD::fetchElement($compacted['as:endpoints'], 'as:sharedInbox', '@id'); + self::unarchiveInbox($apcontact['sharedinbox'], true); } $apcontact['nick'] = JsonLD::fetchElement($compacted, 'as:preferredUsername'); @@ -231,4 +234,27 @@ class APContact extends BaseObject return $apcontact; } + + /** + * Unarchive inboxes + * + * @param string $url inbox url + */ + private static function unarchiveInbox($url, $shared) + { + if (empty($url)) { + return; + } + + $now = DateTimeFormat::utcNow(); + + $fields = ['archive' => false, 'success' => $now, 'shared' => $shared]; + + if (!DBA::exists('inbox-status', ['url' => $url])) { + $fields = array_merge($fields, ['url' => $url, 'created' => $now]); + DBA::insert('inbox-status', $fields); + } else { + DBA::update('inbox-status', $fields, ['url' => $url]); + } + } } diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php index ce23e6db20..eb1da09b3a 100644 --- a/src/Protocol/ActivityPub/Transmitter.php +++ b/src/Protocol/ActivityPub/Transmitter.php @@ -465,6 +465,18 @@ class Transmitter return $receivers; } + /** + * Check if an inbox is archived + * + * @param string $url Inbox url + * + * @return boolean "true" if inbox is archived + */ + private static function archivedInbox($url) + { + return DBA::exists('inbox-status', ['url' => $url, 'archive' => true]); + } + /** * Fetches a list of inboxes of followers of a given user * @@ -506,7 +518,9 @@ class Transmitter } else { $target = $profile['sharedinbox']; } - $inboxes[$target] = $target; + if (!self::archivedInbox($target)) { + $inboxes[$target] = $target; + } } } DBA::close($contacts); @@ -563,7 +577,9 @@ class Transmitter } else { $target = $profile['sharedinbox']; } - $inboxes[$target] = $target; + if (!self::archivedInbox($target)) { + $inboxes[$target] = $target; + } } } } diff --git a/src/Core/Config/Cache/ConfigCacheLoader.php b/src/Util/Config/ConfigFileLoader.php similarity index 80% rename from src/Core/Config/Cache/ConfigCacheLoader.php rename to src/Util/Config/ConfigFileLoader.php index 9e06d8fb89..3d4f31a9d1 100644 --- a/src/Core/Config/Cache/ConfigCacheLoader.php +++ b/src/Util/Config/ConfigFileLoader.php @@ -1,29 +1,21 @@ appMode = $mode; - $this->baseDir = $baseDir; - $this->configDir = $baseDir . DIRECTORY_SEPARATOR . self::SUBDIRECTORY; } /** - * Load the configuration files + * Load the configuration files into an configuration cache * * First loads the default value for all the configuration keys, then the legacy configuration files, then the * expected local.config.php * - * @param IConfigCache The config cache to load to + * @param IConfigCache $config The config cache to load to + * @param bool $raw Setup the raw config format * * @throws \Exception */ - public function loadConfigFiles(IConfigCache $config) + public function setupCache(IConfigCache $config, $raw = false) { $config->load($this->loadCoreConfig('defaults')); $config->load($this->loadCoreConfig('settings')); @@ -57,7 +49,7 @@ class ConfigCacheLoader $config->load($this->loadCoreConfig('local'), true); // In case of install mode, add the found basepath (because there isn't a basepath set yet - if ($this->appMode->isInstall()) { + if (!$raw && ($this->appMode->isInstall() || empty($config->get('system', 'basepath')))) { // Setting at least the basepath we know $config->set('system', 'basepath', $this->baseDir); } @@ -66,18 +58,18 @@ class ConfigCacheLoader /** * Tries to load the specified core-configuration and returns the config array. * - * @param string $name The name of the configuration + * @param string $name The name of the configuration (default is empty, which means 'local') * * @return array The config array (empty if no config found) * * @throws \Exception if the configuration file isn't readable */ - public function loadCoreConfig($name) + public function loadCoreConfig($name = '') { - if (file_exists($this->configDir . DIRECTORY_SEPARATOR . $name . '.config.php')) { - return $this->loadConfigFile($this->configDir . DIRECTORY_SEPARATOR . $name . '.config.php'); - } elseif (file_exists($this->configDir . DIRECTORY_SEPARATOR . $name . '.ini.php')) { - return $this->loadINIConfigFile($this->configDir . DIRECTORY_SEPARATOR . $name . '.ini.php'); + if (!empty($this->getConfigFullName($name))) { + return $this->loadConfigFile($this->getConfigFullName($name)); + } elseif (!empty($this->getIniFullName($name))) { + return $this->loadINIConfigFile($this->getIniFullName($name)); } else { return []; } @@ -110,22 +102,19 @@ class ConfigCacheLoader /** * Tries to load the legacy config files (.htconfig.php, .htpreconfig.php) and returns the config array. * - * @param string $name The name of the config file + * @param string $name The name of the config file (default is empty, which means .htconfig.php) * * @return array The configuration array (empty if no config found) * * @deprecated since version 2018.09 */ - private function loadLegacyConfig($name) + private function loadLegacyConfig($name = '') { - $filePath = $this->baseDir . DIRECTORY_SEPARATOR . '.' . $name . '.php'; - $config = []; - - if (file_exists($filePath)) { + if (!empty($this->getHtConfigFullName($name))) { $a = new \stdClass(); $a->config = []; - include $filePath; + include $this->getHtConfigFullName($name); $htConfigCategories = array_keys($a->config); diff --git a/src/Util/Config/ConfigFileManager.php b/src/Util/Config/ConfigFileManager.php new file mode 100644 index 0000000000..729e59d746 --- /dev/null +++ b/src/Util/Config/ConfigFileManager.php @@ -0,0 +1,90 @@ +baseDir = $baseDir; + $this->configDir = $baseDir . DIRECTORY_SEPARATOR . self::SUBDIRECTORY; + } + + /** + * Gets the full name (including the path) for a *.config.php (default is local.config.php) + * + * @param string $name The config name (default is empty, which means local.config.php) + * + * @return string The full name or empty if not found + */ + protected function getConfigFullName($name = '') + { + $name = !empty($name) ? $name : self::CONFIG_LOCAL; + + $fullName = $this->configDir . DIRECTORY_SEPARATOR . $name . '.config.php'; + return file_exists($fullName) ? $fullName : ''; + } + + /** + * Gets the full name (including the path) for a *.ini.php (default is local.ini.php) + * + * @param string $name The config name (default is empty, which means local.ini.php) + * + * @return string The full name or empty if not found + */ + protected function getIniFullName($name = '') + { + $name = !empty($name) ? $name : self::CONFIG_INI; + + $fullName = $this->configDir . DIRECTORY_SEPARATOR . $name . '.ini.php'; + return file_exists($fullName) ? $fullName : ''; + } + + /** + * Gets the full name (including the path) for a .*.php (default is .htconfig.php) + * + * @param string $name The config name (default is empty, which means .htconfig.php) + * + * @return string The full name or empty if not found + */ + protected function getHtConfigFullName($name = '') + { + $name = !empty($name) ? $name : self::CONFIG_HTCONFIG; + + $fullName = $this->baseDir . DIRECTORY_SEPARATOR . '.' . $name . '.php'; + return file_exists($fullName) ? $fullName : ''; + } +} diff --git a/src/Util/Config/ConfigFileSaver.php b/src/Util/Config/ConfigFileSaver.php new file mode 100644 index 0000000000..e32e11a4d5 --- /dev/null +++ b/src/Util/Config/ConfigFileSaver.php @@ -0,0 +1,341 @@ +settings)); + + for ($i = 0; $i < $settingsCount; $i++) { + // if already set, overwrite the value + if ($this->settings[$i]['cat'] === $cat && + $this->settings[$i]['key'] === $key) { + $this->settings[$i] = ['cat' => $cat, 'key' => $key, 'value' => $value]; + return; + } + } + + $this->settings[] = ['cat' => $cat, 'key' => $key, 'value' => $value]; + } + + /** + * Resetting all added configuration entries so far + */ + public function reset() + { + $this->settings = []; + } + + /** + * Save all added configuration entries to the given config files + * After updating the config entries, all configuration entries will be reseted + * + * @param string $name The name of the configuration file (default is empty, which means the default name each type) + * + * @return bool true, if at least one configuration file was successfully updated or nothing to do + */ + public function saveToConfigFile($name = '') + { + // If no settings et, return true + if (count(array_keys($this->settings)) === 0) { + return true; + } + + $saved = false; + + // Check for the *.config.php file inside the /config/ path + list($reading, $writing) = $this->openFile($this->getConfigFullName($name)); + if (isset($reading) && isset($writing)) { + $this->saveConfigFile($reading, $writing); + // Close the current file handler and rename them + if ($this->closeFile($this->getConfigFullName($name), $reading, $writing)) { + // just return true, if everything went fine + $saved = true; + } + } + + // Check for the *.ini.php file inside the /config/ path + list($reading, $writing) = $this->openFile($this->getIniFullName($name)); + if (isset($reading) && isset($writing)) { + $this->saveINIConfigFile($reading, $writing); + // Close the current file handler and rename them + if ($this->closeFile($this->getIniFullName($name), $reading, $writing)) { + // just return true, if everything went fine + $saved = true; + } + } + + // Check for the *.php file (normally .htconfig.php) inside the / path + list($reading, $writing) = $this->openFile($this->getHtConfigFullName($name)); + if (isset($reading) && isset($writing)) { + $this->saveToLegacyConfig($reading, $writing); + // Close the current file handler and rename them + if ($this->closeFile($this->getHtConfigFullName($name), $reading, $writing)) { + // just return true, if everything went fine + $saved = true; + } + } + + $this->reset(); + + return $saved; + } + + /** + * Opens a config file and returns two handler for reading and writing + * + * @param string $fullName The full name of the current config + * + * @return array An array containing the two reading and writing handler + */ + private function openFile($fullName) + { + if (empty($fullName)) { + return [null, null]; + } + + try { + $reading = fopen($fullName, 'r'); + } catch (\Exception $exception) { + return [null, null]; + } + + if (!$reading) { + return [null, null]; + } + + try { + $writing = fopen($fullName . '.tmp', 'w'); + } catch (\Exception $exception) { + fclose($reading); + return [null, null]; + } + + if (!$writing) { + fclose($reading); + return [null, null]; + } + + return [$reading, $writing]; + } + + /** + * Close and rename the config file + * + * @param string $fullName The full name of the current config + * @param resource $reading The reading resource handler + * @param resource $writing The writing resource handler + * + * @return bool True, if the close was successful + */ + private function closeFile($fullName, $reading, $writing) + { + fclose($reading); + fclose($writing); + + try { + $renamed = rename($fullName, $fullName . '.old'); + } catch (\Exception $exception) { + return false; + } + + if (!$renamed) { + return false; + } + + try { + $renamed = rename($fullName . '.tmp', $fullName); + } catch (\Exception $exception) { + // revert the move of the current config file to have at least the old config + rename($fullName . '.old', $fullName); + return false; + } + + if (!$renamed) { + // revert the move of the current config file to have at least the old config + rename($fullName . '.old', $fullName); + return false; + } + + return true; + } + + /** + * Saves all configuration values to a config file + * + * @param resource $reading The reading handler + * @param resource $writing The writing handler + */ + private function saveConfigFile($reading, $writing) + { + $settingsCount = count(array_keys($this->settings)); + $categoryFound = array_fill(0, $settingsCount, false); + $categoryBracketFound = array_fill(0, $settingsCount, false);; + $lineFound = array_fill(0, $settingsCount, false);; + $lineArrowFound = array_fill(0, $settingsCount, false);; + + while (!feof($reading)) { + + $line = fgets($reading); + + // check for each added setting if we have to replace a config line + for ($i = 0; $i < $settingsCount; $i++) { + + // find the first line like "'system' =>" + if (!$categoryFound[$i] && stristr($line, sprintf('\'%s\'', $this->settings[$i]['cat']))) { + $categoryFound[$i] = true; + } + + // find the first line with a starting bracket ( "[" ) + if ($categoryFound[$i] && !$categoryBracketFound[$i] && stristr($line, '[')) { + $categoryBracketFound[$i] = true; + } + + // find the first line with the key like "'value'" + if ($categoryBracketFound[$i] && !$lineFound[$i] && stristr($line, sprintf('\'%s\'', $this->settings[$i]['key']))) { + $lineFound[$i] = true; + } + + // find the first line with an arrow ("=>") after finding the key + if ($lineFound[$i] && !$lineArrowFound[$i] && stristr($line, '=>')) { + $lineArrowFound[$i] = true; + } + + // find the current value and replace it + if ($lineArrowFound[$i] && preg_match_all('/\'(.*?)\'/', $line, $matches, PREG_SET_ORDER)) { + $lineVal = end($matches)[0]; + $line = str_replace($lineVal, '\'' . $this->settings[$i]['value'] . '\'', $line); + $categoryFound[$i] = false; + $categoryBracketFound[$i] = false; + $lineFound[$i] = false; + $lineArrowFound[$i] = false; + // if a line contains a closing bracket for the category ( "]" ) and we didn't find the key/value pair, + // add it as a new line before the closing bracket + } elseif ($categoryBracketFound[$i] && !$lineArrowFound[$i] && stristr($line, ']')) { + $categoryFound[$i] = false; + $categoryBracketFound[$i] = false; + $lineFound[$i] = false; + $lineArrowFound[$i] = false; + $newLine = sprintf(self::INDENT . self::INDENT . '\'%s\' => \'%s\',' . PHP_EOL, $this->settings[$i]['key'], $this->settings[$i]['value']); + $line = $newLine . $line; + } + } + + fputs($writing, $line); + } + } + + /** + * Saves a value to a ini file + * + * @param resource $reading The reading handler + * @param resource $writing The writing handler + */ + private function saveINIConfigFile($reading, $writing) + { + $settingsCount = count(array_keys($this->settings)); + $categoryFound = array_fill(0, $settingsCount, false); + + while (!feof($reading)) { + + $line = fgets($reading); + + // check for each added setting if we have to replace a config line + for ($i = 0; $i < $settingsCount; $i++) { + + // find the category of the current setting + if (!$categoryFound[$i] && stristr($line, sprintf('[%s]', $this->settings[$i]['cat']))) { + $categoryFound[$i] = true; + + // check the current value + } elseif ($categoryFound[$i] && preg_match_all('/^' . $this->settings[$i]['key'] . '\s*=\s*(.*?)$/', $line, $matches, PREG_SET_ORDER)) { + $line = $this->settings[$i]['key'] . ' = ' . $this->settings[$i]['value'] . PHP_EOL; + $categoryFound[$i] = false; + + // If end of INI file, add the line before the INI end + } elseif ($categoryFound[$i] && (preg_match_all('/^\[.*?\]$/', $line) || preg_match_all('/^INI;.*$/', $line))) { + $categoryFound[$i] = false; + $newLine = $this->settings[$i]['key'] . ' = ' . $this->settings[$i]['value'] . PHP_EOL; + $line = $newLine . $line; + } + } + + fputs($writing, $line); + } + } + + /** + * Saves a value to a .php file (normally .htconfig.php) + * + * @param resource $reading The reading handler + * @param resource $writing The writing handler + */ + private function saveToLegacyConfig($reading, $writing) + { + $settingsCount = count(array_keys($this->settings)); + $found = array_fill(0, $settingsCount, false); + while (!feof($reading)) { + + $line = fgets($reading); + + // check for each added setting if we have to replace a config line + for ($i = 0; $i < $settingsCount; $i++) { + + // check for a non plain config setting (use category too) + if ($this->settings[$i]['cat'] !== 'config' && preg_match_all('/^\$a\-\>config\[\'' . $this->settings[$i]['cat'] . '\'\]\[\'' . $this->settings[$i]['key'] . '\'\]\s*=\s\'*(.*?)\';$/', $line, $matches, PREG_SET_ORDER)) { + $line = '$a->config[\'' . $this->settings[$i]['cat'] . '\'][\'' . $this->settings[$i]['key'] . '\'] = \'' . $this->settings[$i]['value'] . '\';' . PHP_EOL; + $found[$i] = true; + + // check for a plain config setting (don't use a category) + } elseif ($this->settings[$i]['cat'] === 'config' && preg_match_all('/^\$a\-\>config\[\'' . $this->settings[$i]['key'] . '\'\]\s*=\s\'*(.*?)\';$/', $line, $matches, PREG_SET_ORDER)) { + $line = '$a->config[\'' . $this->settings[$i]['key'] . '\'] = \'' . $this->settings[$i]['value'] . '\';' . PHP_EOL; + $found[$i] = true; + } + } + + fputs($writing, $line); + } + + for ($i = 0; $i < $settingsCount; $i++) { + if (!$found[$i]) { + if ($this->settings[$i]['cat'] !== 'config') { + $line = '$a->config[\'' . $this->settings[$i]['cat'] . '\'][\'' . $this->settings[$i]['key'] . '\'] = \'' . $this->settings[$i]['value'] . '\';' . PHP_EOL; + } else { + $line = '$a->config[\'' . $this->settings[$i]['key'] . '\'] = \'' . $this->settings[$i]['value'] . '\';' . PHP_EOL; + } + + fputs($writing, $line); + } + } + } +} diff --git a/src/Util/HTTPSignature.php b/src/Util/HTTPSignature.php index d5e1732c08..e002d5981c 100644 --- a/src/Util/HTTPSignature.php +++ b/src/Util/HTTPSignature.php @@ -5,6 +5,7 @@ */ namespace Friendica\Util; +use Friendica\Database\DBA; use Friendica\Core\Config; use Friendica\Core\Logger; use Friendica\Model\User; @@ -314,7 +315,66 @@ class HTTPSignature Logger::log('Transmit to ' . $target . ' returned ' . $return_code, Logger::DEBUG); - return ($return_code >= 200) && ($return_code <= 299); + $success = ($return_code >= 200) && ($return_code <= 299); + + self::setInboxStatus($target, $success); + + return $success; + } + + /** + * @brief Set the delivery status for a given inbox + * + * @param string $url The URL of the inbox + * @param boolean $success Transmission status + */ + static private function setInboxStatus($url, $success) + { + $now = DateTimeFormat::utcNow(); + + $status = DBA::selectFirst('inbox-status', [], ['url' => $url]); + if (!DBA::isResult($status)) { + DBA::insert('inbox-status', ['url' => $url, 'created' => $now]); + $status = DBA::selectFirst('inbox-status', [], ['url' => $url]); + } + + if ($success) { + $fields = ['success' => $now]; + } else { + $fields = ['failure' => $now]; + } + + if ($status['failure'] > DBA::NULL_DATETIME) { + $new_previous_stamp = strtotime($status['failure']); + $old_previous_stamp = strtotime($status['previous']); + + // Only set "previous" with at least one day difference. + // We use this to assure to not accidentally archive too soon. + if (($new_previous_stamp - $old_previous_stamp) >= 86400) { + $fields['previous'] = $status['failure']; + } + } + + if (!$success) { + if ($status['success'] <= DBA::NULL_DATETIME) { + $stamp1 = strtotime($status['created']); + } else { + $stamp1 = strtotime($status['success']); + } + + $stamp2 = strtotime($now); + $previous_stamp = strtotime($status['previous']); + + // Archive the inbox when there had been failures for five days. + // Additionally ensure that at least one previous attempt has to be in between. + if ((($stamp2 - $stamp1) >= 86400 * 5) && ($previous_stamp > $stamp1)) { + $fields['archive'] = true; + } + } else { + $fields['archive'] = false; + } + + DBA::update('inbox-status', $fields, ['url' => $url]); } /** diff --git a/src/Worker/DBUpdate.php b/src/Worker/DBUpdate.php index 05bace1467..001df25a81 100644 --- a/src/Worker/DBUpdate.php +++ b/src/Worker/DBUpdate.php @@ -6,12 +6,16 @@ namespace Friendica\Worker; use Friendica\BaseObject; +use Friendica\Core\Config; use Friendica\Core\Update; class DBUpdate extends BaseObject { public static function execute() { - Update::run(self::getApp()->getBasePath()); + // Just in case the last update wasn't failed + if (Config::get('system', 'update', Update::SUCCESS, true) != Update::FAILED) { + Update::run(self::getApp()->getBasePath()); + } } } diff --git a/tests/DatabaseTest.php b/tests/DatabaseTest.php index 6a64b28816..fec31b05af 100644 --- a/tests/DatabaseTest.php +++ b/tests/DatabaseTest.php @@ -6,10 +6,10 @@ namespace Friendica\Test; use Friendica\App; -use Friendica\Core\Config\Cache; use Friendica\Database\DBA; use Friendica\Factory; use Friendica\Util\BasePath; +use Friendica\Util\Config\ConfigFileLoader; use Friendica\Util\Profiler; use PHPUnit\DbUnit\DataSet\YamlDataSet; use PHPUnit\DbUnit\TestCaseTrait; @@ -43,7 +43,7 @@ abstract class DatabaseTest extends MockedTest $basePath = BasePath::create(dirname(__DIR__)); $mode = new App\Mode($basePath); - $configLoader = new Cache\ConfigCacheLoader($basePath, $mode); + $configLoader = new ConfigFileLoader($basePath, $mode); $config = Factory\ConfigFactory::createCache($configLoader); $profiler = \Mockery::mock(Profiler::class); diff --git a/tests/datasets/config/.htconfig.test.php b/tests/datasets/config/.htconfig.php similarity index 95% rename from tests/datasets/config/.htconfig.test.php rename to tests/datasets/config/.htconfig.php index 193142c49c..04e3e9cc96 100644 --- a/tests/datasets/config/.htconfig.test.php +++ b/tests/datasets/config/.htconfig.php @@ -33,7 +33,7 @@ $a->config['sitename'] = "Friendica My Network"; // must precisely match the email address of the person logged in. $a->config['register_policy'] = REGISTER_OPEN; $a->config['register_text'] = 'A register text'; -$a->config['admin_email'] = 'admin@friendica.local'; +$a->config['admin_email'] = 'admin@test.it'; $a->config['admin_nickname'] = 'Friendly admin'; // Maximum size of an imported message, 0 is unlimited @@ -52,7 +52,7 @@ $a->config['system']['huburl'] = '[internal]'; $a->config['system']['allowed_themes'] = 'quattro,vier,duepuntozero'; // default system theme -$a->config['system']['theme'] = 'duepuntozero'; +$a->config['system']['theme'] = 'frio'; // By default allow pseudonyms $a->config['system']['no_regfullname'] = true; diff --git a/tests/datasets/config/local.config.php b/tests/datasets/config/local.config.php index 8a392909f2..f28e1f2e85 100644 --- a/tests/datasets/config/local.config.php +++ b/tests/datasets/config/local.config.php @@ -23,5 +23,6 @@ return [ 'system' => [ 'default_timezone' => 'UTC', 'language' => 'en', + 'theme' => 'frio', ], ]; diff --git a/tests/datasets/config/local.ini.php b/tests/datasets/config/local.ini.php index 1fea0b028e..a9e462d13e 100644 --- a/tests/datasets/config/local.ini.php +++ b/tests/datasets/config/local.ini.php @@ -11,6 +11,9 @@ username = testuser password = testpw database = testdb +[system] +theme = frio + [config] admin_email = admin@test.it INI; diff --git a/tests/include/ApiTest.php b/tests/include/ApiTest.php index df3a7e9401..80a25c20c1 100644 --- a/tests/include/ApiTest.php +++ b/tests/include/ApiTest.php @@ -7,13 +7,13 @@ namespace Friendica\Test; use Friendica\App; use Friendica\Core\Config; -use Friendica\Core\Config\Cache; use Friendica\Core\PConfig; use Friendica\Core\Protocol; use Friendica\Core\System; use Friendica\Factory; use Friendica\Network\HTTPException; use Friendica\Util\BasePath; +use Friendica\Util\Config\ConfigFileLoader; use Monolog\Handler\TestHandler; require_once __DIR__ . '/../../include/api.php'; @@ -50,14 +50,14 @@ class ApiTest extends DatabaseTest { $basePath = BasePath::create(dirname(__DIR__) . '/../'); $mode = new App\Mode($basePath); - $configLoader = new Cache\ConfigCacheLoader($basePath, $mode); + $configLoader = new ConfigFileLoader($basePath, $mode); $configCache = Factory\ConfigFactory::createCache($configLoader); $profiler = Factory\ProfilerFactory::create($configCache); Factory\DBFactory::init($basePath, $configCache, $profiler, $_SERVER); $config = Factory\ConfigFactory::createConfig($configCache); Factory\ConfigFactory::createPConfig($configCache); $logger = Factory\LoggerFactory::create('test', $config, $profiler); - $this->app = new App($basePath, $config, $mode, $logger, $profiler, false); + $this->app = new App($config, $mode, $logger, $profiler, false); parent::setUp(); diff --git a/tests/src/Content/SmiliesTest.php b/tests/src/Content/SmiliesTest.php new file mode 100644 index 0000000000..40d126e005 --- /dev/null +++ b/tests/src/Content/SmiliesTest.php @@ -0,0 +1,69 @@ +setUpVfsDir(); + $this->mockApp($this->root); + $this->app->videowidth = 425; + $this->app->videoheight = 350; + $this->configMock->shouldReceive('get') + ->with('system', 'no_smilies') + ->andReturn(false); + $this->configMock->shouldReceive('get') + ->with(false, 'system', 'no_smilies') + ->andReturn(false); + } + + public function dataLinks() + { + return [ + /** @see https://github.com/friendica/friendica/pull/6933 */ + 'bug-6933-1' => [ + 'data' => '/', + 'smilies' => ['texts' => [], 'icons' => []], + 'expected' => '/', + ], + 'bug-6933-2' => [ + 'data' => 'code', + 'smilies' => ['texts' => [], 'icons' => []], + 'expected' => 'code', + ], + ]; + } + + /** + * Test replace smilies in different texts + * @dataProvider dataLinks + * + * @param string $text Test string + * @param array $smilies List of smilies to replace + * @param string $expected Expected result + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public function testReplaceFromArray($text, $smilies, $expected) + { + $output = Smilies::replaceFromArray($text, $smilies); + $this->assertEquals($expected, $output); + } +} diff --git a/tests/src/Database/DBATest.php b/tests/src/Database/DBATest.php index e638e27405..c941377219 100644 --- a/tests/src/Database/DBATest.php +++ b/tests/src/Database/DBATest.php @@ -3,11 +3,11 @@ namespace Friendica\Test\src\Database; use Friendica\App; use Friendica\Core\Config; -use Friendica\Core\Config\Cache; use Friendica\Database\DBA; use Friendica\Factory; use Friendica\Test\DatabaseTest; use Friendica\Util\BasePath; +use Friendica\Util\Config\ConfigFileLoader; class DBATest extends DatabaseTest { @@ -15,14 +15,14 @@ class DBATest extends DatabaseTest { $basePath = BasePath::create(dirname(__DIR__) . '/../../'); $mode = new App\Mode($basePath); - $configLoader = new Cache\ConfigCacheLoader($basePath, $mode); + $configLoader = new ConfigFileLoader($basePath, $mode); $configCache = Factory\ConfigFactory::createCache($configLoader); $profiler = Factory\ProfilerFactory::create($configCache); Factory\DBFactory::init($basePath, $configCache, $profiler, $_SERVER); $config = Factory\ConfigFactory::createConfig($configCache); Factory\ConfigFactory::createPConfig($configCache); $logger = Factory\LoggerFactory::create('test', $config, $profiler); - $this->app = new App($basePath, $config, $mode, $logger, $profiler, false); + $this->app = new App($config, $mode, $logger, $profiler, false); parent::setUp(); diff --git a/tests/src/Database/DBStructureTest.php b/tests/src/Database/DBStructureTest.php index 34f659b51c..152014c114 100644 --- a/tests/src/Database/DBStructureTest.php +++ b/tests/src/Database/DBStructureTest.php @@ -3,11 +3,11 @@ namespace Friendica\Test\src\Database; use Friendica\App; -use Friendica\Core\Config\Cache; use Friendica\Database\DBStructure; use Friendica\Factory; use Friendica\Test\DatabaseTest; use Friendica\Util\BasePath; +use Friendica\Util\Config\ConfigFileLoader; class DBStructureTest extends DatabaseTest { @@ -15,14 +15,14 @@ class DBStructureTest extends DatabaseTest { $basePath = BasePath::create(dirname(__DIR__) . '/../../'); $mode = new App\Mode($basePath); - $configLoader = new Cache\ConfigCacheLoader($basePath, $mode); + $configLoader = new ConfigFileLoader($basePath, $mode); $configCache = Factory\ConfigFactory::createCache($configLoader); $profiler = Factory\ProfilerFactory::create($configCache); Factory\DBFactory::init($basePath, $configCache, $profiler, $_SERVER); $config = Factory\ConfigFactory::createConfig($configCache); Factory\ConfigFactory::createPConfig($configCache); $logger = Factory\LoggerFactory::create('test', $config, $profiler); - $this->app = new App($basePath, $config, $mode, $logger, $profiler, false); + $this->app = new App($config, $mode, $logger, $profiler, false); parent::setUp(); } diff --git a/tests/src/Core/Config/Cache/ConfigCacheLoaderTest.php b/tests/src/Util/Config/ConfigFileLoaderTest.php similarity index 82% rename from tests/src/Core/Config/Cache/ConfigCacheLoaderTest.php rename to tests/src/Util/Config/ConfigFileLoaderTest.php index 39dc20efdb..ad0fe8afca 100644 --- a/tests/src/Core/Config/Cache/ConfigCacheLoaderTest.php +++ b/tests/src/Util/Config/ConfigFileLoaderTest.php @@ -1,16 +1,16 @@ root->url(), $this->mode); + $configFileLoader = new ConfigFileLoader($this->root->url(), $this->mode); $configCache = new ConfigCache(); - $configCacheLoader->loadConfigFiles($configCache); + $configFileLoader->setupCache($configCache); $this->assertEquals($this->root->url(), $configCache->get('system', 'basepath')); } @@ -55,10 +55,10 @@ class ConfigCacheLoaderTest extends MockedTest ->at($this->root->getChild('config')) ->setContent('root->url(), $this->mode); + $configFileLoader = new ConfigFileLoader($this->root->url(), $this->mode); $configCache = new ConfigCache(); - $configCacheLoader->loadConfigFiles($configCache); + $configFileLoader->setupCache($configCache); } /** @@ -69,7 +69,6 @@ class ConfigCacheLoaderTest extends MockedTest $this->delConfigFile('local.config.php'); $file = dirname(__DIR__) . DIRECTORY_SEPARATOR . - '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'datasets' . DIRECTORY_SEPARATOR . @@ -80,10 +79,10 @@ class ConfigCacheLoaderTest extends MockedTest ->at($this->root->getChild('config')) ->setContent(file_get_contents($file)); - $configCacheLoader = new ConfigCacheLoader($this->root->url(), $this->mode); + $configFileLoader = new ConfigFileLoader($this->root->url(), $this->mode); $configCache = new ConfigCache(); - $configCacheLoader->loadConfigFiles($configCache); + $configFileLoader->setupCache($configCache); $this->assertEquals('testhost', $configCache->get('database', 'hostname')); $this->assertEquals('testuser', $configCache->get('database', 'username')); @@ -102,7 +101,6 @@ class ConfigCacheLoaderTest extends MockedTest $this->delConfigFile('local.config.php'); $file = dirname(__DIR__) . DIRECTORY_SEPARATOR . - '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'datasets' . DIRECTORY_SEPARATOR . @@ -113,10 +111,10 @@ class ConfigCacheLoaderTest extends MockedTest ->at($this->root->getChild('config')) ->setContent(file_get_contents($file)); - $configCacheLoader = new ConfigCacheLoader($this->root->url(), $this->mode); + $configFileLoader = new ConfigFileLoader($this->root->url(), $this->mode); $configCache = new ConfigCache(); - $configCacheLoader->loadConfigFiles($configCache); + $configFileLoader->setupCache($configCache); $this->assertEquals('testhost', $configCache->get('database', 'hostname')); $this->assertEquals('testuser', $configCache->get('database', 'username')); @@ -134,21 +132,20 @@ class ConfigCacheLoaderTest extends MockedTest $this->delConfigFile('local.config.php'); $file = dirname(__DIR__) . DIRECTORY_SEPARATOR . - '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'datasets' . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . - '.htconfig.test.php'; + '.htconfig.php'; vfsStream::newFile('.htconfig.php') ->at($this->root) ->setContent(file_get_contents($file)); - $configCacheLoader = new ConfigCacheLoader($this->root->url(), $this->mode); + $configFileLoader = new ConfigFileLoader($this->root->url(), $this->mode); $configCache = new ConfigCache(); - $configCacheLoader->loadConfigFiles($configCache); + $configFileLoader->setupCache($configCache); $this->assertEquals('testhost', $configCache->get('database', 'hostname')); $this->assertEquals('testuser', $configCache->get('database', 'username')); @@ -160,7 +157,7 @@ class ConfigCacheLoaderTest extends MockedTest $this->assertEquals('Europe/Berlin', $configCache->get('system', 'default_timezone')); $this->assertEquals('fr', $configCache->get('system', 'language')); - $this->assertEquals('admin@friendica.local', $configCache->get('config', 'admin_email')); + $this->assertEquals('admin@test.it', $configCache->get('config', 'admin_email')); $this->assertEquals('Friendly admin', $configCache->get('config', 'admin_nickname')); $this->assertEquals('/another/php', $configCache->get('config', 'php_path')); @@ -184,7 +181,6 @@ class ConfigCacheLoaderTest extends MockedTest vfsStream::create($structure, $this->root); $file = dirname(__DIR__) . DIRECTORY_SEPARATOR . - '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'datasets' . DIRECTORY_SEPARATOR . @@ -195,9 +191,9 @@ class ConfigCacheLoaderTest extends MockedTest ->at($this->root->getChild('addon')->getChild('test')->getChild('config')) ->setContent(file_get_contents($file)); - $configCacheLoader = new ConfigCacheLoader($this->root->url(), $this->mode); + $configFileLoader = new ConfigFileLoader($this->root->url(), $this->mode); - $conf = $configCacheLoader->loadAddonConfig('test'); + $conf = $configFileLoader->loadAddonConfig('test'); $this->assertEquals('testhost', $conf['database']['hostname']); $this->assertEquals('testuser', $conf['database']['username']); diff --git a/tests/src/Util/Config/ConfigFileSaverTest.php b/tests/src/Util/Config/ConfigFileSaverTest.php new file mode 100644 index 0000000000..04adf6014a --- /dev/null +++ b/tests/src/Util/Config/ConfigFileSaverTest.php @@ -0,0 +1,189 @@ +setUpVfsDir(); + $this->mode = \Mockery::mock(App\Mode::class); + $this->mode->shouldReceive('isInstall')->andReturn(true); + } + + public function dataConfigFiles() + { + return [ + 'config' => [ + 'fileName' => 'local.config.php', + 'filePath' => dirname(__DIR__) . DIRECTORY_SEPARATOR . + '..' . DIRECTORY_SEPARATOR . + '..' . DIRECTORY_SEPARATOR . + 'datasets' . DIRECTORY_SEPARATOR . + 'config', + 'relativePath' => 'config', + ], + 'ini' => [ + 'fileName' => 'local.ini.php', + 'filePath' => dirname(__DIR__) . DIRECTORY_SEPARATOR . + '..' . DIRECTORY_SEPARATOR . + '..' . DIRECTORY_SEPARATOR . + 'datasets' . DIRECTORY_SEPARATOR . + 'config', + 'relativePath' => 'config', + ], + 'htconfig' => [ + 'fileName' => '.htconfig.php', + 'filePath' => dirname(__DIR__) . DIRECTORY_SEPARATOR . + '..' . DIRECTORY_SEPARATOR . + '..' . DIRECTORY_SEPARATOR . + 'datasets' . DIRECTORY_SEPARATOR . + 'config', + 'relativePath' => '', + ], + ]; + } + + /** + * Test the saveToConfigFile() method + * @dataProvider dataConfigFiles + * + * @todo 20190324 [nupplaphil] for ini-configs, it isn't possible to use $ or ! inside values + */ + public function testSaveToConfig($fileName, $filePath, $relativePath) + { + $this->delConfigFile('local.config.php'); + + if (empty($relativePath)) { + $root = $this->root; + $relativeFullName = $fileName; + } else { + $root = $this->root->getChild($relativePath); + $relativeFullName = $relativePath . DIRECTORY_SEPARATOR . $fileName; + } + + vfsStream::newFile($fileName) + ->at($root) + ->setContent(file_get_contents($filePath . DIRECTORY_SEPARATOR . $fileName)); + + $configFileSaver = new ConfigFileSaver($this->root->url()); + $configFileLoader = new ConfigFileLoader($this->root->url(), $this->mode); + $configCache = new ConfigCache(); + $configFileLoader->setupCache($configCache); + + $this->assertEquals('admin@test.it', $configCache->get('config', 'admin_email')); + $this->assertEquals('frio', $configCache->get('system', 'theme')); + $this->assertNull($configCache->get('config', 'test_val')); + $this->assertNull($configCache->get('system', 'test_val2')); + + // update values (system and config value) + $configFileSaver->addConfigValue('config', 'admin_email', 'new@mail.it'); + $configFileSaver->addConfigValue('system', 'theme', 'vier'); + + // insert values (system and config value) + $configFileSaver->addConfigValue('config', 'test_val', 'Testingwith@all.we can'); + $configFileSaver->addConfigValue('system', 'test_val2', 'TestIt First'); + + // overwrite value + $configFileSaver->addConfigValue('system', 'test_val2', 'TestIt Now'); + + // save it + $this->assertTrue($configFileSaver->saveToConfigFile()); + + $newConfigCache = new ConfigCache(); + $configFileLoader->setupCache($newConfigCache); + + $this->assertEquals('new@mail.it', $newConfigCache->get('config', 'admin_email')); + $this->assertEquals('Testingwith@all.we can', $newConfigCache->get('config', 'test_val')); + $this->assertEquals('vier', $newConfigCache->get('system', 'theme')); + $this->assertEquals('TestIt Now', $newConfigCache->get('system', 'test_val2')); + + $this->assertTrue($this->root->hasChild($relativeFullName)); + $this->assertTrue($this->root->hasChild($relativeFullName . '.old')); + $this->assertFalse($this->root->hasChild($relativeFullName . '.tmp')); + + $this->assertEquals(file_get_contents($filePath . DIRECTORY_SEPARATOR . $fileName), file_get_contents($this->root->getChild($relativeFullName . '.old')->url())); + } + + /** + * Test the saveToConfigFile() method without permissions + * @dataProvider dataConfigFiles + */ + public function testNoPermission($fileName, $filePath, $relativePath) + { + $this->delConfigFile('local.config.php'); + + if (empty($relativePath)) { + $root = $this->root; + $relativeFullName = $fileName; + } else { + $root = $this->root->getChild($relativePath); + $relativeFullName = $relativePath . DIRECTORY_SEPARATOR . $fileName; + } + + $root->chmod(000); + + vfsStream::newFile($fileName) + ->at($root) + ->setContent(file_get_contents($filePath . DIRECTORY_SEPARATOR . $fileName)); + + $configFileSaver = new ConfigFileSaver($this->root->url()); + + $configFileSaver->addConfigValue('system', 'test_val2', 'TestIt Now'); + + // wrong mod, so return false if nothing to write + $this->assertFalse($configFileSaver->saveToConfigFile()); + } + + /** + * Test the saveToConfigFile() method with nothing to do + * @dataProvider dataConfigFiles + */ + public function testNothingToDo($fileName, $filePath, $relativePath) + { + $this->delConfigFile('local.config.php'); + + if (empty($relativePath)) { + $root = $this->root; + $relativeFullName = $fileName; + } else { + $root = $this->root->getChild($relativePath); + $relativeFullName = $relativePath . DIRECTORY_SEPARATOR . $fileName; + } + + vfsStream::newFile($fileName) + ->at($root) + ->setContent(file_get_contents($filePath . DIRECTORY_SEPARATOR . $fileName)); + + $configFileSaver = new ConfigFileSaver($this->root->url()); + $configFileLoader = new ConfigFileLoader($this->root->url(), $this->mode); + $configCache = new ConfigCache(); + $configFileLoader->setupCache($configCache); + + // save nothing + $this->assertTrue($configFileSaver->saveToConfigFile()); + + $this->assertTrue($this->root->hasChild($relativeFullName)); + $this->assertFalse($this->root->hasChild($relativeFullName . '.old')); + $this->assertFalse($this->root->hasChild($relativeFullName . '.tmp')); + + $this->assertEquals(file_get_contents($filePath . DIRECTORY_SEPARATOR . $fileName), file_get_contents($this->root->getChild($relativeFullName)->url())); + } +} diff --git a/update.php b/update.php index e619ec89dd..0ef74ea005 100644 --- a/update.php +++ b/update.php @@ -1,5 +1,6 @@ getBasePath(), $app->getMode())) { + return Update::SUCCESS; + } else { + return Update::FAILED; + } +} diff --git a/view/global.css b/view/global.css index afab5d9032..b48fa1a24a 100644 --- a/view/global.css +++ b/view/global.css @@ -1,6 +1,10 @@ /* General style rules .*/ .pull-right { float: right } +details > summary { + cursor: pointer; +} + /* General designing elements */ .btn { outline: none; diff --git a/view/js/acl.js b/view/js/acl.js index fe0e9f1bbe..d01ffe7cbd 100644 --- a/view/js/acl.js +++ b/view/js/acl.js @@ -225,30 +225,41 @@ ACL.prototype.is_show_all = function() { this.deny_gid.length==0 && this.deny_cid.length==0); }; -ACL.prototype.update_view = function(){ - if (this.is_show_all()){ - this.showall.addClass("selected"); - /* jot acl */ - $('#jot-perms-icon').removeClass('lock').addClass('unlock'); - $('#jot-public').show(); - $('.profile-jot-net input').attr('disabled', false); - if(typeof editor != 'undefined' && editor != false) { - $('#profile-jot-desc').html(ispublic); - } +ACL.prototype.update_view = function () { + if (this.is_show_all()) { + this.showall.addClass("selected"); + /* jot acl */ + $('#jot-perms-icon').removeClass('lock').addClass('unlock'); + $('#jot-public').show(); + $('.profile-jot-net input[type=checkbox]').each(function() { + // Restores checkbox state if it had been saved + if ($(this).attr('data-checked') !== undefined) { + $(this).prop('checked', $(this).attr('data-checked') === 'true'); + } + }); + $('.profile-jot-net input').attr('disabled', false); + if (typeof editor != 'undefined' && editor != false) { + $('#profile-jot-desc').html(ispublic); + } } else { - this.showall.removeClass("selected"); - /* jot acl */ - $('#jot-perms-icon').removeClass('unlock').addClass('lock'); - $('#jot-public').hide(); - $('.profile-jot-net input').attr('disabled', 'disabled'); - $('#profile-jot-desc').html(' '); + this.showall.removeClass("selected"); + /* jot acl */ + $('#jot-perms-icon').removeClass('unlock').addClass('lock'); + $('#jot-public').hide(); + $('.profile-jot-net input[type=checkbox]').each(function() { + // Saves current checkbox state + $(this) + .attr('data-checked', $(this).prop('checked')) + .prop('checked', false); + }); + $('.profile-jot-net input').attr('disabled', 'disabled'); + $('#profile-jot-desc').html(' '); } - $("#acl-list-content .acl-list-item").each(function(){ - $(this).removeClass("groupshow grouphide"); - }); - $("#acl-list-content .acl-list-item").each(function(index, element){ + $("#acl-list-content .acl-list-item").each(function (index, element) { + $(this).removeClass("groupshow grouphide"); + itemid = $(element).attr('id'); type = itemid[0]; id = parseInt(itemid.substr(1)); @@ -256,40 +267,40 @@ ACL.prototype.update_view = function(){ btshow = $(element).children(".acl-button-show").removeClass("selected"); bthide = $(element).children(".acl-button-hide").removeClass("selected"); - switch(type){ + switch (type) { case "g": var uclass = ""; - if (this.allow_gid.indexOf(id)>=0){ + if (this.allow_gid.indexOf(id) >= 0) { btshow.addClass("selected"); bthide.removeClass("selected"); - uclass="groupshow"; + uclass = "groupshow"; } - if (this.deny_gid.indexOf(id)>=0){ + if (this.deny_gid.indexOf(id) >= 0) { btshow.removeClass("selected"); bthide.addClass("selected"); - uclass="grouphide"; + uclass = "grouphide"; } - $(this.group_uids[id]).each(function(i,v) { - if(uclass == "grouphide") - $("#c"+v).removeClass("groupshow"); - if(uclass != "") { - var cls = $("#c"+v).attr('class'); - if( cls == undefined) + $(this.group_uids[id]).each(function (i, v) { + if (uclass == "grouphide") + $("#c" + v).removeClass("groupshow"); + if (uclass != "") { + var cls = $("#c" + v).attr('class'); + if (cls == undefined) return true; var hiding = cls.indexOf('grouphide'); - if(hiding == -1) - $("#c"+v).addClass(uclass); + if (hiding == -1) + $("#c" + v).addClass(uclass); } }); break; case "c": - if (this.allow_cid.indexOf(id)>=0){ + if (this.allow_cid.indexOf(id) >= 0) { btshow.addClass("selected"); bthide.removeClass("selected"); } - if (this.deny_cid.indexOf(id)>=0){ + if (this.deny_cid.indexOf(id) >= 0) { btshow.removeClass("selected"); bthide.addClass("selected"); } @@ -297,7 +308,7 @@ ACL.prototype.update_view = function(){ }.bind(this)); -} +}; ACL.prototype.get = function(start,count, search){ var postdata = { diff --git a/view/templates/acl_selector.tpl b/view/templates/acl_selector.tpl index 48706535f4..6755f2a198 100644 --- a/view/templates/acl_selector.tpl +++ b/view/templates/acl_selector.tpl @@ -19,9 +19,30 @@
{{$emailcc}}
-{{if $jotnets}} -{{$jotnets nofilter}} -{{/if}}{{/if}} + + {{if $jotnets_fields}} + {{if $jotnets_fields|count < 3}} +
+ {{else}} +
+ {{$jotnets_summary}} + {{/if}} + + {{foreach $jotnets_fields as $jotnets_field}} + {{if $jotnets_field.type == 'checkbox'}} + {{include file="field_checkbox.tpl" field=$jotnets_field.field}} + {{elseif $jotnets_field.type == 'select'}} + {{include file="field_select.tpl" field=$jotnets_field.field}} + {{/if}} + {{/foreach}} + + {{if $jotnets_fields|count >= 3}} +
+ {{else}} +
+ {{/if}} + {{/if}} +{{/if}}