*/
use Friendica\App;
+use Friendica\Core\Config;
+use Friendica\Factory;
+use Friendica\Util\BasePath;
use Friendica\Util\ExAuth;
-use Friendica\Util\LoggerFactory;
if (sizeof($_SERVER["argv"]) == 0) {
die();
require dirname(__DIR__) . '/vendor/autoload.php';
-$logger = LoggerFactory::create('auth_ejabberd');
+$basedir = BasePath::create(dirname(__DIR__), $_SERVER);
+$configLoader = new Config\ConfigCacheLoader($basedir);
+$config = Factory\ConfigFactory::createCache($configLoader);
+$logger = Factory\LoggerFactory::create('auth_ejabberd', $config);
-$a = new App(dirname(__DIR__), $logger);
+$a = new App($config, $logger);
if ($a->getMode()->isNormal()) {
$oAuth = new ExAuth();
require dirname(__DIR__) . '/vendor/autoload.php';
-use Friendica\Util\LoggerFactory;
+use Friendica\Core\Config;
+use Friendica\Factory;
+use Friendica\Util\BasePath;
-$logger = LoggerFactory::create('console');
+$basedir = BasePath::create(dirname(__DIR__), $_SERVER);
+$configLoader = new Config\ConfigCacheLoader($basedir);
+$config = Factory\ConfigFactory::createCache($configLoader);
+$logger = Factory\LoggerFactory::create('console', $config);
-$a = new Friendica\App(dirname(__DIR__), $logger);
+$a = new Friendica\App($config, $logger);
\Friendica\BaseObject::setApp($a);
(new Friendica\Core\Console($argv))->execute();
use Friendica\Core\Config;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
-use Friendica\Util\LoggerFactory;
+use Friendica\Factory;
+use Friendica\Util\BasePath;
// Get options
$shortopts = 'f';
require dirname(__DIR__) . '/vendor/autoload.php';
-$logger = LoggerFactory::create('daemon');
+$basedir = BasePath::create(dirname(__DIR__), $_SERVER);
+$configLoader = new Config\ConfigCacheLoader($basedir);
+$config = Factory\ConfigFactory::createCache($configLoader);
+$logger = Factory\LoggerFactory::create('daemon', $config);
-$a = new App(dirname(__DIR__), $logger);
+$a = new App($config, $logger);
if ($a->getMode()->isInstall()) {
die("Friendica isn't properly installed yet.\n");
* @file bin/worker.php
* @brief Starts the background processing
*/
+
use Friendica\App;
use Friendica\Core\Config;
-use Friendica\Core\Worker;
use Friendica\Core\Update;
-use Friendica\Util\LoggerFactory;
+use Friendica\Core\Worker;
+use Friendica\Factory;
+use Friendica\Util\BasePath;
// Get options
$shortopts = 'sn';
require dirname(__DIR__) . '/vendor/autoload.php';
-$logger = LoggerFactory::create('worker');
+$basedir = BasePath::create(dirname(__DIR__), $_SERVER);
+$configLoader = new Config\ConfigCacheLoader($basedir);
+$config = Factory\ConfigFactory::createCache($configLoader);
+$logger = Factory\LoggerFactory::create('worker', $config);
-$a = new App(dirname(__DIR__), $logger);
+$a = new App($config, $logger);
// Check the database structure and possibly fixes it
-Update::check(true);
+Update::check($a->getBasePath(), true);
// Quit when in maintenance
if (!$a->getMode()->has(App\Mode::MAINTENANCEDISABLED)) {
use Friendica\App;
use Friendica\BaseObject;
-use Friendica\Core\Addon;
-use Friendica\Core\Cache;
use Friendica\Core\Config;
-use Friendica\Core\L10n;
use Friendica\Core\PConfig;
use Friendica\Core\Protocol;
use Friendica\Core\System;
-use Friendica\Core\Update;
-use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
-use Friendica\Model\Conversation;
+use Friendica\Util\BasePath;
use Friendica\Util\DateTimeFormat;
define('FRIENDICA_PLATFORM', 'Friendica');
$temppath = Config::get("system", "temppath");
- if (($temppath != "") && App::isDirectoryUsable($temppath)) {
+ if (($temppath != "") && System::isDirectoryUsable($temppath)) {
// We have a temp path and it is usable
- return App::getRealPath($temppath);
+ return BasePath::getRealPath($temppath);
}
// We don't have a working preconfigured temp path, so we take the system path.
$temppath = sys_get_temp_dir();
// Check if it is usable
- if (($temppath != "") && App::isDirectoryUsable($temppath)) {
+ if (($temppath != "") && System::isDirectoryUsable($temppath)) {
// Always store the real path, not the path through symlinks
- $temppath = App::getRealPath($temppath);
+ $temppath = BasePath::getRealPath($temppath);
// To avoid any interferences with other systems we create our own directory
$new_temppath = $temppath . "/" . $a->getHostName();
mkdir($new_temppath);
}
- if (App::isDirectoryUsable($new_temppath)) {
+ if (System::isDirectoryUsable($new_temppath)) {
// The new path is usable, we are happy
Config::set("system", "temppath", $new_temppath);
return $new_temppath;
}
$itemcache = Config::get('system', 'itemcache');
- if (($itemcache != "") && App::isDirectoryUsable($itemcache)) {
- return App::getRealPath($itemcache);
+ if (($itemcache != "") && System::isDirectoryUsable($itemcache)) {
+ return BasePath::getRealPath($itemcache);
}
$temppath = get_temppath();
mkdir($itemcache);
}
- if (App::isDirectoryUsable($itemcache)) {
+ if (System::isDirectoryUsable($itemcache)) {
Config::set("system", "itemcache", $itemcache);
return $itemcache;
}
function get_spoolpath()
{
$spoolpath = Config::get('system', 'spoolpath');
- if (($spoolpath != "") && App::isDirectoryUsable($spoolpath)) {
+ if (($spoolpath != "") && System::isDirectoryUsable($spoolpath)) {
// We have a spool path and it is usable
return $spoolpath;
}
mkdir($spoolpath);
}
- if (App::isDirectoryUsable($spoolpath)) {
+ if (System::isDirectoryUsable($spoolpath)) {
// The new path is usable, we are happy
Config::set("system", "spoolpath", $spoolpath);
return $spoolpath;
*/
use Friendica\App;
-use Friendica\Util\LoggerFactory;
+use Friendica\Core\Config;
+use Friendica\Factory;
+use Friendica\Util\BasePath;
if (!file_exists(__DIR__ . '/vendor/autoload.php')) {
die('Vendor path not found. Please execute "bin/composer.phar --no-dev install" on the command line in the web root.');
require __DIR__ . '/vendor/autoload.php';
-$logger = LoggerFactory::create('index');
+$basedir = BasePath::create(__DIR__, $_SERVER);
+$configLoader = new Config\ConfigCacheLoader($basedir);
+$config = Factory\ConfigFactory::createCache($configLoader);
+$logger = Factory\LoggerFactory::create('index', $config);
// We assume that the index.php is called by a frontend process
// The value is set to "true" by default in App
-$a = new App(__DIR__, $logger, false);
+$a = new App($config, $logger, false);
$a->runFrontend();
use Friendica\Core\L10n;
use Friendica\Core\Logger;
use Friendica\Core\Renderer;
+use Friendica\Core\StorageManager;
use Friendica\Core\System;
use Friendica\Core\Theme;
use Friendica\Core\Update;
use Friendica\Core\Worker;
-use Friendica\Core\StorageManager;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\Model\Contact;
use Friendica\Module\Login;
use Friendica\Module\Tos;
use Friendica\Util\Arrays;
+use Friendica\Util\BasePath;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Network;
use Friendica\Util\Strings;
}
if (Config::get('system', 'dbupdate', DBStructure::UPDATE_NOT_CHECKED) == DBStructure::UPDATE_NOT_CHECKED) {
- DBStructure::update(false, true);
+ DBStructure::update($a->getBasePath(), false, true);
}
if (Config::get('system', 'dbupdate') == DBStructure::UPDATE_FAILED) {
$showwarning = true;
Config::set('system', 'dbclean-expire-unclaimed', $dbclean_unclaimed);
if ($itemcache != '') {
- $itemcache = App::getRealPath($itemcache);
+ $itemcache = BasePath::getRealPath($itemcache);
}
Config::set('system', 'itemcache', $itemcache);
Config::set('system', 'max_comments', $max_comments);
if ($temppath != '') {
- $temppath = App::getRealPath($temppath);
+ $temppath = BasePath::getRealPath($temppath);
}
Config::set('system', 'temppath', $temppath);
if ($basepath != '') {
- $basepath = App::getRealPath($basepath);
+ $basepath = BasePath::getRealPath($basepath);
}
Config::set('system', 'basepath' , $basepath);
'$hide_help' => ['hide_help', L10n::t("Hide help entry from navigation menu"), Config::get('system', 'hide_help'), L10n::t("Hides the menu entry for the Help pages from the navigation menu. You can still access it calling /help directly.")],
'$singleuser' => ['singleuser', L10n::t("Single user instance"), Config::get('system', 'singleuser', '---'), L10n::t("Make this instance multi-user or single-user for the named user"), $user_names],
- '$storagebackend' => ['storagebackend', L10n::t("File storage backend"), $storage_current_backend, L10n::t('The backend used to store uploaded files data. If you change the storage backend, you have to manually move the existing files see <a href="/help/Settings#1_2_3_1">the settings documentation</a> for more information about the choices and the moving procedure.'), $storage_backends_choices],
+ '$storagebackend' => ['storagebackend', L10n::t("File storage backend"), $storage_current_backend, L10n::t('The backend used to store uploaded data. If you change the storage backend, you can manually move the existing files. If you do not do so, the files uploaded before the change will still be available at the old backend. Please see <a href="/help/Settings#1_2_3_1">the settings documentation</a> for more information about the choices and the moving procedure.'), $storage_backends_choices],
'$storageform' => $storage_form,
'$maximagesize' => ['maximagesize', L10n::t("Maximum image size"), Config::get('system', 'maximagesize'), L10n::t("Maximum size in bytes of uploaded images. Default is 0, which means no limits.")],
'$maximagelength' => ['maximagelength', L10n::t("Maximum image length"), Config::get('system', 'max_image_length'), L10n::t("Maximum length in pixels of the longest side of uploaded images. Default is -1, which means no limits.")],
}
if (($a->argc > 2) && (intval($a->argv[2]) || ($a->argv[2] === 'check'))) {
- $retval = DBStructure::update(false, true);
+ $retval = DBStructure::update($a->getBasePath(), false, true);
if ($retval === '') {
$o .= L10n::t("Database structure update %s was successfully applied.", DB_UPDATE_VERSION) . "<br />";
Config::set('database', 'last_successful_update', DB_UPDATE_VERSION);
}
$sql_extra = '';
- if (!empty($a->config['admin_nickname'])) {
+ if (Config::get('config', 'admin_nickname') !== null) {
$sql_extra = sprintf(" AND `nickname` = '%s' ", DBA::escape(Config::get('config', 'admin_nickname')));
}
if (!empty(Config::get('config', 'admin_email'))) {
Config::load('feature_lock');
$locked_features = [];
- if (!empty($a->config['feature_lock'])) {
- foreach ($a->config['feature_lock'] as $k => $v) {
+ $featureLock = Config::get('config', 'feature_lock');
+ if (isset($featureLock)) {
+ foreach ($featureLock as $k => $v) {
if ($k === 'config_loaded') {
continue;
}
use Friendica\Util\Security;
use Friendica\Util\Strings;
+require_once 'include/items.php';
+
function item_post(App $a) {
if (!local_user() && !remote_user()) {
return 0;
$categories = '';
$postopts = '';
$emailcc = '';
+ $body = defaults($_REQUEST, 'body', '');
+ $has_attachment = defaults($_REQUEST, 'has_attachment', 0);
+
+ // If we have a speparate attachment, we need to add it to the body.
+ if (!empty($has_attachment)) {
+ $attachment_type = defaults($_REQUEST, 'attachment_type', '');
+ $attachment_title = defaults($_REQUEST, 'attachment_title', '');
+ $attachment_text = defaults($_REQUEST, 'attachment_text', '');
+
+ $attachment_url = hex2bin(defaults($_REQUEST, 'attachment_url', ''));
+ $attachment_img_src = hex2bin(defaults($_REQUEST, 'attachment_img_src', ''));
+
+ $attachment_img_width = defaults($_REQUEST, 'attachment_img_width', 0);
+ $attachment_img_height = defaults($_REQUEST, 'attachment_img_height', 0);
+ $attachment = [
+ 'type' => $attachment_type,
+ 'title' => $attachment_title,
+ 'text' => $attachment_text,
+ 'url' => $attachment_url,
+ ];
+
+ if (!empty($attachment_img_src)) {
+ $attachment['images'] = [
+ 0 => [
+ 'src' => $attachment_img_src,
+ 'width' => $attachment_img_width,
+ 'height' => $attachment_img_height
+ ]
+ ];
+ }
+
+ $att_bbcode = add_page_info_data($attachment);
+ $body .= $att_bbcode;
+ }
if (!empty($orig_post)) {
$str_group_allow = $orig_post['allow_gid'];
$app = $orig_post['app'];
$categories = $orig_post['file'];
$title = Strings::escapeTags(trim($_REQUEST['title']));
- $body = Strings::escapeHtml(trim($_REQUEST['body']));
+ $body = Strings::escapeHtml(trim($body));
$private = $orig_post['private'];
$pubmail_enabled = $orig_post['pubmail'];
$network = $orig_post['network'];
$coord = Strings::escapeTags(trim(defaults($_REQUEST, 'coord' , '')));
$verb = Strings::escapeTags(trim(defaults($_REQUEST, 'verb' , '')));
$emailcc = Strings::escapeTags(trim(defaults($_REQUEST, 'emailcc' , '')));
- $body = Strings::escapeHtml(trim(defaults($_REQUEST, 'body' , '')));
+ $body = Strings::escapeHtml(trim($body));
$network = Strings::escapeTags(trim(defaults($_REQUEST, 'network' , Protocol::DFRN)));
$guid = System::createUUID();
use Friendica\App;
use Friendica\Core\Hook;
use Friendica\Core\Logger;
+use Friendica\Core\System;
use Friendica\Util\Network;
use Friendica\Util\ParseUrl;
{
$text = null;
$str_tags = '';
+ $format = '';
+ $ret= ['success' => false, 'contentType' => ''];
$br = "\n";
}
}
+ if (isset($_GET['format']) && $_GET['format'] == 'json') {
+ $format = 'json';
+ }
+
// Add url scheme if it is missing
$arrurl = parse_url($url);
if (empty($arrurl['scheme'])) {
}
}
$type = null;
+ $content_type = '';
+ $bbcode = '';
if (array_key_exists('Content-Type', $hdrs)) {
$type = $hdrs['Content-Type'];
}
if ($type) {
if (stripos($type, 'image/') !== false) {
- echo $br . '[img]' . $url . '[/img]' . $br;
- exit();
+ $content_type = 'image';
+ $bbcode = $br . '[img]' . $url . '[/img]' . $br;
}
if (stripos($type, 'video/') !== false) {
- echo $br . '[video]' . $url . '[/video]' . $br;
- exit();
+ $content_type = 'video';
+ $bbcode = $br . '[video]' . $url . '[/video]' . $br;
}
if (stripos($type, 'audio/') !== false) {
- echo $br . '[audio]' . $url . '[/audio]' . $br;
- exit();
+ $content_type = 'audio';
+ $bbcode = $br . '[audio]' . $url . '[/audio]' . $br;
+ }
+ }
+ if (!empty($content_type)) {
+ if ($format == 'json') {
+ $ret['contentType'] = $content_type;
+ $ret['data'] = ['url' => $url];
+ $ret['success'] = true;
+ System::jsonExit($ret);
}
+
+ echo $bbcode;
+ exit();
}
}
exit();
}
+ if ($format == 'json') {
+ $ret['data'] = $siteinfo;
+ $ret['contentType'] = 'attachment';
+ $ret['success'] = true;
+
+ System::jsonExit($ret);
+ }
+
// Format it as BBCode attachment
$info = add_page_info_data($siteinfo);
use DOMDocument;
use DOMXPath;
use Exception;
+use Friendica\Core\Config\ConfigCache;
+use Friendica\Core\Config\ConfigCacheLoader;
use Friendica\Database\DBA;
+use Friendica\Factory\ConfigFactory;
use Friendica\Network\HTTPException\InternalServerErrorException;
use Psr\Log\LoggerInterface;
public $module_loaded = false;
public $module_class = null;
public $query_string = '';
- public $config = [];
public $page = [];
public $profile;
public $profile_uid;
*/
private $logger;
+ /**
+ * @var ConfigCache The cached config
+ */
+ private $config;
+
+ /**
+ * Returns the current config cache of this node
+ *
+ * @return ConfigCache
+ */
+ public function getConfig()
+ {
+ return $this->config;
+ }
+
+ /**
+ * The basepath of this app
+ *
+ * @return string
+ */
+ public function getBasePath()
+ {
+ return $this->basePath;
+ }
+
/**
* Register a stylesheet file path to be included in the <head> tag of every page.
* Inclusion is done in App->initHead().
*/
public function registerStylesheet($path)
{
- $url = str_replace($this->getBasePath() . DIRECTORY_SEPARATOR, '', $path);
+ $url = str_replace($this->basePath . DIRECTORY_SEPARATOR, '', $path);
$this->stylesheets[] = trim($url, '/');
}
*/
public function registerFooterScript($path)
{
- $url = str_replace($this->getBasePath() . DIRECTORY_SEPARATOR, '', $path);
+ $url = str_replace($this->basePath . DIRECTORY_SEPARATOR, '', $path);
$this->footerScripts[] = trim($url, '/');
}
/**
* @brief App constructor.
*
- * @param string $basePath Path to the app base folder
+ * @param ConfigCache $config The Cached Config
* @param LoggerInterface $logger Logger of this application
* @param bool $isBackend Whether it is used for backend or frontend (Default true=backend)
*
* @throws Exception if the Basepath is not usable
*/
- public function __construct($basePath, LoggerInterface $logger, $isBackend = true)
+ public function __construct(ConfigCache $config, LoggerInterface $logger, $isBackend = true)
{
- $this->logger = $logger;
+ $this->config = $config;
+ $this->logger = $logger;
+ $this->basePath = $this->config->get('system', 'basepath');
- if (!static::isDirectoryUsable($basePath, false)) {
- throw new Exception('Basepath ' . $basePath . ' isn\'t usable.');
+ if (!Core\System::isDirectoryUsable($this->basePath, false)) {
+ throw new Exception('Basepath ' . $this->basePath . ' isn\'t usable.');
}
+ $this->basePath = rtrim($this->basePath, DIRECTORY_SEPARATOR);
BaseObject::setApp($this);
- $this->basePath = rtrim($basePath, DIRECTORY_SEPARATOR);
$this->checkBackend($isBackend);
$this->checkFriendicaApp();
$this->callstack['rendering'] = [];
$this->callstack['parser'] = [];
- $this->mode = new App\Mode($basePath);
+ $this->mode = new App\Mode($this->basePath);
$this->reload();
set_include_path(
get_include_path() . PATH_SEPARATOR
- . $this->getBasePath() . DIRECTORY_SEPARATOR . 'include' . PATH_SEPARATOR
- . $this->getBasePath() . DIRECTORY_SEPARATOR . 'library' . PATH_SEPARATOR
- . $this->getBasePath());
+ . $this->basePath . DIRECTORY_SEPARATOR . 'include' . PATH_SEPARATOR
+ . $this->basePath . DIRECTORY_SEPARATOR . 'library' . PATH_SEPARATOR
+ . $this->basePath);
if (!empty($_SERVER['QUERY_STRING']) && strpos($_SERVER['QUERY_STRING'], 'pagename=') === 0) {
$this->query_string = substr($_SERVER['QUERY_STRING'], 9);
*/
public function reload()
{
- // The order of the following calls is important to ensure proper initialization
- $this->loadConfigFiles();
+ Core\Config::init($this->config);
+ Core\PConfig::init($this->config);
$this->loadDatabase();
- $this->getMode()->determine($this->getBasePath());
+ $this->getMode()->determine($this->basePath);
$this->determineURLPath();
- Core\Config::load();
+ if ($this->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
+ $adapterType = $this->config->get('system', 'config_adapter');
+ $adapter = ConfigFactory::createConfig($adapterType, $this->config);
+ Core\Config::setAdapter($adapter);
+ $adapterP = ConfigFactory::createPConfig($adapterType, $this->config);
+ Core\PConfig::setAdapter($adapterP);
+ Core\Config::load();
+ }
+
+ // again because DB-config could change the config
+ $this->getMode()->determine($this->basePath);
if ($this->getMode()->has(App\Mode::DBAVAILABLE)) {
Core\Hook::loadHooks();
-
- $this->loadAddonConfig();
+ $loader = new ConfigCacheLoader($this->basePath);
+ Core\Hook::callAll('load_config', $loader);
+ $this->config->loadConfigArray($loader->loadCoreConfig('addon'), true);
}
$this->loadDefaultTimezone();
Core\Logger::setLogger($this->logger);
}
- /**
- * Load the configuration files
- *
- * First loads the default value for all the configuration keys, then the legacy configuration files, then the
- * expected local.config.php
- */
- private function loadConfigFiles()
- {
- $this->loadConfigFile($this->getBasePath() . '/config/defaults.config.php');
- $this->loadConfigFile($this->getBasePath() . '/config/settings.config.php');
-
- // Legacy .htconfig.php support
- if (file_exists($this->getBasePath() . '/.htpreconfig.php')) {
- $a = $this;
- include $this->getBasePath() . '/.htpreconfig.php';
- }
-
- // Legacy .htconfig.php support
- if (file_exists($this->getBasePath() . '/.htconfig.php')) {
- $a = $this;
-
- include $this->getBasePath() . '/.htconfig.php';
-
- $this->setConfigValue('database', 'hostname', $db_host);
- $this->setConfigValue('database', 'username', $db_user);
- $this->setConfigValue('database', 'password', $db_pass);
- $this->setConfigValue('database', 'database', $db_data);
- if (isset($a->config['system']['db_charset'])) {
- $this->setConfigValue('database', 'charset', $a->config['system']['db_charset']);
- }
-
- unset($db_host, $db_user, $db_pass, $db_data);
-
- if (isset($default_timezone)) {
- $this->setConfigValue('system', 'default_timezone', $default_timezone);
- unset($default_timezone);
- }
-
- if (isset($pidfile)) {
- $this->setConfigValue('system', 'pidfile', $pidfile);
- unset($pidfile);
- }
-
- if (isset($lang)) {
- $this->setConfigValue('system', 'language', $lang);
- unset($lang);
- }
- }
-
- if (file_exists($this->getBasePath() . '/config/local.config.php')) {
- $this->loadConfigFile($this->getBasePath() . '/config/local.config.php', true);
- } elseif (file_exists($this->getBasePath() . '/config/local.ini.php')) {
- $this->loadINIConfigFile($this->getBasePath() . '/config/local.ini.php', true);
- }
- }
-
- /**
- * Tries to load the specified legacy configuration file into the App->config array.
- * Doesn't overwrite previously set values by default to prevent default config files to supersede DB Config.
- *
- * @deprecated since version 2018.12
- * @param string $filepath
- * @param bool $overwrite Force value overwrite if the config key already exists
- * @throws Exception
- */
- public function loadINIConfigFile($filepath, $overwrite = false)
- {
- if (!file_exists($filepath)) {
- throw new Exception('Error parsing non-existent INI config file ' . $filepath);
- }
-
- $contents = include($filepath);
-
- $config = parse_ini_string($contents, true, INI_SCANNER_TYPED);
-
- if ($config === false) {
- throw new Exception('Error parsing INI config file ' . $filepath);
- }
-
- $this->loadConfigArray($config, $overwrite);
- }
-
- /**
- * Tries to load the specified configuration file into the App->config array.
- * Doesn't overwrite previously set values by default to prevent default config files to supersede DB Config.
- *
- * The config format is PHP array and the template for configuration files is the following:
- *
- * <?php return [
- * 'section' => [
- * 'key' => 'value',
- * ],
- * ];
- *
- * @param string $filepath
- * @param bool $overwrite Force value overwrite if the config key already exists
- * @throws Exception
- */
- public function loadConfigFile($filepath, $overwrite = false)
- {
- if (!file_exists($filepath)) {
- throw new Exception('Error loading non-existent config file ' . $filepath);
- }
-
- $config = include($filepath);
-
- if (!is_array($config)) {
- throw new Exception('Error loading config file ' . $filepath);
- }
-
- $this->loadConfigArray($config, $overwrite);
- }
-
- /**
- * Loads addons configuration files
- *
- * First loads all activated addons default configuration through the load_config hook, then load the local.config.php
- * again to overwrite potential local addon configuration.
- */
- private function loadAddonConfig()
- {
- // Loads addons default config
- Core\Hook::callAll('load_config');
-
- // Load the local addon config file to overwritten default addon config values
- if (file_exists($this->getBasePath() . '/config/addon.config.php')) {
- $this->loadConfigFile($this->getBasePath() . '/config/addon.config.php', true);
- } elseif (file_exists($this->getBasePath() . '/config/addon.ini.php')) {
- $this->loadINIConfigFile($this->getBasePath() . '/config/addon.ini.php', true);
- }
- }
-
- /**
- * Tries to load the specified configuration array into the App->config array.
- * Doesn't overwrite previously set values by default to prevent default config files to supersede DB Config.
- *
- * @param array $config
- * @param bool $overwrite Force value overwrite if the config key already exists
- */
- private function loadConfigArray(array $config, $overwrite = false)
- {
- foreach ($config as $category => $values) {
- foreach ($values as $key => $value) {
- if ($overwrite) {
- $this->setConfigValue($category, $key, $value);
- } else {
- $this->setDefaultConfigValue($category, $key, $value);
- }
- }
- }
- }
-
/**
* Loads the default timezone
*
*/
private function loadDefaultTimezone()
{
- if ($this->getConfigValue('system', 'default_timezone')) {
- $this->timezone = $this->getConfigValue('system', 'default_timezone');
+ if ($this->config->get('system', 'default_timezone')) {
+ $this->timezone = $this->config->get('system', 'default_timezone');
} else {
global $default_timezone;
$this->timezone = !empty($default_timezone) ? $default_timezone : 'UTC';
$relative_script_path = defaults($_SERVER, 'SCRIPT_URL' , $relative_script_path);
$relative_script_path = defaults($_SERVER, 'REQUEST_URI' , $relative_script_path);
- $this->urlPath = $this->getConfigValue('system', 'urlpath');
+ $this->urlPath = $this->config->get('system', 'urlpath');
/* $relative_script_path gives /relative/path/to/friendica/module/parameter
* QUERY_STRING gives pagename=module/parameter
return;
}
- $db_host = $this->getConfigValue('database', 'hostname');
- $db_user = $this->getConfigValue('database', 'username');
- $db_pass = $this->getConfigValue('database', 'password');
- $db_data = $this->getConfigValue('database', 'database');
- $charset = $this->getConfigValue('database', 'charset');
+ $db_host = $this->config->get('database', 'hostname');
+ $db_user = $this->config->get('database', 'username');
+ $db_pass = $this->config->get('database', 'password');
+ $db_data = $this->config->get('database', 'database');
+ $charset = $this->config->get('database', 'charset');
// Use environment variables for mysql if they are set beforehand
if (!empty(getenv('MYSQL_HOST'))
$stamp1 = microtime(true);
- if (DBA::connect($db_host, $db_user, $db_pass, $db_data, $charset)) {
+ if (DBA::connect($this->config, $db_host, $db_user, $db_pass, $db_data, $charset)) {
// Loads DB_UPDATE_VERSION constant
- Database\DBStructure::definition(false);
+ Database\DBStructure::definition($this->basePath, false);
}
unset($db_host, $db_user, $db_pass, $db_data, $charset);
$this->saveTimestamp($stamp1, 'network');
}
- /**
- * @brief Returns the base filesystem path of the App
- *
- * It first checks for the internal variable, then for DOCUMENT_ROOT and
- * finally for PWD
- *
- * @return string
- * @throws InternalServerErrorException
- */
- public function getBasePath()
- {
- $basepath = $this->basePath;
-
- if (!$basepath) {
- $basepath = Core\Config::get('system', 'basepath');
- }
-
- if (!$basepath && !empty($_SERVER['DOCUMENT_ROOT'])) {
- $basepath = $_SERVER['DOCUMENT_ROOT'];
- }
-
- if (!$basepath && !empty($_SERVER['PWD'])) {
- $basepath = $_SERVER['PWD'];
- }
-
- return self::getRealPath($basepath);
- }
-
- /**
- * @brief Returns a normalized file path
- *
- * This is a wrapper for the "realpath" function.
- * That function cannot detect the real path when some folders aren't readable.
- * Since this could happen with some hosters we need to handle this.
- *
- * @param string $path The path that is about to be normalized
- * @return string normalized path - when possible
- */
- public static function getRealPath($path)
- {
- $normalized = realpath($path);
-
- if (!is_bool($normalized)) {
- return $normalized;
- } else {
- return $path;
- }
- }
-
public function getScheme()
{
return $this->scheme;
$this->urlPath = trim($parsed['path'], '\\/');
}
- if (file_exists($this->getBasePath() . '/.htpreconfig.php')) {
- include $this->getBasePath() . '/.htpreconfig.php';
+ if (file_exists($this->basePath . '/.htpreconfig.php')) {
+ include $this->basePath . '/.htpreconfig.php';
}
if (Core\Config::get('config', 'hostname') != '') {
// compose the page title from the sitename and the
// current module called
if (!$this->module == '') {
- $this->page['title'] = $this->config['sitename'] . ' (' . $this->module . ')';
+ $this->page['title'] = $this->config->get('config', 'sitename') . ' (' . $this->module . ')';
} else {
- $this->page['title'] = $this->config['sitename'];
+ $this->page['title'] = $this->config->get('config', 'sitename');
}
if (!empty(Core\Renderer::$theme['stylesheet'])) {
*/
public function saveTimestamp($timestamp, $value)
{
- if (!isset($this->config['system']['profiler']) || !$this->config['system']['profiler']) {
+ $profiler = $this->config->get('system', 'profiler');
+
+ if (!isset($profiler) || !$profiler) {
return;
}
return;
}
- $cmdline = $this->getConfigValue('config', 'php_path', 'php') . ' ' . escapeshellarg($command);
+ $cmdline = $this->config->get('config', 'php_path', 'php') . ' ' . escapeshellarg($command);
foreach ($args as $key => $value) {
if (!is_null($value) && is_bool($value) && !$value) {
}
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
- $resource = proc_open('cmd /c start /b ' . $cmdline, [], $foo, $this->getBasePath());
+ $resource = proc_open('cmd /c start /b ' . $cmdline, [], $foo, $this->basePath);
} else {
- $resource = proc_open($cmdline . ' &', [], $foo, $this->getBasePath());
+ $resource = proc_open($cmdline . ' &', [], $foo, $this->basePath);
}
if (!is_resource($resource)) {
Core\Logger::log('We got no resource for command ' . $cmdline, Core\Logger::DEBUG);
proc_close($resource);
}
- /**
- * @brief Returns the system user that is executing the script
- *
- * This mostly returns something like "www-data".
- *
- * @return string system username
- */
- private static function getSystemUser()
- {
- if (!function_exists('posix_getpwuid') || !function_exists('posix_geteuid')) {
- return '';
- }
-
- $processUser = posix_getpwuid(posix_geteuid());
- return $processUser['name'];
- }
-
- /**
- * @brief Checks if a given directory is usable for the system
- *
- * @param $directory
- * @param bool $check_writable
- * @return boolean the directory is usable
- * @throws Exception
- */
- public static function isDirectoryUsable($directory, $check_writable = true)
- {
- if ($directory == '') {
- Core\Logger::log('Directory is empty. This shouldn\'t happen.', Core\Logger::DEBUG);
- return false;
- }
-
- if (!file_exists($directory)) {
- Core\Logger::log('Path "' . $directory . '" does not exist for user ' . self::getSystemUser(), Core\Logger::DEBUG);
- return false;
- }
-
- if (is_file($directory)) {
- Core\Logger::log('Path "' . $directory . '" is a file for user ' . self::getSystemUser(), Core\Logger::DEBUG);
- return false;
- }
-
- if (!is_dir($directory)) {
- Core\Logger::log('Path "' . $directory . '" is not a directory for user ' . self::getSystemUser(), Core\Logger::DEBUG);
- return false;
- }
-
- if ($check_writable && !is_writable($directory)) {
- Core\Logger::log('Path "' . $directory . '" is not writable for user ' . self::getSystemUser(), Core\Logger::DEBUG);
- return false;
- }
-
- return true;
- }
-
- /**
- * @param string $cat Config category
- * @param string $k Config key
- * @param mixed $default Default value if it isn't set
- *
- * @return string Returns the value of the Config entry
- */
- public function getConfigValue($cat, $k, $default = null)
- {
- $return = $default;
-
- if ($cat === 'config') {
- if (isset($this->config[$k])) {
- $return = $this->config[$k];
- }
- } else {
- if (isset($this->config[$cat][$k])) {
- $return = $this->config[$cat][$k];
- }
- }
-
- return $return;
- }
-
- /**
- * Sets a default value in the config cache. Ignores already existing keys.
- *
- * @param string $cat Config category
- * @param string $k Config key
- * @param mixed $v Default value to set
- */
- private function setDefaultConfigValue($cat, $k, $v)
- {
- if (!isset($this->config[$cat][$k])) {
- $this->setConfigValue($cat, $k, $v);
- }
- }
-
- /**
- * Sets a value in the config cache. Accepts raw output from the config table
- *
- * @param string $cat Config category
- * @param string $k Config key
- * @param mixed $v Value to set
- */
- public function setConfigValue($cat, $k, $v)
- {
- // Only arrays are serialized in database, so we have to unserialize sparingly
- $value = is_string($v) && preg_match("|^a:[0-9]+:{.*}$|s", $v) ? unserialize($v) : $v;
-
- if ($cat === 'config') {
- $this->config[$k] = $value;
- } else {
- if (!isset($this->config[$cat])) {
- $this->config[$cat] = [];
- }
-
- $this->config[$cat][$k] = $value;
- }
- }
-
- /**
- * Deletes a value from the config cache
- *
- * @param string $cat Config category
- * @param string $k Config key
- */
- public function deleteConfigValue($cat, $k)
- {
- if ($cat === 'config') {
- if (isset($this->config[$k])) {
- unset($this->config[$k]);
- }
- } else {
- if (isset($this->config[$cat][$k])) {
- unset($this->config[$cat][$k]);
- }
- }
- }
-
-
- /**
- * Retrieves a value from the user config cache
- *
- * @param int $uid User Id
- * @param string $cat Config category
- * @param string $k Config key
- * @param mixed $default Default value if key isn't set
- *
- * @return string The value of the config entry
- */
- public function getPConfigValue($uid, $cat, $k, $default = null)
- {
- $return = $default;
-
- if (isset($this->config[$uid][$cat][$k])) {
- $return = $this->config[$uid][$cat][$k];
- }
-
- return $return;
- }
-
- /**
- * Sets a value in the user config cache
- *
- * Accepts raw output from the pconfig table
- *
- * @param int $uid User Id
- * @param string $cat Config category
- * @param string $k Config key
- * @param mixed $v Value to set
- */
- public function setPConfigValue($uid, $cat, $k, $v)
- {
- // Only arrays are serialized in database, so we have to unserialize sparingly
- $value = is_string($v) && preg_match("|^a:[0-9]+:{.*}$|s", $v) ? unserialize($v) : $v;
-
- if (!isset($this->config[$uid]) || !is_array($this->config[$uid])) {
- $this->config[$uid] = [];
- }
-
- if (!isset($this->config[$uid][$cat]) || !is_array($this->config[$uid][$cat])) {
- $this->config[$uid][$cat] = [];
- }
-
- $this->config[$uid][$cat][$k] = $value;
- }
-
- /**
- * Deletes a value from the user config cache
- *
- * @param int $uid User Id
- * @param string $cat Config category
- * @param string $k Config key
- */
- public function deletePConfigValue($uid, $cat, $k)
- {
- if (isset($this->config[$uid][$cat][$k])) {
- unset($this->config[$uid][$cat][$k]);
- }
- }
-
/**
* Generates the site's default sender email address
*
$this->module = 'maintenance';
} else {
$this->checkURL();
- Core\Update::check(false);
+ Core\Update::check($this->basePath, false);
Core\Addon::loadAddons();
Core\Hook::loadHooks();
}
require_once 'boot.php';
-use Friendica\Util\LoggerFactory;
+use Friendica\Network\HTTPException\InternalServerErrorException;
/**
* Basic object
public static function getApp()
{
if (empty(self::$app)) {
- $logger = $logger = LoggerFactory::create('app');
- self::$app = new App(dirname(__DIR__), $logger);
+ throw new InternalServerErrorException('App isn\'t initialized.');
}
return self::$app;
*/
class Addon extends BaseObject
{
+ /**
+ * The addon sub-directory
+ * @var string
+ */
+ const DIRECTORY = 'addon';
+
/**
* List of the names of enabled addons
*
@include_once('addon/' . $addon . '/' . $addon . '.php');
if (function_exists($addon . '_install')) {
$func = $addon . '_install';
- $func();
+ $func(self::getApp());
$addon_admin = (function_exists($addon . "_addon_admin") ? 1 : 0);
if (function_exists($addon . '_uninstall')) {
$func = $addon . '_uninstall';
- $func();
+ $func(self::getApp());
}
if (function_exists($addon . '_install')) {
$func = $addon . '_install';
- $func();
+ $func(self::getApp());
}
DBA::update('addon', ['timestamp' => $t], ['id' => $i['id']]);
}
*/
namespace Friendica\Core;
-use Friendica\App;
-use Friendica\BaseObject;
+use Friendica\Core\Config\ConfigCache;
+use Friendica\Core\Config\IConfigAdapter;
+use Friendica\Core\Config\IConfigCache;
/**
* @brief Arbitrary system configuration storage
* If we ever would decide to return exactly the variable type as entered,
* we will have fun with the additional features. :-)
*/
-class Config extends BaseObject
+class Config
{
/**
- * @var \Friendica\Core\Config\IConfigAdapter
+ * @var Config\IConfigAdapter|null
*/
- private static $adapter = null;
+ private static $adapter;
- public static function init()
+ /**
+ * @var Config\IConfigCache
+ */
+ private static $cache;
+
+ /**
+ * Initialize the config with only the cache
+ *
+ * @param Config\IConfigCache $cache The configuration cache
+ */
+ public static function init(Config\IConfigCache $cache)
{
- // Database isn't ready or populated yet
- if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
- return;
- }
+ self::$cache = $cache;
+ }
- if (self::getApp()->getConfigValue('system', 'config_adapter') == 'preload') {
- self::$adapter = new Config\PreloadConfigAdapter();
- } else {
- self::$adapter = new Config\JITConfigAdapter();
- }
+ /**
+ * Add the adapter for DB-backend
+ *
+ * @param Config\IConfigAdapter $adapter
+ */
+ public static function setAdapter(Config\IConfigAdapter $adapter)
+ {
+ self::$adapter = $adapter;
}
/**
* @brief Loads all configuration values of family into a cached storage.
*
- * All configuration values of the system are stored in global cache
- * which is available under the global variable $a->config
+ * All configuration values of the system are stored in the cache ( @see IConfigCache )
*
* @param string $family The category of the configuration value
*
* @return void
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function load($family = "config")
{
- // Database isn't ready or populated yet
- if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
+ if (!isset(self::$adapter) || !self::$adapter->isConnected()) {
return;
}
- if (empty(self::$adapter)) {
- self::init();
- }
-
self::$adapter->load($family);
}
* ($family) and a key.
*
* Get a particular config value from the given category ($family)
- * and the $key from a cached storage in $a->config[$uid].
- * $instore is only used by the set_config function
- * to determine if the key already exists in the DB
- * If a key is found in the DB but doesn't exist in
- * local config cache, pull it into the cache so we don't have
- * to hit the DB again for this item.
+ * and the $key from a cached storage either from the self::$adapter
+ * (@see IConfigAdapter ) or from the static::$cache (@see IConfigCache ).
*
* @param string $family The category of the configuration value
* @param string $key The configuration key to query
* @param boolean $refresh optional, If true the config is loaded from the db and not from the cache (default: false)
*
* @return mixed Stored value or null if it does not exist
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function get($family, $key, $default_value = null, $refresh = false)
{
- // Database isn't ready or populated yet, fallback to file config
- if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
- return self::getApp()->getConfigValue($family, $key, $default_value);
- }
-
- if (empty(self::$adapter)) {
- self::init();
+ if (!isset(self::$adapter) || !self::$adapter->isConnected()) {
+ return self::$cache->get($family, $key, $default_value);
}
return self::$adapter->get($family, $key, $default_value, $refresh);
* @brief Sets a configuration value for system config
*
* Stores a config value ($value) in the category ($family) under the key ($key)
- * for the user_id $uid.
*
* Note: Please do not store booleans - convert to 0/1 integer values!
*
* @param mixed $value The value to store
*
* @return bool Operation success
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function set($family, $key, $value)
{
- // Database isn't ready or populated yet
- if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
- return false;
- }
-
- if (empty(self::$adapter)) {
- self::init();
+ if (!isset(self::$adapter) || !self::$adapter->isConnected()) {
+ return self::$cache->set($family, $key, $value);
}
return self::$adapter->set($family, $key, $value);
/**
* @brief Deletes the given key from the system configuration.
*
- * Removes the configured value from the stored cache in $a->config
- * and removes it from the database.
+ * Removes the configured value from the stored cache in self::$config
+ * (@see ConfigCache ) and removes it from the database (@see IConfigAdapter ).
*
* @param string $family The category of the configuration value
* @param string $key The configuration key to delete
*
* @return mixed
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function delete($family, $key)
{
- // Database isn't ready or populated yet
- if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
- return false;
- }
-
- if (empty(self::$adapter)) {
- self::init();
+ if (!isset(self::$adapter) || !self::$adapter->isConnected()) {
+ self::$cache->delete($family, $key);
}
return self::$adapter->delete($family, $key);
--- /dev/null
+<?php
+
+namespace Friendica\Core\Config;
+
+use Friendica\Database\DBA;
+
+abstract class AbstractDbaConfigAdapter
+{
+ public function isConnected()
+ {
+ return DBA::connected();
+ }
+}
--- /dev/null
+<?php
+
+namespace Friendica\Core\Config;
+
+/**
+ * The Friendica config cache for the application
+ * Initial, all *.config.php files are loaded into this cache with the
+ * ConfigCacheLoader ( @see ConfigCacheLoader )
+ *
+ * Is used for further caching operations too (depending on the ConfigAdapter )
+ */
+class ConfigCache implements IConfigCache, IPConfigCache
+{
+ private $config;
+
+ /**
+ * @param array $config A initial config array
+ */
+ public function __construct(array $config = [])
+ {
+ $this->config = $config;
+ }
+
+ /**
+ * Tries to load the specified configuration array into the App->config array.
+ * Doesn't overwrite previously set values by default to prevent default config files to supersede DB Config.
+ *
+ * @param array $config
+ * @param bool $overwrite Force value overwrite if the config key already exists
+ */
+ public function loadConfigArray(array $config, $overwrite = false)
+ {
+ foreach ($config as $category => $values) {
+ foreach ($values as $key => $value) {
+ if ($overwrite) {
+ $this->set($category, $key, $value);
+ } else {
+ $this->setDefault($category, $key, $value);
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($cat, $key = null, $default = null)
+ {
+ $return = $default;
+
+ if ($cat === 'config') {
+ if (isset($this->config[$key])) {
+ $return = $this->config[$key];
+ }
+ } else {
+ if (isset($this->config[$cat][$key])) {
+ $return = $this->config[$cat][$key];
+ } elseif ($key == null && isset($this->config[$cat])) {
+ $return = $this->config[$cat];
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * Sets a default value in the config cache. Ignores already existing keys.
+ *
+ * @param string $cat Config category
+ * @param string $k Config key
+ * @param mixed $v Default value to set
+ */
+ private function setDefault($cat, $k, $v)
+ {
+ if (!isset($this->config[$cat][$k])) {
+ $this->set($cat, $k, $v);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($cat, $key, $value)
+ {
+ // Only arrays are serialized in database, so we have to unserialize sparingly
+ $value = is_string($value) && preg_match("|^a:[0-9]+:{.*}$|s", $value) ? unserialize($value) : $value;
+
+ if ($cat === 'config') {
+ $this->config[$key] = $value;
+ } else {
+ if (!isset($this->config[$cat])) {
+ $this->config[$cat] = [];
+ }
+
+ $this->config[$cat][$key] = $value;
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function delete($cat, $key)
+ {
+ if ($cat === 'config') {
+ if (isset($this->config[$key])) {
+ unset($this->config[$key]);
+ }
+ } else {
+ if (isset($this->config[$cat][$key])) {
+ unset($this->config[$cat][$key]);
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getP($uid, $cat, $key = null, $default = null)
+ {
+ $return = $default;
+
+ if (isset($this->config[$uid][$cat][$key])) {
+ $return = $this->config[$uid][$cat][$key];
+ } elseif ($key === null && isset($this->config[$uid][$cat])) {
+ $return = $this->config[$uid][$cat];
+ }
+
+ return $return;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setP($uid, $cat, $key, $value)
+ {
+ // Only arrays are serialized in database, so we have to unserialize sparingly
+ $value = is_string($value) && preg_match("|^a:[0-9]+:{.*}$|s", $value) ? unserialize($value) : $value;
+
+ if (!isset($this->config[$uid]) || !is_array($this->config[$uid])) {
+ $this->config[$uid] = [];
+ }
+
+ if (!isset($this->config[$uid][$cat]) || !is_array($this->config[$uid][$cat])) {
+ $this->config[$uid][$cat] = [];
+ }
+
+ if ($key === null) {
+ $this->config[$uid][$cat] = $value;
+ } else {
+ $this->config[$uid][$cat][$key] = $value;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function deleteP($uid, $cat, $key)
+ {
+ if (isset($this->config[$uid][$cat][$key])) {
+ unset($this->config[$uid][$cat][$key]);
+ }
+ }
+
+ /**
+ * Returns the whole configuration
+ *
+ * @return array The configuration
+ */
+ public function getAll()
+ {
+ return $this->config;
+ }
+}
--- /dev/null
+<?php
+
+namespace Friendica\Core\Config;
+
+use Friendica\Core\Addon;
+
+/**
+ * The ConfigCacheLoader loads config-files and stores them in a ConfigCache ( @see ConfigCache )
+ *
+ * It is capable of loading the following config files:
+ * - *.config.php (current)
+ * - *.ini.php (deprecated)
+ * - *.htconfig.php (deprecated)
+ */
+class ConfigCacheLoader
+{
+ /**
+ * The Sub directory of the config-files
+ * @var string
+ */
+ const SUBDIRECTORY = 'config';
+
+ private $baseDir;
+ private $configDir;
+
+ public function __construct($baseDir)
+ {
+ $this->baseDir = $baseDir;
+ $this->configDir = $baseDir . DIRECTORY_SEPARATOR . self::SUBDIRECTORY;
+ }
+
+ /**
+ * Load the configuration files
+ *
+ * First loads the default value for all the configuration keys, then the legacy configuration files, then the
+ * expected local.config.php
+ */
+ public function loadConfigFiles(ConfigCache $config)
+ {
+ // Setting at least the basepath we know
+ $config->set('system', 'basepath', $this->baseDir);
+
+ $config->loadConfigArray($this->loadCoreConfig('defaults'));
+ $config->loadConfigArray($this->loadCoreConfig('settings'));
+
+ $config->loadConfigArray($this->loadLegacyConfig('htpreconfig'), true);
+ $config->loadConfigArray($this->loadLegacyConfig('htconfig'), true);
+
+ $config->loadConfigArray($this->loadCoreConfig('local'), true);
+ }
+
+ /**
+ * Tries to load the specified core-configuration and returns the config array.
+ *
+ * @param string $name The name of the configuration
+ *
+ * @return array The config array (empty if no config found)
+ *
+ * @throws \Exception if the configuration file isn't readable
+ */
+ 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');
+ } else {
+ return [];
+ }
+ }
+
+ /**
+ * Tries to load the specified addon-configuration and returns the config array.
+ *
+ * @param string $name The name of the configuration
+ *
+ * @return array The config array (empty if no config found)
+ *
+ * @throws \Exception if the configuration file isn't readable
+ */
+ public function loadAddonConfig($name)
+ {
+ $filepath = $this->baseDir . DIRECTORY_SEPARATOR . // /var/www/html/
+ Addon::DIRECTORY . DIRECTORY_SEPARATOR . // addon/
+ $name . DIRECTORY_SEPARATOR . // openstreetmap/
+ self::SUBDIRECTORY . DIRECTORY_SEPARATOR . // config/
+ $name . ".config.php"; // openstreetmap.config.php
+
+ if (file_exists($filepath)) {
+ return $this->loadConfigFile($filepath);
+ } else {
+ return [];
+ }
+ }
+
+ /**
+ * 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
+ *
+ * @return array The configuration array (empty if no config found)
+ *
+ * @deprecated since version 2018.09
+ */
+ private function loadLegacyConfig($name)
+ {
+ $filePath = $this->baseDir . DIRECTORY_SEPARATOR . '.' . $name . '.php';
+
+ if (file_exists($filePath)) {
+ $a = new \stdClass();
+ $a->config = [];
+ include $filePath;
+
+ if (isset($db_host)) {
+ $a->config['database']['hostname'] = $db_host;
+ unset($db_host);
+ }
+ if (isset($db_user)) {
+ $a->config['database']['username'] = $db_user;
+ unset($db_user);
+ }
+ if (isset($db_pass)) {
+ $a->config['database']['password'] = $db_pass;
+ unset($db_pass);
+ }
+ if (isset($db_data)) {
+ $a->config['database']['database'] = $db_data;
+ unset($db_data);
+ }
+ if (isset($a->config['system']['db_charset'])) {
+ $a->config['database']['charset'] = $a->config['system']['charset'];
+ }
+ if (isset($pidfile)) {
+ $a->config['system']['pidfile'] = $pidfile;
+ unset($pidfile);
+ }
+ if (isset($default_timezone)) {
+ $a->config['system']['default_timezone'] = $default_timezone;
+ unset($default_timezone);
+ }
+ if (isset($lang)) {
+ $a->config['system']['language'] = $lang;
+ unset($lang);
+ }
+
+ return $a->config;
+ } else {
+ return [];
+ }
+ }
+
+ /**
+ * Tries to load the specified legacy configuration file and returns the config array.
+ *
+ * @deprecated since version 2018.12
+ * @param string $filepath
+ *
+ * @return array The configuration array
+ * @throws \Exception
+ */
+ private function loadINIConfigFile($filepath)
+ {
+ if (!file_exists($filepath)) {
+ throw new \Exception('Error parsing non-existent INI config file ' . $filepath);
+ }
+
+ $contents = include($filepath);
+
+ $config = parse_ini_string($contents, true, INI_SCANNER_TYPED);
+
+ if ($config === false) {
+ throw new \Exception('Error parsing INI config file ' . $filepath);
+ }
+
+ return $config;
+ }
+
+ /**
+ * Tries to load the specified configuration file and returns the config array.
+ *
+ * The config format is PHP array and the template for configuration files is the following:
+ *
+ * <?php return [
+ * 'section' => [
+ * 'key' => 'value',
+ * ],
+ * ];
+ *
+ * @param string $filepath The filepath of the
+ * @return array The config array0
+ *
+ * @throws \Exception if the config cannot get loaded.
+ */
+ private function loadConfigFile($filepath)
+ {
+ if (!file_exists($filepath)) {
+ throw new \Exception('Error loading non-existent config file ' . $filepath);
+ }
+
+ $config = include($filepath);
+
+ if (!is_array($config)) {
+ throw new \Exception('Error loading config file ' . $filepath);
+ }
+
+ return $config;
+ }
+}
interface IConfigAdapter
{
/**
- * @brief Loads all configuration values into a cached storage.
- *
- * All configuration values of the system are stored in global cache
- * which is available under the global variable $a->config
+ * Loads all configuration values into a cached storage.
*
* @param string $cat The category of the configuration values to load
*
public function load($cat = "config");
/**
- * @brief Get a particular user's config variable given the category name
+ * Get a particular user's config variable given the category name
* ($family) and a key.
*
- * Get a particular config value from the given category ($family)
- * and the $key from a cached storage in $a->config[$uid].
- * $instore is only used by the set_config function
- * to determine if the key already exists in the DB
- * If a key is found in the DB but doesn't exist in
- * local config cache, pull it into the cache so we don't have
- * to hit the DB again for this item.
- *
* @param string $cat The category of the configuration value
* @param string $k The configuration key to query
* @param mixed $default_value optional, The value to return if key is not set (default: null)
public function get($cat, $k, $default_value = null, $refresh = false);
/**
- * @brief Sets a configuration value for system config
- *
* Stores a config value ($value) in the category ($family) under the key ($key)
* for the user_id $uid.
*
public function set($cat, $k, $value);
/**
- * @brief Deletes the given key from the system configuration.
- *
- * Removes the configured value from the stored cache in $a->config
+ * Removes the configured value from the stored cache
* and removes it from the database.
*
* @param string $cat The category of the configuration value
* @return mixed
*/
public function delete($cat, $k);
+
+ /**
+ * Checks, if the current adapter is connected to the backend
+ *
+ * @return bool
+ */
+ public function isConnected();
}
--- /dev/null
+<?php
+
+namespace Friendica\Core\Config;
+
+/**
+ * The interface for a system-wide ConfigCache
+ */
+interface IConfigCache
+{
+ /**
+ * @param string $cat Config category
+ * @param string $key Config key
+ * @param mixed $default Default value if it isn't set
+ *
+ * @return mixed Returns the value of the Config entry
+ */
+ function get($cat, $key = null, $default = null);
+
+ /**
+ * Sets a value in the config cache. Accepts raw output from the config table
+ *
+ * @param string $cat Config category
+ * @param string $key Config key
+ * @param mixed $value Value to set
+ *
+ * @return bool True, if the value is set
+ */
+ function set($cat, $key, $value);
+
+ /**
+ * Deletes a value from the config cache
+ *
+ * @param string $cat Config category
+ * @param string $key Config key
+ */
+ function delete($cat, $key);
+
+ function getAll();
+}
interface IPConfigAdapter
{
/**
- * @brief Loads all configuration values of a user's config family into a cached storage.
- *
- * All configuration values of the given user are stored in global cache
- * which is available under the global variable $a->config[$uid].
+ * Loads all configuration values of a user's config family into a cached storage.
*
* @param string $uid The user_id
* @param string $cat The category of the configuration value
public function load($uid, $cat);
/**
- * @brief Get a particular user's config variable given the category name
+ * Get a particular user's config variable given the category name
* ($family) and a key.
*
- * Get a particular user's config value from the given category ($family)
- * and the $key from a cached storage in $a->config[$uid].
- *
* @param string $uid The user_id
* @param string $cat The category of the configuration value
* @param string $k The configuration key to query
public function get($uid, $cat, $k, $default_value = null, $refresh = false);
/**
- * @brief Sets a configuration value for a user
- *
* Stores a config value ($value) in the category ($family) under the key ($key)
* for the user_id $uid.
*
public function set($uid, $cat, $k, $value);
/**
- * @brief Deletes the given key from the users's configuration.
- *
- * Removes the configured value from the stored cache in $a->config[$uid]
+ * Removes the configured value from the stored cache
* and removes it from the database.
*
* @param string $uid The user_id
--- /dev/null
+<?php
+
+namespace Friendica\Core\Config;
+
+/**
+ * The interface for a user-specific config cache
+ */
+interface IPConfigCache
+{
+ /**
+ * Retrieves a value from the user config cache
+ *
+ * @param int $uid User Id
+ * @param string $cat Config category
+ * @param string $key Config key
+ * @param mixed $default Default value if key isn't set
+ *
+ * @return string The value of the config entry
+ */
+ function getP($uid, $cat, $key = null, $default = null);
+
+ /**
+ * Sets a value in the user config cache
+ *
+ * Accepts raw output from the pconfig table
+ *
+ * @param int $uid User Id
+ * @param string $cat Config category
+ * @param string $key Config key
+ * @param mixed $value Value to set
+ */
+ function setP($uid, $cat, $key, $value);
+
+ /**
+ * Deletes a value from the user config cache
+ *
+ * @param int $uid User Id
+ * @param string $cat Config category
+ * @param string $key Config key
+ */
+ function deleteP($uid, $cat, $key);
+
+ function getAll();
+}
<?php
namespace Friendica\Core\Config;
-use Friendica\BaseObject;
use Friendica\Database\DBA;
/**
*
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
-class JITConfigAdapter extends BaseObject implements IConfigAdapter
+class JITConfigAdapter extends AbstractDbaConfigAdapter implements IConfigAdapter
{
private $cache;
private $in_db;
+ /**
+ * @var IConfigCache The config cache of this driver
+ */
+ private $configCache;
+
+ /**
+ * @param IConfigCache $configCache The config cache of this driver
+ */
+ public function __construct(IConfigCache $configCache)
+ {
+ $this->configCache = $configCache;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function load($cat = "config")
{
+ if (!$this->isConnected()) {
+ return;
+ }
+
// We don't preload "system" anymore.
// This reduces the number of database reads a lot.
if ($cat === 'system') {
while ($config = DBA::fetch($configs)) {
$k = $config['k'];
- self::getApp()->setConfigValue($cat, $k, $config['v']);
+ $this->configCache->set($cat, $k, $config['v']);
if ($cat !== 'config') {
$this->cache[$cat][$k] = $config['v'];
DBA::close($configs);
}
+ /**
+ * {@inheritdoc}
+ */
public function get($cat, $k, $default_value = null, $refresh = false)
{
- $a = self::getApp();
+ if (!$this->isConnected()) {
+ return $default_value;
+ }
if (!$refresh) {
// Do we have the cached value? Then return it
$this->cache[$cat][$k] = $value;
$this->in_db[$cat][$k] = true;
return $value;
- } elseif (isset($a->config[$cat][$k])) {
+ } elseif ($this->configCache->get($cat, $k) !== null) {
// Assign the value (mostly) from config/local.config.php file to the cache
- $this->cache[$cat][$k] = $a->config[$cat][$k];
+ $this->cache[$cat][$k] = $this->configCache->get($cat, $k);
$this->in_db[$cat][$k] = false;
- return $a->config[$cat][$k];
- } elseif (isset($a->config[$k])) {
+ return $this->configCache->get($cat, $k);
+ } elseif ($this->configCache->get('config', $k) !== null) {
// Assign the value (mostly) from config/local.config.php file to the cache
- $this->cache[$k] = $a->config[$k];
+ $this->cache[$k] = $this->configCache->get('config', $k);
$this->in_db[$k] = false;
- return $a->config[$k];
+ return $this->configCache->get('config', $k);
}
$this->cache[$cat][$k] = '!<unset>!';
return $default_value;
}
+ /**
+ * {@inheritdoc}
+ */
public function set($cat, $k, $value)
{
+ if (!$this->isConnected()) {
+ return false;
+ }
+
// We store our setting values in a string variable.
// So we have to do the conversion here so that the compare below works.
// The exception are array values.
return true;
}
- self::getApp()->setConfigValue($cat, $k, $value);
+ $this->configCache->set($cat, $k, $value);
// Assign the just added value to the cache
$this->cache[$cat][$k] = $dbvalue;
return $result;
}
+ /**
+ * {@inheritdoc}
+ */
public function delete($cat, $k)
{
+ if (!$this->isConnected()) {
+ return false;
+ }
+
if (isset($this->cache[$cat][$k])) {
unset($this->cache[$cat][$k]);
unset($this->in_db[$cat][$k]);
<?php
namespace Friendica\Core\Config;
-use Friendica\BaseObject;
use Friendica\Database\DBA;
/**
*
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
-class JITPConfigAdapter extends BaseObject implements IPConfigAdapter
+class JITPConfigAdapter implements IPConfigAdapter
{
private $in_db;
- public function load($uid, $cat)
+ /**
+ * The config cache of this adapter
+ * @var IPConfigCache
+ */
+ private $configCache;
+
+ /**
+ * @param IPConfigCache $configCache The config cache of this adapter
+ */
+ public function __construct(IPConfigCache $configCache)
{
- $a = self::getApp();
+ $this->configCache = $configCache;
+ }
+ /**
+ * {@inheritdoc}
+ */
+ public function load($uid, $cat)
+ {
$pconfigs = DBA::select('pconfig', ['v', 'k'], ['cat' => $cat, 'uid' => $uid]);
if (DBA::isResult($pconfigs)) {
while ($pconfig = DBA::fetch($pconfigs)) {
$k = $pconfig['k'];
- self::getApp()->setPConfigValue($uid, $cat, $k, $pconfig['v']);
+ $this->configCache->setP($uid, $cat, $k, $pconfig['v']);
$this->in_db[$uid][$cat][$k] = true;
}
} else if ($cat != 'config') {
// Negative caching
- $a->config[$uid][$cat] = "!<unset>!";
+ $this->configCache->setP($uid, $cat, null, "!<unset>!");
}
DBA::close($pconfigs);
}
+ /**
+ * {@inheritdoc}
+ */
public function get($uid, $cat, $k, $default_value = null, $refresh = false)
{
- $a = self::getApp();
-
if (!$refresh) {
// Looking if the whole family isn't set
- if (isset($a->config[$uid][$cat])) {
- if ($a->config[$uid][$cat] === '!<unset>!') {
+ if ($this->configCache->getP($uid, $cat) !== null) {
+ if ($this->configCache->getP($uid, $cat) === '!<unset>!') {
return $default_value;
}
}
- if (isset($a->config[$uid][$cat][$k])) {
- if ($a->config[$uid][$cat][$k] === '!<unset>!') {
+ if ($this->configCache->getP($uid, $cat, $k) !== null) {
+ if ($this->configCache->getP($uid, $cat, $k) === '!<unset>!') {
return $default_value;
}
- return $a->config[$uid][$cat][$k];
+ return $this->configCache->getP($uid, $cat, $k);
}
}
if (DBA::isResult($pconfig)) {
$val = (preg_match("|^a:[0-9]+:{.*}$|s", $pconfig['v']) ? unserialize($pconfig['v']) : $pconfig['v']);
- self::getApp()->setPConfigValue($uid, $cat, $k, $val);
+ $this->configCache->setP($uid, $cat, $k, $val);
$this->in_db[$uid][$cat][$k] = true;
return $val;
} else {
- self::getApp()->setPConfigValue($uid, $cat, $k, '!<unset>!');
+ $this->configCache->setP($uid, $cat, $k, '!<unset>!');
$this->in_db[$uid][$cat][$k] = false;
}
}
+ /**
+ * {@inheritdoc}
+ */
public function set($uid, $cat, $k, $value)
{
// We store our setting values in a string variable.
return true;
}
- self::getApp()->setPConfigValue($uid, $cat, $k, $value);
+ $this->configCache->setP($uid, $cat, $k, $value);
// manage array value
$dbvalue = (is_array($value) ? serialize($value) : $dbvalue);
return $result;
}
+ /**
+ * {@inheritdoc}
+ */
public function delete($uid, $cat, $k)
{
- self::getApp()->deletePConfigValue($uid, $cat, $k);
+ $this->configCache->deleteP($uid, $cat, $k);
if (!empty($this->in_db[$uid][$cat][$k])) {
unset($this->in_db[$uid][$cat][$k]);
namespace Friendica\Core\Config;
use Exception;
-use Friendica\BaseObject;
use Friendica\Database\DBA;
/**
*
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
-class PreloadConfigAdapter extends BaseObject implements IConfigAdapter
+class PreloadConfigAdapter extends AbstractDbaConfigAdapter implements IConfigAdapter
{
private $config_loaded = false;
- public function __construct()
+ /**
+ * @var IConfigCache The config cache of this driver
+ */
+ private $configCache;
+
+ /**
+ * @param IConfigCache $configCache The config cache of this driver
+ */
+ public function __construct(IConfigCache $configCache)
{
+ $this->configCache = $configCache;
$this->load();
}
+ /**
+ * {@inheritdoc}
+ */
public function load($family = 'config')
{
+ if (!$this->isConnected()) {
+ return;
+ }
+
if ($this->config_loaded) {
return;
}
$configs = DBA::select('config', ['cat', 'v', 'k']);
while ($config = DBA::fetch($configs)) {
- self::getApp()->setConfigValue($config['cat'], $config['k'], $config['v']);
+ $this->configCache->set($config['cat'], $config['k'], $config['v']);
}
DBA::close($configs);
$this->config_loaded = true;
}
+ /**
+ * {@inheritdoc}
+ */
public function get($cat, $k, $default_value = null, $refresh = false)
{
+ if (!$this->isConnected()) {
+ return $default_value;
+ }
+
if ($refresh) {
$config = DBA::selectFirst('config', ['v'], ['cat' => $cat, 'k' => $k]);
if (DBA::isResult($config)) {
- self::getApp()->setConfigValue($cat, $k, $config['v']);
+ $this->configCache->set($cat, $k, $config['v']);
}
}
- $return = self::getApp()->getConfigValue($cat, $k, $default_value);
+ $return = $this->configCache->get($cat, $k, $default_value);
return $return;
}
+ /**
+ * {@inheritdoc}
+ */
public function set($cat, $k, $value)
{
+ if (!$this->isConnected()) {
+ return false;
+ }
+
// We store our setting values as strings.
// So we have to do the conversion here so that the compare below works.
// The exception are array values.
$compare_value = !is_array($value) ? (string)$value : $value;
- if (self::getApp()->getConfigValue($cat, $k) === $compare_value) {
+ if ($this->configCache->get($cat, $k) === $compare_value) {
return true;
}
- self::getApp()->setConfigValue($cat, $k, $value);
+ $this->configCache->set($cat, $k, $value);
// manage array value
$dbvalue = is_array($value) ? serialize($value) : $value;
return true;
}
+ /**
+ * {@inheritdoc}
+ */
public function delete($cat, $k)
{
- self::getApp()->deleteConfigValue($cat, $k);
+ if (!$this->isConnected()) {
+ return false;
+ }
+
+ $this->configCache->delete($cat, $k);
$result = DBA::delete('config', ['cat' => $cat, 'k' => $k]);
namespace Friendica\Core\Config;
use Exception;
-use Friendica\BaseObject;
use Friendica\Database\DBA;
/**
*
* @author Hypolite Petovan <hypolite@mrpetovan.com>
*/
-class PreloadPConfigAdapter extends BaseObject implements IPConfigAdapter
+class PreloadPConfigAdapter implements IPConfigAdapter
{
private $config_loaded = false;
- public function __construct($uid)
+ /**
+ * The config cache of this adapter
+ * @var IPConfigCache
+ */
+ private $configCache;
+
+ /**
+ * @param IPConfigCache $configCache The config cache of this adapter
+ * @param int $uid The UID of the current user
+ */
+ public function __construct(IPConfigCache $configCache, $uid = null)
{
- $this->load($uid, 'config');
+ $this->configCache = $configCache;
+ if (isset($uid)) {
+ $this->load($uid, 'config');
+ }
}
+ /**
+ * {@inheritdoc}
+ */
public function load($uid, $family)
{
if ($this->config_loaded) {
$pconfigs = DBA::select('pconfig', ['cat', 'v', 'k'], ['uid' => $uid]);
while ($pconfig = DBA::fetch($pconfigs)) {
- self::getApp()->setPConfigValue($uid, $pconfig['cat'], $pconfig['k'], $pconfig['v']);
+ $this->configCache->setP($uid, $pconfig['cat'], $pconfig['k'], $pconfig['v']);
}
DBA::close($pconfigs);
$this->config_loaded = true;
}
+ /**
+ * {@inheritdoc}
+ */
public function get($uid, $cat, $k, $default_value = null, $refresh = false)
{
if (!$this->config_loaded) {
if ($refresh) {
$config = DBA::selectFirst('pconfig', ['v'], ['uid' => $uid, 'cat' => $cat, 'k' => $k]);
if (DBA::isResult($config)) {
- self::getApp()->setPConfigValue($uid, $cat, $k, $config['v']);
+ $this->configCache->setP($uid, $cat, $k, $config['v']);
} else {
- self::getApp()->deletePConfigValue($uid, $cat, $k);
+ $this->configCache->deleteP($uid, $cat, $k);
}
}
- $return = self::getApp()->getPConfigValue($uid, $cat, $k, $default_value);
-
- return $return;
+ return $this->configCache->getP($uid, $cat, $k, $default_value);;
}
+ /**
+ * {@inheritdoc}
+ */
public function set($uid, $cat, $k, $value)
{
if (!$this->config_loaded) {
// The exception are array values.
$compare_value = !is_array($value) ? (string)$value : $value;
- if (self::getApp()->getPConfigValue($uid, $cat, $k) === $compare_value) {
+ if ($this->configCache->getP($uid, $cat, $k) === $compare_value) {
return true;
}
- self::getApp()->setPConfigValue($uid, $cat, $k, $value);
+ $this->configCache->setP($uid, $cat, $k, $value);
// manage array value
$dbvalue = is_array($value) ? serialize($value) : $value;
return true;
}
+ /**
+ * {@inheritdoc}
+ */
public function delete($uid, $cat, $k)
{
if (!$this->config_loaded) {
$this->load($uid, $cat);
}
- self::getApp()->deletePConfigValue($uid, $cat, $k);
+ $this->configCache->deleteP($uid, $cat, $k);
$result = DBA::delete('pconfig', ['uid' => $uid, 'cat' => $cat, 'k' => $k]);
}
}
- $db_host = $a->getConfigValue('database', 'hostname');
- $db_user = $a->getConfigValue('database', 'username');
- $db_pass = $a->getConfigValue('database', 'password');
- $db_data = $a->getConfigValue('database', 'database');
+ $db_host = $a->getConfig()->get('database', 'hostname');
+ $db_user = $a->getConfig()->get('database', 'username');
+ $db_pass = $a->getConfig()->get('database', 'password');
+ $db_data = $a->getConfig()->get('database', 'database');
} else {
// Creating config file
$this->out("Creating config file...\n");
$installer->resetChecks();
- if (!$installer->installDatabase()) {
+ if (!$installer->installDatabase($a->getBasePath())) {
$errorMessage = $this->extractErrors($installer->getChecks());
throw new RuntimeException($errorMessage);
}
if (is_array($value)) {
foreach ($value as $k => $v) {
- $this->out("{$cat}.{$key}[{$k}] => " . $v);
+ $this->out("{$cat}.{$key}[{$k}] => " . (is_array($v) ? implode(', ', $v) : $v));
}
} else {
$this->out("{$cat}.{$key} => " . $value);
$cat = $this->getArgument(0);
Core\Config::load($cat);
- if (!is_null($a->config[$cat])) {
+ if ($a->getConfig()->get($cat) !== null) {
$this->out("[{$cat}]");
- foreach ($a->config[$cat] as $key => $value) {
+ $catVal = $a->getConfig()->get($cat);
+ foreach ($catVal as $key => $value) {
if (is_array($value)) {
foreach ($value as $k => $v) {
- $this->out("{$key}[{$k}] => " . $v);
+ $this->out("{$key}[{$k}] => " . (is_array($v) ? implode(', ', $v) : $v));
}
} else {
$this->out("{$key} => " . $value);
$this->out('Warning: The JIT (Just In Time) Config adapter doesn\'t support loading the entire configuration, showing file config only');
}
- foreach ($a->config as $cat => $section) {
+ $config = $a->getConfig()->getAll();
+ foreach ($config as $cat => $section) {
if (is_array($section)) {
foreach ($section as $key => $value) {
if (is_array($value)) {
foreach ($value as $k => $v) {
- $this->out("{$cat}.{$key}[{$k}] => " . $v);
+ $this->out("{$cat}.{$key}[{$k}] => " . (is_array($v) ? implode(', ', $v) : $v));
}
} else {
$this->out("{$cat}.{$key} => " . $value);
Core\Config::load();
+ $a = get_app();
+
switch ($this->getArgument(0)) {
case "dryrun":
- $output = DBStructure::update(true, false);
+ $output = DBStructure::update($a->getBasePath(), true, false);
break;
case "update":
$force = $this->getOption(['f', 'force'], false);
- $output = Update::run($force, true, false);
+ $output = Update::run($a->getBasePath(), $force, true, false);
break;
case "dumpsql":
ob_start();
- DBStructure::printStructure();
+ DBStructure::printStructure($a->getBasePath());
$output = ob_get_clean();
break;
case "toinnodb":
namespace Friendica\Core\Console;
-use Friendica\Core\L10n;
use Friendica\Core\Config;
+use Friendica\Core\L10n;
use Friendica\Core\Update;
/**
}
echo L10n::t('Check for pending update actions.') . "\n";
- Update::run(true, true, false);
+ Update::run($a->getBasePath(), true, true, false);
echo L10n::t('Done.') . "\n";
echo L10n::t('Execute pending post updates.') . "\n";
namespace Friendica\Core\Console;
+use Friendica\BaseObject;
+
/**
* Tired of chasing typos and finding them after a commit.
* Run this and quickly see if we've got any parse errors in our application files.
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
}
- $a = \get_app();
-
- $php_path = $a->getConfigValue('config', 'php_path', 'php');
+ $php_path = BaseObject::getApp()->getConfig()->get('config', 'php_path', 'php');
if ($this->getOption('v')) {
$this->out('Directory: src');
/***
* Installs the DB-Scheme for Friendica
*
+ * @param string $basePath The base path of this application
+ *
* @return bool true if the installation was successful, otherwise false
* @throws Exception
*/
- public function installDatabase()
+ public function installDatabase($basePath)
{
- $result = DBStructure::update(false, true, true);
+ $result = DBStructure::update($basePath, false, true, true);
if ($result) {
$txt = L10n::t('You may need to import the file "database.sql" manually using phpmyadmin or mysql.') . EOL;
namespace Friendica\Core;
use Friendica\BaseObject;
+use Friendica\Factory\LoggerFactory;
use Friendica\Network\HTTPException\InternalServerErrorException;
-use Friendica\Util\LoggerFactory;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
*/
namespace Friendica\Core;
-use Friendica\App;
-use Friendica\BaseObject;
-
/**
* @brief Management of user configuration storage
* Note:
* The PConfig::get() functions return boolean false for keys that are unset,
* and this could lead to subtle bugs.
*/
-class PConfig extends BaseObject
+class PConfig
{
/**
- * @var \Friendica\Core\Config\IPConfigAdapter
+ * @var Config\IPConfigAdapter
*/
- private static $adapter = null;
+ private static $adapter;
- public static function init($uid)
- {
- $a = self::getApp();
+ /**
+ * @var Config\IPConfigCache
+ */
+ private static $cache;
- // Database isn't ready or populated yet
- if (!$a->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
- return;
- }
+ /**
+ * Initialize the config with only the cache
+ *
+ * @param Config\IPConfigCache $cache The configuration cache
+ */
+ public static function init(Config\IPConfigCache $cache)
+ {
+ self::$cache = $cache;
+ }
- if ($a->getConfigValue('system', 'config_adapter') == 'preload') {
- self::$adapter = new Config\PreloadPConfigAdapter($uid);
- } else {
- self::$adapter = new Config\JITPConfigAdapter();
- }
+ /**
+ * Add the adapter for DB-backend
+ *
+ * @param Config\IPConfigAdapter $adapter
+ */
+ public static function setAdapter(Config\IPConfigAdapter $adapter)
+ {
+ self::$adapter = $adapter;
}
/**
* @brief Loads all configuration values of a user's config family into a cached storage.
*
- * All configuration values of the given user are stored in global cache
- * which is available under the global variable $a->config[$uid].
+ * All configuration values of the given user are stored with the $uid in
+ * the cache ( @see IPConfigCache )
*
* @param string $uid The user_id
* @param string $family The category of the configuration value
*
* @return void
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function load($uid, $family)
{
- // Database isn't ready or populated yet
- if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
+ if (!isset(self::$adapter)) {
return;
}
- if (empty(self::$adapter)) {
- self::init($uid);
- }
-
self::$adapter->load($uid, $family);
}
* ($family) and a key.
*
* Get a particular user's config value from the given category ($family)
- * and the $key from a cached storage in $a->config[$uid].
+ * and the $key with the $uid from a cached storage either from the self::$adapter
+ * (@see IConfigAdapter ) or from the static::$cache (@see IConfigCache ).
*
* @param string $uid The user_id
* @param string $family The category of the configuration value
* @param boolean $refresh optional, If true the config is loaded from the db and not from the cache (default: false)
*
* @return mixed Stored value or null if it does not exist
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function get($uid, $family, $key, $default_value = null, $refresh = false)
{
- // Database isn't ready or populated yet
- if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
- return;
- }
-
- if (empty(self::$adapter)) {
- self::init($uid);
+ if (!isset(self::$adapter)) {
+ return self::$cache->getP($uid, $family, $key, $default_value);
}
return self::$adapter->get($uid, $family, $key, $default_value, $refresh);
* @param mixed $value The value to store
*
* @return bool Operation success
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function set($uid, $family, $key, $value)
{
- // Database isn't ready or populated yet
- if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
- return false;
- }
-
- if (empty(self::$adapter)) {
- self::init($uid);
+ if (!isset(self::$adapter)) {
+ return self::$cache->setP($uid, $family, $key, $value);
}
return self::$adapter->set($uid, $family, $key, $value);
/**
* @brief Deletes the given key from the users's configuration.
*
- * Removes the configured value from the stored cache in $a->config[$uid]
- * and removes it from the database.
+ * Removes the configured value from the stored cache in self::$config
+ * (@see ConfigCache ) and removes it from the database (@see IConfigAdapter )
+ * with the given $uid.
*
* @param string $uid The user_id
* @param string $family The category of the configuration value
* @param string $key The configuration key to delete
*
* @return mixed
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function delete($uid, $family, $key)
{
- // Database isn't ready or populated yet
- if (!self::getApp()->getMode()->has(App\Mode::DBCONFIGAVAILABLE)) {
- return false;
- }
-
- if (empty(self::$adapter)) {
- self::init($uid);
+ if (!isset(self::$adapter)) {
+ return self::$cache->deleteP($uid, $family, $key);
}
return self::$adapter->delete($uid, $family, $key);
exit();
}
+ /**
+ * @brief Returns the system user that is executing the script
+ *
+ * This mostly returns something like "www-data".
+ *
+ * @return string system username
+ */
+ public static function getUser()
+ {
+ if (!function_exists('posix_getpwuid') || !function_exists('posix_geteuid')) {
+ return '';
+ }
+
+ $processUser = posix_getpwuid(posix_geteuid());
+ return $processUser['name'];
+ }
+
+ /**
+ * @brief Checks if a given directory is usable for the system
+ *
+ * @param $directory
+ * @param bool $check_writable
+ *
+ * @return boolean the directory is usable
+ */
+ public static function isDirectoryUsable($directory, $check_writable = true)
+ {
+ if ($directory == '') {
+ Logger::log('Directory is empty. This shouldn\'t happen.', Logger::DEBUG);
+ return false;
+ }
+
+ if (!file_exists($directory)) {
+ Logger::log('Path "' . $directory . '" does not exist for user ' . static::getUser(), Logger::DEBUG);
+ return false;
+ }
+
+ if (is_file($directory)) {
+ Logger::log('Path "' . $directory . '" is a file for user ' . static::getUser(), Logger::DEBUG);
+ return false;
+ }
+
+ if (!is_dir($directory)) {
+ Logger::log('Path "' . $directory . '" is not a directory for user ' . static::getUser(), Logger::DEBUG);
+ return false;
+ }
+
+ if ($check_writable && !is_writable($directory)) {
+ Logger::log('Path "' . $directory . '" is not writable for user ' . static::getUser(), Logger::DEBUG);
+ return false;
+ }
+
+ return true;
+ }
+
/// @todo Move the following functions from boot.php
/*
function killme()
/**
* @brief Function to check if the Database structure needs an update.
*
+ * @param string $basePath The base path of this application
* @param boolean $via_worker boolean Is the check run via the worker?
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
- public static function check($via_worker)
+ public static function check($basePath, $via_worker)
{
if (!DBA::connected()) {
return;
if ($build < DB_UPDATE_VERSION) {
// When we cannot execute the database update via the worker, we will do it directly
if (!Worker::add(PRIORITY_CRITICAL, 'DBUpdate') && $via_worker) {
- self::run();
+ self::run($basePath);
}
}
}
/**
* Automatic database updates
*
- * @param bool $force Force the Update-Check even if the lock is set
- * @param bool $verbose Run the Update-Check verbose
- * @param bool $sendMail Sends a Mail to the administrator in case of success/failure
+ * @param string $basePath The base path of this application
+ * @param bool $force Force the Update-Check even if the lock is set
+ * @param bool $verbose Run the Update-Check verbose
+ * @param bool $sendMail Sends a Mail to the administrator in case of success/failure
*
* @return string Empty string if the update is successful, error messages otherwise
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
- public static function run($force = false, $verbose = false, $sendMail = true)
+ public static function run($basePath, $force = false, $verbose = false, $sendMail = true)
{
// In force mode, we release the dbupdate lock first
// Necessary in case of an stuck update
}
// update the structure in one call
- $retval = DBStructure::update($verbose, true);
+ $retval = DBStructure::update($basePath, $verbose, true);
if ($retval) {
if ($sendMail) {
self::updateFailed(
}
}
} elseif ($force) {
- DBStructure::update($verbose, true);
+ DBStructure::update($basePath, $verbose, true);
}
return '';
namespace Friendica\Database;
-// Do not use Core\Config in this class at risk of infinite loop.
-// Please use App->getConfigVariable() instead.
-//use Friendica\Core\Config;
-
+use Friendica\Core\Config\IConfigCache;
use Friendica\Core\Logger;
use Friendica\Core\System;
use Friendica\Util\DateTimeFormat;
public static $connected = false;
+ /**
+ * @var IConfigCache
+ */
+ private static $configCache;
private static $server_info = '';
private static $connection;
private static $driver;
private static $db_name = '';
private static $db_charset = '';
- public static function connect($serveraddr, $user, $pass, $db, $charset = null)
+ public static function connect($configCache, $serveraddr, $user, $pass, $db, $charset = null)
{
if (!is_null(self::$connection) && self::connected()) {
return true;
}
// We are storing these values for being able to perform a reconnect
+ self::$configCache = $configCache;
self::$db_serveraddr = $serveraddr;
self::$db_user = $user;
self::$db_pass = $pass;
public static function reconnect() {
self::disconnect();
- $ret = self::connect(self::$db_serveraddr, self::$db_user, self::$db_pass, self::$db_name, self::$db_charset);
+ $ret = self::connect(self::$configCache, self::$db_serveraddr, self::$db_user, self::$db_pass, self::$db_name, self::$db_charset);
return $ret;
}
* @throws \Exception
*/
private static function logIndex($query) {
- $a = \get_app();
- if (!$a->getConfigVariable('system', 'db_log_index')) {
+ if (!self::$configCache->get('system', 'db_log_index')) {
return;
}
return;
}
- $watchlist = explode(',', $a->getConfigVariable('system', 'db_log_index_watch'));
- $blacklist = explode(',', $a->getConfigVariable('system', 'db_log_index_blacklist'));
+ $watchlist = explode(',', self::$configCache->get('system', 'db_log_index_watch'));
+ $blacklist = explode(',', self::$configCache->get('system', 'db_log_index_blacklist'));
while ($row = self::fetch($r)) {
- if ((intval($a->getConfigVariable('system', 'db_loglimit_index')) > 0)) {
+ if ((intval(self::$configCache->get('system', 'db_loglimit_index')) > 0)) {
$log = (in_array($row['key'], $watchlist) &&
- ($row['rows'] >= intval($a->getConfigVariable('system', 'db_loglimit_index'))));
+ ($row['rows'] >= intval(self::$configCache->get('system', 'db_loglimit_index'))));
} else {
$log = false;
}
- if ((intval($a->getConfigVariable('system', 'db_loglimit_index_high')) > 0) && ($row['rows'] >= intval($a->getConfigVariable('system', 'db_loglimit_index_high')))) {
+ if ((intval(self::$configCache->get('system', 'db_loglimit_index_high')) > 0) && ($row['rows'] >= intval(self::$configCache->get('system', 'db_loglimit_index_high')))) {
$log = true;
}
if ($log) {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
- @file_put_contents($a->getConfigVariable('system', 'db_log_index'), DateTimeFormat::utcNow()."\t".
+ @file_put_contents(self::$configCache->get('system', 'db_log_index'), DateTimeFormat::utcNow()."\t".
$row['key']."\t".$row['rows']."\t".$row['Extra']."\t".
basename($backtrace[1]["file"])."\t".
$backtrace[1]["line"]."\t".$backtrace[2]["function"]."\t".
$orig_sql = $sql;
- if ($a->getConfigValue('system', 'db_callstack')) {
+ if (self::$configCache->get('system', 'db_callstack') !== null) {
$sql = "/*".System::callstack()." */ ".$sql;
}
$a->saveTimestamp($stamp1, 'database');
- if ($a->getConfigValue('system', 'db_log')) {
+ if (self::$configCache->get('system', 'db_log')) {
$stamp2 = microtime(true);
$duration = (float)($stamp2 - $stamp1);
- if (($duration > $a->getConfigValue('system', 'db_loglimit'))) {
+ if (($duration > self::$configCache->get('system', 'db_loglimit'))) {
$duration = round($duration, 3);
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
- @file_put_contents($a->getConfigValue('system', 'db_log'), DateTimeFormat::utcNow()."\t".$duration."\t".
+ @file_put_contents(self::$configCache->get('system', 'db_log'), DateTimeFormat::utcNow()."\t".$duration."\t".
basename($backtrace[1]["file"])."\t".
$backtrace[1]["line"]."\t".$backtrace[2]["function"]."\t".
substr(self::replaceParameters($sql, $args), 0, 2000)."\n", FILE_APPEND);
* This process must only be started once, since the value is cached.
*/
private static function buildRelationData() {
- $definition = DBStructure::definition();
+ $definition = DBStructure::definition(self::$configCache->get('system', 'basepath'));
foreach ($definition AS $table => $structure) {
foreach ($structure['fields'] AS $field => $field_struct) {
return L10n::t('Errors encountered performing database changes: ') . $message . EOL;
}
- public static function printStructure()
+ public static function printStructure($basePath)
{
- $database = self::definition(false);
+ $database = self::definition($basePath, false);
echo "-- ------------------------------------------\n";
echo "-- " . FRIENDICA_PLATFORM . " " . FRIENDICA_VERSION . " (" . FRIENDICA_CODENAME, ")\n";
*
* @see config/dbstructure.config.php
* @param boolean $with_addons_structure Whether to tack on addons additional tables
+ * @param string $basePath The base path of this application
* @return array
* @throws Exception
*/
- public static function definition($with_addons_structure = true)
+ public static function definition($basePath, $with_addons_structure = true)
{
if (!self::$definition) {
- $a = \Friendica\BaseObject::getApp();
- $filename = $a->getBasePath() . '/config/dbstructure.config.php';
+ $filename = $basePath . '/config/dbstructure.config.php';
if (!is_readable($filename)) {
throw new Exception('Missing database structure config file config/dbstructure.config.php');
/**
* Updates DB structure and returns eventual errors messages
*
- * @param bool $verbose
- * @param bool $action Whether to actually apply the update
- * @param bool $install Is this the initial update during the installation?
- * @param array $tables An array of the database tables
- * @param array $definition An array of the definition tables
+ * @param string $basePath The base path of this application
+ * @param bool $verbose
+ * @param bool $action Whether to actually apply the update
+ * @param bool $install Is this the initial update during the installation?
+ * @param array $tables An array of the database tables
+ * @param array $definition An array of the definition tables
* @return string Empty string if the update is successful, error messages otherwise
* @throws Exception
*/
- public static function update($verbose, $action, $install = false, array $tables = null, array $definition = null)
+ public static function update($basePath, $verbose, $action, $install = false, array $tables = null, array $definition = null)
{
if ($action && !$install) {
Config::set('system', 'maintenance', 1);
// Get the definition
if (is_null($definition)) {
- $definition = self::definition();
+ $definition = self::definition($basePath);
}
// MySQL >= 5.7.4 doesn't support the IGNORE keyword in ALTER TABLE statements
--- /dev/null
+<?php
+
+namespace Friendica\Factory;
+
+use Friendica\Core\Config;
+
+class ConfigFactory
+{
+ /**
+ * @param Config\ConfigCacheLoader $loader The Config Cache loader (INI/config/.htconfig)
+ *
+ * @return Config\ConfigCache
+ */
+ public static function createCache(Config\ConfigCacheLoader $loader)
+ {
+ $configCache = new Config\ConfigCache();
+ $loader->loadConfigFiles($configCache);
+
+ return $configCache;
+ }
+
+ /**
+ * @param string $type The adapter type
+ * @param Config\IConfigCache $config The config cache of this adapter
+ *
+ * @return Config\IConfigAdapter
+ */
+ public static function createConfig($type, Config\IConfigCache $config)
+ {
+ if ($type == 'preload') {
+ return new Config\PreloadConfigAdapter($config);
+ } else {
+ return new Config\JITConfigAdapter($config);
+ }
+ }
+
+ /**
+ * @param string $type The adapter type
+ * @param Config\IPConfigCache $config The config cache of this adapter
+ * @param int $uid The UID of the current user
+ *
+ * @return Config\IPConfigAdapter
+ */
+ public static function createPConfig($type, Config\IPConfigCache $config, $uid = null)
+ {
+ if ($type == 'preload') {
+ return new Config\PreloadPConfigAdapter($config, $uid);
+ } else {
+ return new Config\JITPConfigAdapter($config);
+ }
+ }
+}
--- /dev/null
+<?php
+
+namespace Friendica\Factory;
+
+use Friendica\Core\Config\ConfigCache;
+use Friendica\Network\HTTPException\InternalServerErrorException;
+use Friendica\Util\Logger\FriendicaDevelopHandler;
+use Friendica\Util\Logger\FriendicaIntrospectionProcessor;
+use Monolog;
+use Psr\Log\LoggerInterface;
+use Psr\Log\LogLevel;
+
+/**
+ * A logger factory
+ *
+ * Currently only Monolog is supported
+ */
+class LoggerFactory
+{
+ /**
+ * Creates a new PSR-3 compliant logger instances
+ *
+ * @param string $channel The channel of the logger instance
+ * @param ConfigCache $config The config
+ *
+ * @return LoggerInterface The PSR-3 compliant logger instance
+ */
+ public static function create($channel, ConfigCache $config = null)
+ {
+ $logger = new Monolog\Logger($channel);
+ $logger->pushProcessor(new Monolog\Processor\PsrLogMessageProcessor());
+ $logger->pushProcessor(new Monolog\Processor\ProcessIdProcessor());
+ $logger->pushProcessor(new Monolog\Processor\UidProcessor());
+ $logger->pushProcessor(new FriendicaIntrospectionProcessor(LogLevel::DEBUG, ['Friendica\\Core\\Logger']));
+
+ if (isset($config)) {
+ $debugging = $config->get('system', 'debugging');
+ $stream = $config->get('system', 'logfile');
+ $level = $config->get('system', 'loglevel');
+
+ if ($debugging) {
+ static::addStreamHandler($logger, $stream, $level);
+ }
+ }
+
+ return $logger;
+ }
+
+ /**
+ * Creates a new PSR-3 compliant develop logger
+ *
+ * If you want to debug only interactions from your IP or the IP of a remote server for federation debug,
+ * you'll use this logger instance for the duration of your work.
+ *
+ * It should never get filled during normal usage of Friendica
+ *
+ * @param string $channel The channel of the logger instance
+ * @param string $developerIp The IP of the developer who wants to use the logger
+ *
+ * @return LoggerInterface The PSR-3 compliant logger instance
+ */
+ public static function createDev($channel, $developerIp)
+ {
+ $logger = new Monolog\Logger($channel);
+ $logger->pushProcessor(new Monolog\Processor\PsrLogMessageProcessor());
+ $logger->pushProcessor(new Monolog\Processor\ProcessIdProcessor());
+ $logger->pushProcessor(new Monolog\Processor\UidProcessor());
+ $logger->pushProcessor(new FriendicaIntrospectionProcessor(LogLevel::DEBUG, ['Friendica\\Core\\Logger']));
+
+
+ $logger->pushHandler(new FriendicaDevelopHandler($developerIp));
+
+ return $logger;
+ }
+
+ /**
+ * Adding a handler to a given logger instance
+ *
+ * @param LoggerInterface $logger The logger instance
+ * @param mixed $stream The stream which handles the logger output
+ * @param string $level The level, for which this handler at least should handle logging
+ *
+ * @return void
+ *
+ * @throws InternalServerErrorException if the logger is incompatible to the logger factory
+ * @throws \Exception in case of general failures
+ */
+ public static function addStreamHandler($logger, $stream, $level = LogLevel::NOTICE)
+ {
+ if ($logger instanceof Monolog\Logger) {
+ $loglevel = Monolog\Logger::toMonologLevel($level);
+
+ // fallback to notice if an invalid loglevel is set
+ if (!is_int($loglevel)) {
+ $loglevel = LogLevel::NOTICE;
+ }
+ $fileHandler = new Monolog\Handler\StreamHandler($stream, $loglevel);
+
+ $formatter = new Monolog\Formatter\LineFormatter("%datetime% %channel% [%level_name%]: %message% %context% %extra%\n");
+ $fileHandler->setFormatter($formatter);
+
+ $logger->pushHandler($fileHandler);
+ } else {
+ throw new InternalServerErrorException('Logger instance incompatible for MonologFactory');
+ }
+ }
+
+ /**
+ * This method enables the test mode of a given logger
+ *
+ * @param LoggerInterface $logger The logger
+ *
+ * @return Monolog\Handler\TestHandler the Handling for tests
+ *
+ * @throws InternalServerErrorException if the logger is incompatible to the logger factory
+ */
+ public static function enableTest($logger)
+ {
+ if ($logger instanceof Monolog\Logger) {
+ // disable every handler so far
+ $logger->pushHandler(new Monolog\Handler\NullHandler());
+
+ // enable the test handler
+ $fileHandler = new Monolog\Handler\TestHandler();
+ $formatter = new Monolog\Formatter\LineFormatter("%datetime% %channel% [%level_name%]: %message% %context% %extra%\n");
+ $fileHandler->setFormatter($formatter);
+
+ $logger->pushHandler($fileHandler);
+
+ return $fileHandler;
+ } else {
+ throw new InternalServerErrorException('Logger instance incompatible for MonologFactory');
+ }
+ }
+}
namespace Friendica\Model;
use Friendica\BaseObject;
-use Friendica\Core\System;
use Friendica\Core\StorageManager;
+use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\Model\Storage\IStorage;
use Friendica\Object\Image;
-use Friendica\Util\Security;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Mimetype;
+use Friendica\Util\Security;
/**
* Class to handle attach dabatase table
*/
private static function getFields()
{
- $allfields = DBStructure::definition(false);
+ $allfields = DBStructure::definition(self::getApp()->getBasePath(), false);
$fields = array_keys($allfields['attach']['fields']);
array_splice($fields, array_search('data', $fields), 1);
return $fields;
$item['private'] = 0;
}
- // If its a post from myself then tag the thread as "mention"
- Logger::log("Checking if parent ".$parent_id." has to be tagged as mention for user ".$item['uid'], Logger::DEBUG);
- $user = DBA::selectFirst('user', ['nickname'], ['uid' => $item['uid']]);
- if (DBA::isResult($user)) {
- $self = Strings::normaliseLink(System::baseUrl() . '/profile/' . $user['nickname']);
- $self_id = Contact::getIdForURL($self, 0, true);
- Logger::log("'myself' is ".$self_id." for parent ".$parent_id." checking against ".$item['author-id']." and ".$item['owner-id'], Logger::DEBUG);
- if (($item['author-id'] == $self_id) || ($item['owner-id'] == $self_id)) {
- DBA::update('thread', ['mention' => true], ['iid' => $parent_id]);
- Logger::log("tagged thread ".$parent_id." as mention for user ".$self, Logger::DEBUG);
- }
+ // If its a post that originated here then tag the thread as "mention"
+ if ($item['origin'] && $item['uid']) {
+ DBA::update('thread', ['mention' => true], ['iid' => $parent_id]);
+ Logger::log('tagged thread ' . $parent_id . ' as mention for user ' . $item['uid'], Logger::DEBUG);
}
} else {
/*
return;
}
- self::$installer->installDatabase();
+ self::$installer->installDatabase($a->getBasePath());
break;
}
--- /dev/null
+<?php
+
+namespace Friendica\Util;
+
+class BasePath
+{
+ /**
+ * @brief Returns the base filesystem path of the App
+ *
+ * It first checks for the internal variable, then for DOCUMENT_ROOT and
+ * finally for PWD
+ *
+ * @param string|null $basePath The default base path
+ * @param array $server server arguments
+ *
+ * @return string
+ *
+ * @throws \Exception if directory isn't usable
+ */
+ public static function create($basePath, $server = [])
+ {
+ if (!$basePath && !empty($server['DOCUMENT_ROOT'])) {
+ $basePath = $server['DOCUMENT_ROOT'];
+ }
+
+ if (!$basePath && !empty($server['PWD'])) {
+ $basePath = $server['PWD'];
+ }
+
+ return self::getRealPath($basePath);
+ }
+
+ /**
+ * @brief Returns a normalized file path
+ *
+ * This is a wrapper for the "realpath" function.
+ * That function cannot detect the real path when some folders aren't readable.
+ * Since this could happen with some hosters we need to handle this.
+ *
+ * @param string $path The path that is about to be normalized
+ * @return string normalized path - when possible
+ */
+ public static function getRealPath($path)
+ {
+ $normalized = realpath($path);
+
+ if (!is_bool($normalized)) {
+ return $normalized;
+ } else {
+ return $path;
+ }
+ }
+}
+++ /dev/null
-<?php
-
-namespace Friendica\Util;
-
-use Friendica\Network\HTTPException\InternalServerErrorException;
-use Friendica\Util\Logger\FriendicaDevelopHandler;
-use Friendica\Util\Logger\FriendicaIntrospectionProcessor;
-use Monolog;
-use Psr\Log\LoggerInterface;
-use Psr\Log\LogLevel;
-
-/**
- * A logger factory
- *
- * Currently only Monolog is supported
- */
-class LoggerFactory
-{
- /**
- * Creates a new PSR-3 compliant logger instances
- *
- * @param string $channel The channel of the logger instance
- *
- * @return LoggerInterface The PSR-3 compliant logger instance
- */
- public static function create($channel)
- {
- $logger = new Monolog\Logger($channel);
- $logger->pushProcessor(new Monolog\Processor\PsrLogMessageProcessor());
- $logger->pushProcessor(new Monolog\Processor\ProcessIdProcessor());
- $logger->pushProcessor(new Monolog\Processor\UidProcessor());
- $logger->pushProcessor(new FriendicaIntrospectionProcessor(LogLevel::DEBUG, ['Friendica\\Core\\Logger']));
-
- return $logger;
- }
-
- /**
- * Creates a new PSR-3 compliant develop logger
- *
- * If you want to debug only interactions from your IP or the IP of a remote server for federation debug,
- * you'll use this logger instance for the duration of your work.
- *
- * It should never get filled during normal usage of Friendica
- *
- * @param string $channel The channel of the logger instance
- * @param string $developerIp The IP of the developer who wants to use the logger
- *
- * @return LoggerInterface The PSR-3 compliant logger instance
- */
- public static function createDev($channel, $developerIp)
- {
- $logger = new Monolog\Logger($channel);
- $logger->pushProcessor(new Monolog\Processor\PsrLogMessageProcessor());
- $logger->pushProcessor(new Monolog\Processor\ProcessIdProcessor());
- $logger->pushProcessor(new Monolog\Processor\UidProcessor());
- $logger->pushProcessor(new FriendicaIntrospectionProcessor(LogLevel::DEBUG, ['Friendica\\Core\\Logger']));
-
-
- $logger->pushHandler(new FriendicaDevelopHandler($developerIp));
-
- return $logger;
- }
-
- /**
- * Adding a handler to a given logger instance
- *
- * @param LoggerInterface $logger The logger instance
- * @param mixed $stream The stream which handles the logger output
- * @param string $level The level, for which this handler at least should handle logging
- *
- * @return void
- *
- * @throws InternalServerErrorException if the logger is incompatible to the logger factory
- * @throws \Exception in case of general failures
- */
- public static function addStreamHandler($logger, $stream, $level = LogLevel::NOTICE)
- {
- if ($logger instanceof Monolog\Logger) {
- $loglevel = Monolog\Logger::toMonologLevel($level);
-
- // fallback to notice if an invalid loglevel is set
- if (!is_int($loglevel)) {
- $loglevel = LogLevel::NOTICE;
- }
- $fileHandler = new Monolog\Handler\StreamHandler($stream, $loglevel);
-
- $formatter = new Monolog\Formatter\LineFormatter("%datetime% %channel% [%level_name%]: %message% %context% %extra%\n");
- $fileHandler->setFormatter($formatter);
-
- $logger->pushHandler($fileHandler);
- } else {
- throw new InternalServerErrorException('Logger instance incompatible for MonologFactory');
- }
- }
-
- /**
- * This method enables the test mode of a given logger
- *
- * @param LoggerInterface $logger The logger
- *
- * @return Monolog\Handler\TestHandler the Handling for tests
- *
- * @throws InternalServerErrorException if the logger is incompatible to the logger factory
- */
- public static function enableTest($logger)
- {
- if ($logger instanceof Monolog\Logger) {
- // disable every handler so far
- $logger->pushHandler(new Monolog\Handler\NullHandler());
-
- // enable the test handler
- $fileHandler = new Monolog\Handler\TestHandler();
- $formatter = new Monolog\Formatter\LineFormatter("%datetime% %channel% [%level_name%]: %message% %context% %extra%\n");
- $fileHandler->setFormatter($formatter);
-
- $logger->pushHandler($fileHandler);
-
- return $fileHandler;
- } else {
- throw new InternalServerErrorException('Logger instance incompatible for MonologFactory');
- }
- }
-}
*/
namespace Friendica\Worker;
+use Friendica\BaseObject;
use Friendica\Core\Update;
-class DBUpdate
+class DBUpdate extends BaseObject
{
public static function execute()
{
- Update::run();
+ Update::run(self::getApp()->getBasePath());
}
}
namespace Friendica\Test;
+use Friendica\Core\Config;
use Friendica\Database\DBA;
+use Friendica\Factory;
+use Friendica\Util\BasePath;
use PHPUnit\DbUnit\DataSet\YamlDataSet;
use PHPUnit\DbUnit\TestCaseTrait;
use PHPUnit_Extensions_Database_DB_IDatabaseConnection;
$this->markTestSkipped('Please set the MYSQL_* environment variables to your test database credentials.');
}
- DBA::connect(getenv('MYSQL_HOST'),
+ $basedir = BasePath::create(dirname(__DIR__));
+ $configLoader = new Config\ConfigCacheLoader($basedir);
+ $config = Factory\ConfigFactory::createCache($configLoader);
+
+ DBA::connect(
+ $config,
+ getenv('MYSQL_HOST'),
getenv('MYSQL_USERNAME'),
getenv('MYSQL_PASSWORD'),
getenv('MYSQL_DATABASE'));
use Friendica\App;
use Friendica\BaseObject;
+use Friendica\Core\Config;
+use Friendica\Core\Config\ConfigCache;
use Friendica\Render\FriendicaSmartyEngine;
use Mockery\MockInterface;
use org\bovigo\vfs\vfsStreamDirectory;
*/
trait AppMockTrait
{
- use ConfigMockTrait;
-
/**
* @var MockInterface|App The mocked Friendica\App
*/
protected $app;
+ /**
+ * @var MockInterface|ConfigCache The mocked Config Cache
+ */
+ protected $configCache;
+
/**
* Mock the App
*
* @param vfsStreamDirectory $root The root directory
+ * @param MockInterface|ConfigCache $config The config cache
*/
- public function mockApp($root)
+ public function mockApp($root, $config)
{
- $this->mockConfigGet('system', 'theme', 'testtheme');
-
+ $this->configCache = $config;
// Mocking App and most used functions
$this->app = \Mockery::mock(App::class);
$this->app
->shouldReceive('getBasePath')
->andReturn($root->url());
- $this->app
- ->shouldReceive('getConfigValue')
+ $config
+ ->shouldReceive('get')
->with('database', 'hostname')
->andReturn(getenv('MYSQL_HOST'));
- $this->app
- ->shouldReceive('getConfigValue')
+ $config
+ ->shouldReceive('get')
->with('database', 'username')
->andReturn(getenv('MYSQL_USERNAME'));
- $this->app
- ->shouldReceive('getConfigValue')
+ $config
+ ->shouldReceive('get')
->with('database', 'password')
->andReturn(getenv('MYSQL_PASSWORD'));
- $this->app
- ->shouldReceive('getConfigValue')
+ $config
+ ->shouldReceive('get')
->with('database', 'database')
->andReturn(getenv('MYSQL_DATABASE'));
+ $config
+ ->shouldReceive('get')
+ ->with('config', 'hostname')
+ ->andReturn('localhost');
+ $config
+ ->shouldReceive('get')
+ ->with('system', 'theme', NULL)
+ ->andReturn('system_theme');
+
+ $this->app
+ ->shouldReceive('getConfig')
+ ->andReturn($config);
+
$this->app
->shouldReceive('getTemplateEngine')
->andReturn(new FriendicaSmartyEngine());
->shouldReceive('getBaseUrl')
->andReturn('http://friendica.local');
+ // Initialize empty Config
+ Config::init($config);
+ $configAdapter = \Mockery::mock('Friendica\Core\Config\IConfigAdapter');
+ $configAdapter
+ ->shouldReceive('isConnected')
+ ->andReturn(false);
+ Config::setAdapter($configAdapter);
+
BaseObject::setApp($this->app);
}
}
+++ /dev/null
-<?php
-
-namespace Friendica\Test\Util;
-
-use Mockery\MockInterface;
-
-/**
- * Trait to Mock Config settings
- */
-trait ConfigMockTrait
-{
- /**
- * @var MockInterface The mocking interface of Friendica\Core\Config
- */
- private $configMock;
-
- /**
- * Mocking a config setting
- *
- * @param string $family The family of the config double
- * @param string $key The key of the config double
- * @param mixed $value The value of the config double
- * @param null|int $times How often the Config will get used
- */
- public function mockConfigGet($family, $key, $value, $times = null)
- {
- if (!isset($this->configMock)) {
- $this->configMock = \Mockery::mock('alias:Friendica\Core\Config');
- }
-
- $this->configMock
- ->shouldReceive('get')
- ->times($times)
- ->with($family, $key)
- ->andReturn($value);
- }
-
- /**
- * Mocking setting a new config entry
- *
- * @param string $family The family of the config double
- * @param string $key The key of the config double
- * @param mixed $value The value of the config double
- * @param null|int $times How often the Config will get used
- * @param bool $return Return value of the set (default is true)
- */
- public function mockConfigSet($family, $key, $value, $times = null, $return = true)
- {
- if (!isset($this->configMock)) {
- $this->configMock = \Mockery::mock('alias:Friendica\Core\Config');
- }
-
- $this->mockConfigGet($family, $key, false, 1);
- if ($return) {
- $this->mockConfigGet($family, $key, $value, 1);
- }
-
- $this->configMock
- ->shouldReceive('set')
- ->times($times)
- ->with($family, $key, $value)
- ->andReturn($return);
- }
-}
<?php
-/**
- * Created by PhpStorm.
- * User: philipp
- * Date: 01.11.18
- * Time: 10:08
- */
namespace Friendica\Test\Util;
-
use Mockery\MockInterface;
trait RendererMockTrait
namespace Friendica\Test;
-use Friendica\BaseObject;
+use Friendica\App;
use Friendica\Core\Config;
use Friendica\Core\PConfig;
use Friendica\Core\Protocol;
use Friendica\Core\System;
+use Friendica\Factory;
use Friendica\Network\HTTPException;
-use Friendica\Util\LoggerFactory;
+use Friendica\Util\BasePath;
use Monolog\Handler\TestHandler;
require_once __DIR__ . '/../../include/api.php';
*/
public function setUp()
{
- parent::setUp();
+ $basedir = BasePath::create(dirname(__DIR__) . '/../');
+ $configLoader = new Config\ConfigCacheLoader($basedir);
+ $config = Factory\ConfigFactory::createCache($configLoader);
+ $logger = Factory\LoggerFactory::create('test', $config);
+ $this->app = new App($config, $logger, false);
+ $this->logOutput = FActory\LoggerFactory::enableTest($this->app->getLogger());
- $this->app = BaseObject::getApp();
- $this->logOutput = LoggerFactory::enableTest($this->app->getLogger());
+ parent::setUp();
// User data that the test database is populated with
$this->selfUser = [
namespace Friendica\Test\src\App;
use Friendica\App\Mode;
+use Friendica\Core\Config;
use Friendica\Test\MockedTest;
-use Friendica\Test\Util\ConfigMockTrait;
use Friendica\Test\Util\DBAMockTrait;
use Friendica\Test\Util\VFSTrait;
-/**
- * @runTestsInSeparateProcesses
- * @preserveGlobalState disabled
- */
class ModeTest extends MockedTest
{
use VFSTrait;
use DBAMockTrait;
- use ConfigMockTrait;
public function setUp()
{
- parent::setUp(); // TODO: Change the autogenerated stub
+ parent::setUp();
$this->setUpVfsDir();
}
$this->assertFalse($mode->has(Mode::LOCALCONFIGPRESENT));
}
+ /**
+ * @runInSeparateProcess
+ * @preserveGlobalState disabled
+ */
public function testWithoutDatabase()
{
$this->mockConnected(false, 1);
$this->assertFalse($mode->has(Mode::DBAVAILABLE));
}
+ /**
+ * @runInSeparateProcess
+ * @preserveGlobalState disabled
+ */
public function testWithoutDatabaseSetup()
{
$this->mockConnected(true, 1);
$this->assertTrue($mode->has(Mode::LOCALCONFIGPRESENT));
}
+ /**
+ * @runInSeparateProcess
+ * @preserveGlobalState disabled
+ */
public function testWithMaintenanceMode()
{
$this->mockConnected(true, 1);
$this->mockFetchFirst('SHOW TABLES LIKE \'config\'', true, 1);
- $this->mockConfigGet('system', 'maintenance', true, 1);
+
+ $config = \Mockery::mock('Friendica\Core\Config\ConfigCache');
+ $config
+ ->shouldReceive('get')
+ ->with('system', 'maintenance', null)
+ ->andReturn(true)
+ ->once();
+ // Initialize empty Config
+ Config::init($config);
+ $configAdapter = \Mockery::mock('Friendica\Core\Config\IConfigAdapter');
+ $configAdapter
+ ->shouldReceive('isConnected')
+ ->andReturn(false);
+ Config::setAdapter($configAdapter);
$mode = new Mode($this->root->url());
$mode->determine();
$this->assertFalse($mode->has(Mode::MAINTENANCEDISABLED));
}
+ /**
+ * @runInSeparateProcess
+ * @preserveGlobalState disabled
+ */
public function testNormalMode()
{
$this->mockConnected(true, 1);
$this->mockFetchFirst('SHOW TABLES LIKE \'config\'', true, 1);
- $this->mockConfigGet('system', 'maintenance', false, 1);
+
+ $config = \Mockery::mock('Friendica\Core\Config\ConfigCache');
+ $config
+ ->shouldReceive('get')
+ ->with('system', 'maintenance', null)
+ ->andReturn(false)
+ ->once();
+ // Initialize empty Config
+ Config::init($config);
+ $configAdapter = \Mockery::mock('Friendica\Core\Config\IConfigAdapter');
+ $configAdapter
+ ->shouldReceive('isConnected')
+ ->andReturn(false);
+ Config::setAdapter($configAdapter);
$mode = new Mode($this->root->url());
$mode->determine();
namespace Friendica\Test;
-use Friendica\App;
use Friendica\BaseObject;
use Friendica\Test\Util\AppMockTrait;
use Friendica\Test\Util\VFSTrait;
/**
* Tests for the BaseObject class.
- * @runTestsInSeparateProcesses
- * @preserveGlobalState disabled
*/
class BaseObjectTest extends TestCase
{
private $baseObject;
/**
- * Create variables used in tests.
+ * Test the setApp() and getApp() function.
+ * @return void
*/
- protected function setUp()
+ public function testGetSetApp()
{
+ $baseObject = new BaseObject();
$this->setUpVfsDir();
- $this->mockApp($this->root);
-
- $this->baseObject = new BaseObject();
- }
+ $configMock = \Mockery::mock('Friendica\Core\Config\ConfigCache');
+ $this->mockApp($this->root, $configMock);
- /**
- * Test the getApp() function.
- * @return void
- */
- public function testGetApp()
- {
- $this->assertInstanceOf(App::class, $this->baseObject->getApp());
+ $this->assertNull($baseObject->setApp($this->app));
+ $this->assertEquals($this->app, $baseObject->getApp());
}
/**
- * Test the setApp() function.
- * @return void
+ * Test the getApp() function without App
+ * @expectedException Friendica\Network\HTTPException\InternalServerErrorException
+ * @runInSeparateProcess
+ * @preserveGlobalState disabled
*/
- public function testSetApp()
+ public function testGetAppFailed()
{
- $this->assertNull($this->baseObject->setApp($this->app));
- $this->assertEquals($this->app, $this->baseObject->getApp());
+ $baseObject = new BaseObject();
+ $baseObject->getApp();
}
}
namespace Friendica\Test\src\Core\Cache;
-
use Friendica\Core\Cache\ArrayCache;
-/**
- * @runTestsInSeparateProcesses
- * @preserveGlobalState disabled
- */
class ArrayCacheDriverTest extends MemoryCacheTest
{
protected function getInstance()
use Friendica\Core\Cache\MemcachedCacheDriver;
use Friendica\Test\MockedTest;
use Friendica\Test\Util\AppMockTrait;
-use Friendica\Test\Util\DateTimeFormatMockTrait;
use Friendica\Test\Util\VFSTrait;
use Friendica\Util\PidFile;
{
use VFSTrait;
use AppMockTrait;
- use DateTimeFormatMockTrait;
/**
* @var int Start time of the mock (used for time operations)
protected function setUp()
{
$this->setUpVfsDir();
- $this->mockApp($this->root);
+ $configMock = \Mockery::mock('Friendica\Core\Config\ConfigCache');
+ $this->mockApp($this->root, $configMock);
$this->app
->shouldReceive('getHostname')
->andReturn('friendica.local');
- $this->mockUtcNow($this->startTime);
-
parent::setUp();
$this->instance = $this->getInstance();
- // Default config
- $this->mockConfigGet('config', 'hostname', 'localhost');
- $this->mockConfigGet('system', 'throttle_limit_day', 100);
- $this->mockConfigGet('system', 'throttle_limit_week', 100);
- $this->mockConfigGet('system', 'throttle_limit_month', 100);
- $this->mockConfigGet('system', 'theme', 'system_theme');
-
$this->instance->clear(false);
}
public function setUp()
{
+ $this->mockUtcNow($this->startTime);
+
$this->mockConnected();
$this->mockConnect();
use Friendica\Core\Cache\CacheDriverFactory;
/**
- * @runTestsInSeparateProcesses
- * @preserveGlobalState disabled
* @requires extension memcache
*/
class MemcacheCacheDriverTest extends MemoryCacheTest
{
protected function getInstance()
{
- $this->mockConfigGet('system', 'memcache_host', 'localhost', 1);
- $this->mockConfigGet('system', 'memcache_port', 11211, 1);
+ $this->configCache
+ ->shouldReceive('get')
+ ->with('system', 'memcache_host', NULL)
+ ->andReturn('localhost');
+
+ $this->configCache
+ ->shouldReceive('get')
+ ->with('system', 'memcache_port', NULL)
+ ->andReturn(11211);
$this->cache = CacheDriverFactory::create('memcache');
return $this->cache;
use Friendica\Core\Cache\CacheDriverFactory;
/**
- * @runTestsInSeparateProcesses
- * @preserveGlobalState disabled
* @requires extension memcached
*/
class MemcachedCacheDriverTest extends MemoryCacheTest
{
protected function getInstance()
{
- $this->mockConfigGet('system', 'memcached_hosts', [0 => 'localhost, 11211']);
+ $this->configCache
+ ->shouldReceive('get')
+ ->with('system', 'memcached_hosts', NULL)
+ ->andReturn([0 => 'localhost, 11211']);
$this->cache = CacheDriverFactory::create('memcached');
return $this->cache;
use Friendica\Core\Cache\CacheDriverFactory;
/**
- * @runTestsInSeparateProcesses
- * @preserveGlobalState disabled
* @requires extension redis
*/
class RedisCacheDriverTest extends MemoryCacheTest
{
protected function getInstance()
{
- $this->mockConfigGet('system', 'redis_host', 'localhost', 1);
- $this->mockConfigGet('system', 'redis_port', null, 1);
+ $this->configCache
+ ->shouldReceive('get')
+ ->with('system', 'redis_host', NULL)
+ ->andReturn('localhost');
+
+ $this->configCache
+ ->shouldReceive('get')
+ ->with('system', 'redis_port', NULL)
+ ->andReturn(null);
$this->cache = CacheDriverFactory::create('redis');
return $this->cache;
$this->db_user = getenv('MYSQL_USERNAME') . getenv('MYSQL_USER');
$this->db_pass = getenv('MYSQL_PASSWORD');
- $this->mockConfigGet('config', 'php_path', false);
+ $this->configCache
+ ->shouldReceive('get')
+ ->with('config', 'php_path', NULL)
+ ->andReturn(false);
$this->mockL10nT();
}
$this->mockConnect(true, 1);
$this->mockConnected(true, 1);
$this->mockExistsTable('user', false, 1);
- $this->mockUpdate([false, true, true], null, 1);
+ $this->mockUpdate([$this->root->url(), false, true, true], null, 1);
$config = <<<CONF
<?php
$this->mockConnect(true, 1);
$this->mockConnected(true, 1);
$this->mockExistsTable('user', false, 1);
- $this->mockUpdate([false, true, true], null, 1);
+ $this->mockUpdate([$this->root->url(), false, true, true], null, 1);
$this->mockGetMarkupTemplate('local.config.tpl', 'testTemplate', 1);
$this->mockReplaceMacros('testTemplate', $this->createArgumentsForMacro(true), '', 1);
$this->mockConnect(true, 1);
$this->mockConnected(true, 1);
$this->mockExistsTable('user', false, 1);
- $this->mockUpdate([false, true, true], null, 1);
+ $this->mockUpdate([$this->root->url(), false, true, true], null, 1);
$this->mockGetMarkupTemplate('local.config.tpl', 'testTemplate', 1);
$this->mockReplaceMacros('testTemplate', $this->createArgumentsForMacro(false), '', 1);
$this->mockConnect(true, 1);
$this->mockConnected(true, 1);
$this->mockExistsTable('user', false, 1);
- $this->mockUpdate([false, true, true], null, 1);
+ $this->mockUpdate([$this->root->url(), false, true, true], null, 1);
$this->mockGetMarkupTemplate('local.config.tpl', 'testTemplate', 1);
$this->mockReplaceMacros('testTemplate', $this->createArgumentsForMacro(true), '', 1);
}
function testSetGetKeyValue() {
- $this->mockConfigSet('config', 'test', 'now', 1);
+ $this->configCache
+ ->shouldReceive('set')
+ ->with('config', 'test', 'now')
+ ->andReturn(true)
+ ->once();
+ $this->configCache
+ ->shouldReceive('get')
+ ->with('config', 'test', NULL)
+ ->andReturn('now')
+ ->twice();
+
$console = new Config($this->consoleArgv);
$console->setArgument(0, 'config');
$console->setArgument(1, 'test');
$txt = $this->dumpExecute($console);
$this->assertEquals("config.test <= now\n", $txt);
- $this->mockConfigGet('config', 'test', 'now', 1);
+ $this->configCache
+ ->shouldReceive('get')
+ ->with('config', 'test', null)
+ ->andReturn('now')
+ ->once();
+
$console = new Config($this->consoleArgv);
$console->setArgument(0, 'config');
$console->setArgument(1, 'test');
$txt = $this->dumpExecute($console);
$this->assertEquals("config.test => now\n", $txt);
- $this->mockConfigGet('config', 'test', null, 1);
+ $this->configCache
+ ->shouldReceive('get')
+ ->with('config', 'test', null)
+ ->andReturn(null)
+ ->once();
+
$console = new Config($this->consoleArgv);
$console->setArgument(0, 'config');
$console->setArgument(1, 'test');
function testSetArrayValue() {
$testArray = [1, 2, 3];
- $this->mockConfigGet('config', 'test', $testArray, 1);
+ $this->configCache
+ ->shouldReceive('get')
+ ->with('config', 'test', null)
+ ->andReturn($testArray)
+ ->once();
$console = new Config($this->consoleArgv);
$console->setArgument(0, 'config');
}
function testVerbose() {
- $this->mockConfigGet('test', 'it', 'now', 1);
+ $this->configCache
+ ->shouldReceive('get')
+ ->with('test', 'it', null)
+ ->andReturn('now')
+ ->once();
$console = new Config($this->consoleArgv);
$console->setArgument(0, 'test');
$console->setArgument(1, 'it');
}
function testUnableToSet() {
- $this->mockConfigSet('test', 'it', 'now', 1, false);
+ $this->configCache
+ ->shouldReceive('set')
+ ->with('test', 'it', 'now')
+ ->andReturn(false)
+ ->once();
+ $this->configCache
+ ->shouldReceive('get')
+ ->with('test', 'it', NULL)
+ ->andReturn(NULL)
+ ->once();
$console = new Config();
$console->setArgument(0, 'test');
$console->setArgument(1, 'it');
Intercept::setUp();
$this->setUpVfsDir();
- $this->mockApp($this->root);
+ $configMock = \Mockery::mock('Friendica\Core\Config\ConfigCache');
+ $this->mockApp($this->root, $configMock);
}
/**
use Friendica\Core\Cache\ArrayCache;
use Friendica\Core\Lock\CacheLockDriver;
-/**
- * @runTestsInSeparateProcesses
- * @preserveGlobalState disabled
- */
class ArrayCacheLockDriverTest extends LockTest
{
protected function getInstance()
protected function setUp()
{
- parent::setUp();
- $this->instance = $this->getInstance();
- $this->instance->releaseAll();
-
// Reusable App object
$this->setUpVfsDir();
- $this->mockApp($this->root);
+ $configMock = \Mockery::mock('Friendica\Core\Config\ConfigCache');
+ $this->mockApp($this->root, $configMock);
$this->app
->shouldReceive('getHostname')
->andReturn('friendica.local');
- // Default config
- $this->mockConfigGet('config', 'hostname', 'localhost');
- $this->mockConfigGet('system', 'throttle_limit_day', 100);
- $this->mockConfigGet('system', 'throttle_limit_week', 100);
- $this->mockConfigGet('system', 'throttle_limit_month', 100);
- $this->mockConfigGet('system', 'theme', 'system_theme');
+ parent::setUp();
+ $this->instance = $this->getInstance();
+ $this->instance->releaseAll();
}
protected function tearDown()
/**
* @requires extension Memcache
- * @runTestsInSeparateProcesses
- * @preserveGlobalState disabled
*/
class MemcacheCacheLockDriverTest extends LockTest
{
protected function getInstance()
{
- $this->mockConfigGet('system', 'memcache_host', 'localhost', 1);
- $this->mockConfigGet('system', 'memcache_port', 11211, 1);
+ $this->configCache
+ ->shouldReceive('get')
+ ->with('system', 'memcache_host', NULL)
+ ->andReturn('localhost');
+
+ $this->configCache
+ ->shouldReceive('get')
+ ->with('system', 'memcache_port', NULL)
+ ->andReturn(11211);
return new CacheLockDriver(CacheDriverFactory::create('memcache'));
}
/**
* @requires extension memcached
- * @runTestsInSeparateProcesses
- * @preserveGlobalState disabled
*/
class MemcachedCacheLockDriverTest extends LockTest
{
protected function getInstance()
{
- $this->mockConfigGet('system', 'memcached_hosts', [0 => 'localhost, 11211']);
+ $this->configCache
+ ->shouldReceive('get')
+ ->with('system', 'memcached_hosts', NULL)
+ ->andReturn([0 => 'localhost, 11211']);
return new CacheLockDriver(CacheDriverFactory::create('memcached'));
}
/**
* @requires extension redis
- * @runTestsInSeparateProcesses
- * @preserveGlobalState disabled
*/
class RedisCacheLockDriverTest extends LockTest
{
protected function getInstance()
{
- $this->mockConfigGet('system', 'redis_host', 'localhost', 1);
- $this->mockConfigGet('system', 'redis_port', null, 1);
+ $this->configCache
+ ->shouldReceive('get')
+ ->with('system', 'redis_host', NULL)
+ ->andReturn('localhost');
+
+ $this->configCache
+ ->shouldReceive('get')
+ ->with('system', 'redis_port', NULL)
+ ->andReturn(null);
return new CacheLockDriver(CacheDriverFactory::create('redis'));
}
use Friendica\Core\Lock\SemaphoreLockDriver;
-/**
- * @runTestsInSeparateProcesses
- * @preserveGlobalState disabled
- */
class SemaphoreLockDriverTest extends LockTest
{
public function setUp()
parent::setUp();
$this->app->shouldReceive('getHostname')->andReturn('friendica.local');
- $this->mockConfigGet('system', 'temppath', '/tmp/');
+
+ $this->configCache
+ ->shouldReceive('get')
+ ->with('system', 'temppath', NULL)
+ ->andReturn('/tmp/');
}
protected function getInstance()
<?php
namespace Friendica\Test\Database;
-use Friendica\BaseObject;
+use Friendica\App;
use Friendica\Core\Config;
use Friendica\Database\DBA;
+use Friendica\Factory;
use Friendica\Test\DatabaseTest;
+use Friendica\Util\BasePath;
class DBATest extends DatabaseTest
{
public function setUp()
{
- parent::setUp();
+ $basedir = BasePath::create(dirname(__DIR__) . '/../../');
+ $configLoader = new Config\ConfigCacheLoader($basedir);
+ $config = Factory\ConfigFactory::createCache($configLoader);
+ $logger = Factory\LoggerFactory::create('test', $config);
+ $this->app = new App($config, $logger, false);
+ $this->logOutput = FActory\LoggerFactory::enableTest($this->app->getLogger());
- // Reusable App object
- $this->app = BaseObject::getApp();
+ parent::setUp();
// Default config
Config::set('config', 'hostname', 'localhost');
namespace Friendica\Test\Database;
-use Friendica\BaseObject;
+use Friendica\App;
use Friendica\Core\Config;
use Friendica\Database\DBStructure;
+use Friendica\Factory;
use Friendica\Test\DatabaseTest;
+use Friendica\Util\BasePath;
class DBStructureTest extends DatabaseTest
{
public function setUp()
{
- parent::setUp();
-
- // Reusable App object
- $this->app = BaseObject::getApp();
+ $basedir = BasePath::create(dirname(__DIR__) . '/../../');
+ $configLoader = new Config\ConfigCacheLoader($basedir);
+ $config = Factory\ConfigFactory::createCache($configLoader);
+ $logger = Factory\LoggerFactory::create('test', $config);
+ $this->app = new App($config, $logger, false);
+ $this->logOutput = FActory\LoggerFactory::enableTest($this->app->getLogger());
- // Default config
- Config::set('config', 'hostname', 'localhost');
- Config::set('system', 'throttle_limit_day', 100);
- Config::set('system', 'throttle_limit_week', 100);
- Config::set('system', 'throttle_limit_month', 100);
- Config::set('system', 'theme', 'system_theme');
+ parent::setUp();
}
/**
--- /dev/null
+/**\r
+ * Copyright (c) 2014 Leonardo Cardoso (http://leocardz.com)\r
+ * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)\r
+ * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.\r
+ * \r
+ * Restructured by Rabzuarus (https://friendica.kommune4.de/profile/rabuzarus)\r
+ * to use it in the decentralized social network Friendica (https://friendi.ca).\r
+ * \r
+ * Version: 1.4.0\r
+ */\r
+(function ($) {\r
+ $.fn.linkPreview = function (options) {\r
+ var opts = jQuery.extend({}, $.fn.linkPreview.defaults, options);\r
+\r
+ var selector = $(this).selector;\r
+ selector = selector.substr(1);\r
+\r
+ var previewTpl = '\\r
+ <div id="preview_' + selector + '" class="preview {0}">\\r
+ {1}\\r
+ <input type="hidden" name="has_attachment" id="hasAttachment_' + selector + '" value="{2}" />\\r
+ <input type="hidden" name="attachment_url" id="attachmentUrl_' + selector + '" value="{3}" />\\r
+ <input type="hidden" name="attachment_type" id="attachmentType_' + selector + '" value="{4}" />\\r
+ </div>';\r
+\r
+ var attachmentTpl = '\\r
+ <hr class="previewseparator">\\r
+ <div id="closePreview_' + selector + '" title="Remove" class="closePreview" >\\r
+ <button type="button" class="previewActionBtn">×</button>\\r
+ </div>\\r
+ <div id="previewImages_' + selector + '" class="previewImages">\\r
+ <div id="previewImgBtn_' + selector + '" class="previewImgBtn">\\r
+ <button type="button" id="previewChangeImg_' + selector + '" class="buttonChangeDeactive previewActionBtn" style="display: none">\\r
+ <i class="fa fa-exchange" aria-hidden="true"></i>\\r
+ </button>\\r
+ </div>\\r
+ <div id="previewImage_' + selector + '" class="previewImage">\\r
+ </div>\\r
+ <input type="hidden" id="photoNumber_' + selector + '" class="photoNumber" value="0" />\\r
+ <input type="hidden" name="attachment_img_src" id="attachmentImageSrc_' + selector + '" value="" />\\r
+ <input type="hidden" name="attachment_img_width" id="attachmentImageWidth_' + selector + '" value="0" />\\r
+ <input type="hidden" name="attachment_img_height" id="attachmentImageHeight_' + selector + '" value="0" />\\r
+ </div>\\r
+ <div id="previewContent_' + selector + '" class="previewContent">\\r
+ <h4 id="previewTitle_' + selector + '" class="previewTitle"></h4>\\r
+ <blockquote id="previewDescription_' + selector + '" class="previewDescription"></blockquote>\\r
+ <div id="hiddenDescription_' + selector + '" class="hiddenDescription"></div>\\r
+ <sup id="previewUrl_' + selector + '" class="previewUrl"></sup>\\r
+ </div>\\r
+ <div class="clear"></div>\\r
+ <hr class="previewseparator">';\r
+ var text;\r
+ var urlRegex = /(https?\:\/\/|\s)[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})(\/+[a-z0-9_.\:\;-]*)*(\?[\&\%\|\+a-z0-9_=,\.\:\;-]*)?([\&\%\|\+&a-z0-9_=,\:\;\.-]*)([\!\#\/\&\%\|\+a-z0-9_=,\:\;\.-]*)}*/i;\r
+ var binurl;\r
+ var block = false;\r
+ var blockTitle = false;\r
+ var blockDescription = false;\r
+ var cache = {};\r
+ var images = "";\r
+ var isExtern = false;\r
+ var photoNumber = 0;\r
+ var firstPosted = false;\r
+ var isActive = false;\r
+ var isCrawling = false;\r
+ var defaultTitle = opts.defaultTitle;\r
+ var defaultDescription = opts.defaultDescription;\r
+\r
+ /**\r
+ * Initialize the plugin\r
+ * \r
+ * @returns {void}\r
+ */\r
+ var init = function() {\r
+ $('#' + selector).bind({\r
+ paste: function () {\r
+ setTimeout(function () {\r
+ crawlText();\r
+ }, 100);\r
+ },\r
+ keyup: function (e) {\r
+ // on enter, space, ctrl\r
+ if ((e.which === 13 || e.which === 32 || e.which === 17)) {\r
+ crawlText();\r
+ }\r
+ }\r
+ });\r
+\r
+ // Check if we have already attachment bbcode in the textarea\r
+ // and add it to the attachment preview.\r
+ var content = $('#' + selector).val();\r
+ addBBCodeToPreview(content);\r
+ };\r
+\r
+ /**\r
+ * Reset some values.\r
+ * \r
+ * @returns {void}\r
+ */\r
+ var resetPreview = function() {\r
+ $('#hasAttachment_' + selector).val(0);\r
+ photoNumber = 0;\r
+ images = "";\r
+ };\r
+\r
+ /**\r
+ * Crawl a text string if it contains an url and try\r
+ * to attach it.\r
+ * \r
+ * If no text is passed to crawlText() we take\r
+ * the previous word before the cursor of the textarea.\r
+ * \r
+ * @param {string} text (optional)\r
+ * @returns {void}\r
+ */\r
+ var crawlText = function (text) {\r
+ block = false;\r
+ images = '';\r
+ isExtern = false;\r
+\r
+ // If no text is passed to crawlText() we \r
+ // take the previous word before the cursor.\r
+ if (typeof text === 'undefined') {\r
+ text = getPrevWord(selector);\r
+ } else {\r
+ isExtern = true;\r
+ }\r
+\r
+ // Don't procces the textarea input if we have already\r
+ // an attachment preview.\r
+ if (!isExtern && isActive) {\r
+ return;\r
+ }\r
+\r
+ if (trim(text) !== "") {\r
+ if (block === false && urlRegex.test(text)) {\r
+ binurl = bin2hex(text);\r
+ block = true;\r
+\r
+ isCrawling = true;\r
+ $('#profile-rotator').show();\r
+\r
+ if (binurl in cache) {\r
+ isCrawling = false;\r
+ processContentData(cache[binurl]);\r
+ } else {\r
+ getContentData(binurl, processContentData);\r
+ }\r
+ }\r
+ }\r
+ };\r
+\r
+ /**\r
+ * Process the attachment data according to\r
+ * its content type (image, audio, video, attachment)\r
+ * \r
+ * @param {object} result\r
+ * @returns {void}\r
+ */\r
+ var processContentData = function(result) {\r
+ if (result.contentType === 'image') {\r
+ insertImage(result.data);\r
+ }\r
+ if (result.contentType === 'audio') {\r
+ insertAudio(result.data);\r
+ }\r
+ if (result.contentType === 'video') {\r
+ insertVideo(result.data);\r
+ }\r
+ if (result.contentType === 'attachment') {\r
+ insertAttachment(result.data);\r
+ }\r
+ $('#profile-rotator').hide();\r
+ };\r
+\r
+ /**\r
+ * Fetch the content of link which should be attached.\r
+ * \r
+ * @param {string} binurl Link which should be attached as hexadecimal string.\r
+ * @param {type} callback\r
+ * @returns {void}\r
+ */\r
+ var getContentData = function(binurl, callback) {\r
+ $.get('parse_url?binurl='+ binurl + '&format=json', function (answer) {\r
+ obj = sanitizeInputData(answer);\r
+\r
+ // Put the data into a cache\r
+ cache[binurl] = obj;\r
+\r
+ callback(obj);\r
+\r
+ isCrawling = false;\r
+ });\r
+ };\r
+\r
+ /*\r
+ * Add a [img] bbtag with the image url to the jot editor.\r
+ * \r
+ * @param {type} data\r
+ * @returns {void}\r
+ */\r
+ var insertImage = function(data) {\r
+ if (!isExtern) {\r
+ return;\r
+ }\r
+ var bbcode = '\n[img]' + data.url + '[/img]\n';\r
+ addeditortext(bbcode);\r
+ };\r
+\r
+ /*\r
+ * Add a [audio] bbtag with the audio url to the jot editor.\r
+ * \r
+ * @param {type} data\r
+ * @returns {void}\r
+ */\r
+ var insertAudio = function(data) {\r
+ if (!isExtern) {\r
+ return;\r
+ }\r
+ var bbcode = '\n[audio]' + data.url + '[/audio]\n';\r
+ addeditortext(bbcode);\r
+ };\r
+\r
+ /*\r
+ * Add a [video] bbtag with the video url to the jot editor.\r
+ * \r
+ * @param {type} data\r
+ * @returns {void}\r
+ */\r
+ var insertVideo = function(data) {\r
+ if (!isExtern) {\r
+ return;\r
+ }\r
+ var bbcode = '\n[video]' + json.url + '[/video]\n';\r
+ addeditortext(bbcode);\r
+ };\r
+\r
+ /**\r
+ * Proccess all attachment data and show up a html\r
+ * attachment preview.\r
+ * \r
+ * @param {obj} data Attachment data.\r
+ * @returns {void}\r
+ */\r
+ var insertAttachment = function(data) {\r
+ // If we have already a preview, leaver here.\r
+ // Note: if we finish the Preview of other media content type,\r
+ // we can move this condition to the beggining of crawlText();\r
+ if (isActive) {\r
+ $('#profile-rotator').hide();\r
+ return;\r
+ }\r
+\r
+ if (data.type !== 'link' && data.type !== 'video' && data.type !== 'photo' || data.url === data.title) {\r
+ $('#profile-rotator').hide();\r
+ return;\r
+ }\r
+\r
+ $('#photoNumber_' + selector).val(0);\r
+ resetPreview();\r
+\r
+ processAttachmentTpl(data, 'type-' + data.type);\r
+ addTitleDescription(data);\r
+ addHostToAttachment(data.url);\r
+ addImagesToAttachment(data.images);\r
+\r
+ processEventListener();\r
+ $('#profile-rotator').hide();\r
+ };\r
+\r
+ /**\r
+ * Construct the attachment html from the attachment template and\r
+ * add it to the DOM.\r
+ * \r
+ * @param {object} data Attachment data.\r
+ * @returns {void}\r
+ */\r
+ var processAttachmentTpl = function(data) {\r
+ // Load and add the template if it isn't allready loaded.\r
+ if ($('#preview_' + selector).length === 0) {\r
+ var tpl = previewTpl.format(\r
+ 'type-' + data.type,\r
+ attachmentTpl,\r
+ 1,\r
+ bin2hex(data.url),\r
+ data.type\r
+ );\r
+ $('#' + selector).after(tpl);\r
+ }\r
+\r
+ isActive = true;\r
+ };\r
+\r
+ /**\r
+ * Add the attachment title and the description\r
+ * to the attachment preview.\r
+ * \r
+ * @param {object} data Attachment data.\r
+ * @returns {void}\r
+ */\r
+ var addTitleDescription = function(data) {\r
+ var description = data.text;\r
+\r
+ if (description === '') {\r
+ description = defaultDescription;\r
+ }\r
+\r
+ $('#previewTitle_' + selector).html("\\r
+ <span id='previewSpanTitle_" + selector + "' class='previewSpanTitle' >" + escapeHTML(data.title) + "</span>\\r
+ <input type='text' name='attachment_title' value='" + escapeHTML(data.title) + "' id='previewInputTitle_" + selector + "' class='previewInputTitle inputPreview' style='display: none;'/>"\r
+ );\r
+\r
+ $('#previewDescription_' + selector).html("\\r
+ <span id='previewSpanDescription_" + selector + "' class='previewSpanDescription' >" + escapeHTML(description) + "</span>\n\\r
+ <textarea id='previewInputDescription_" + selector + "' name='attachment_text' class='previewInputDescription' style='display: none;' class='inputPreview' >" + escapeHTML(data.text) + "</textarea>"\r
+ );\r
+ };\r
+\r
+ /**\r
+ * Add the host to the attachment preview.\r
+ * \r
+ * @param {string} url The url of the link attachment.\r
+ * @returns {void}\r
+ */\r
+ var addHostToAttachment = function(url) {\r
+ if (url) {\r
+ var regexpr = "(https?://)([^:^/]*)(:\\d*)?(.*)?";\r
+ var regResult = url.match(regexpr);\r
+ var urlHost = regResult[1] + regResult[2];\r
+ $('#previewUrl_' + selector).html("<a href='" + url + "'>" + urlHost + "</a>");\r
+ }\r
+ };\r
+\r
+ /**\r
+ * Add preview images to the attachment.\r
+ * \r
+ * @param {array} images\r
+ * \r
+ * @returns {void}\r
+ */\r
+ var addImagesToAttachment = function(images) {\r
+ var imageClass = 'attachment-preview';\r
+ \r
+ if (Array.isArray(images)) {\r
+ $('#previewImages_' + selector).show();\r
+ $('#attachmentImageSrc_' + selector).val(bin2hex(images[photoNumber].src));\r
+ $('#attachmentImageWidth_' + selector).val(images[photoNumber].width);\r
+ $('#attachmentImageHeight_' + selector).val(images[photoNumber].height);\r
+ } else {\r
+ $('#previewImages_' + selector).hide();\r
+ }\r
+\r
+ images.length = parseInt(images.length);\r
+ var appendImage = "";\r
+\r
+ for (i = 0; i < images.length; i++) {\r
+ // For small preview images we use a smaller attachment format.\r
+ ///@todo here we need to add a check for !Config::get('system', 'always_show_preview').\r
+ if (images[i].width >= 500 && images[i].width >= images[i].height) {\r
+ imageClass = 'attachment-image';\r
+ }\r
+\r
+ if (i === 0) {\r
+ appendImage += "<img id='imagePreview_" + selector + "_" + i + "' src='" + images[i].src + "' class='" + imageClass + "' ></img>";\r
+ } else {\r
+ appendImage += "<img id='imagePreview_" + selector + "_" + i + "' src='" + images[i].src + "' class='" + imageClass + "' style='display: none;'></img>";\r
+ }\r
+ }\r
+\r
+ $('#previewImage_' + selector).html(appendImage + "<div id='whiteImage' style='color: transparent; display:none;'>...</div>");\r
+\r
+ // More than just one image.\r
+ if (images.length > 1) {\r
+ // Enable the the button to change the preview pictures.\r
+ $('#previewChangeImg_' + selector).show();\r
+\r
+ if (firstPosted === false) {\r
+ firstPosted = true;\r
+\r
+ $('#previewChangeImg_' + selector).unbind('click').click(function (e) {\r
+ e.stopPropagation();\r
+ if (images.length > 1) {\r
+ $('#imagePreview_' + selector + '_' + photoNumber).css({\r
+ 'display': 'none'\r
+ });\r
+ photoNumber += 1;\r
+\r
+ // If have reached the last image, begin with the first image.\r
+ if (photoNumber === images.length) {\r
+ photoNumber = 0;\r
+ }\r
+\r
+ $('#imagePreview_' + selector + '_' + photoNumber).css({\r
+ 'display': 'block'\r
+ });\r
+ $('#photoNumber_' + selector).val(photoNumber);\r
+ $('#attachmentImageSrc_' + selector).val(bin2hex(images[photoNumber].src));\r
+ $('#attachmentImageWidth_' + selector).val(images[photoNumber].width);\r
+ $('#attachmentImageHeight_' + selector).val(images[photoNumber].height);\r
+ }\r
+ });\r
+ }\r
+ }\r
+ };\r
+\r
+ /**\r
+ * Add event listener to control the attachment preview.\r
+ * \r
+ * @returns {void}\r
+ */\r
+ var processEventListener = function() {\r
+ $('#previewSpanTitle_' + selector).unbind('click').click(function (e) {\r
+ e.stopPropagation();\r
+ if (blockTitle === false) {\r
+ blockTitle = true;\r
+ $('#previewSpanTitle_' + selector).hide();\r
+ $('#previewInputTitle_' + selector).show();\r
+ $('#previewInputTitle_' + selector).val($('#previewInputTitle_' + selector).val());\r
+ $('#previewInputTitle_' + selector).focus().select();\r
+ }\r
+ });\r
+\r
+ $('#previewInputTitle_' + selector).blur(function () {\r
+ blockTitle = false;\r
+ $('#previewSpanTitle_' + selector).html($('#previewInputTitle_' + selector).val());\r
+ $('#previewSpanTitle_' + selector).show();\r
+ $('#previewInputTitle_' + selector).hide();\r
+ });\r
+\r
+ $('#previewInputTitle_' + selector).keypress(function (e) {\r
+ if (e.which === 13) {\r
+ blockTitle = false;\r
+ $('#previewSpanTitle_' + selector).html($('#previewInputTitle_' + selector).val());\r
+ $('#previewSpanTitle_' + selector).show();\r
+ $('#previewInputTitle_' + selector).hide();\r
+ }\r
+ });\r
+\r
+ $('#previewSpanDescription_' + selector).unbind('click').click(function (e) {\r
+ e.stopPropagation();\r
+ if (blockDescription === false) {\r
+ blockDescription = true;\r
+ $('#previewSpanDescription_' + selector).hide();\r
+ $('#previewInputDescription_' + selector).show();\r
+ $('#previewInputDescription_' + selector).val($('#previewInputDescription_' + selector).val());\r
+ $('#previewInputDescription_' + selector).focus().select();\r
+ }\r
+ });\r
+\r
+ $('#previewInputDescription_' + selector).blur(function () {\r
+ blockDescription = false;\r
+ $('#previewSpanDescription_' + selector).html($('#previewInputDescription_' + selector).val());\r
+ $('#previewSpanDescription_' + selector).show();\r
+ $('#previewInputDescription_' + selector).hide();\r
+ });\r
+\r
+ $('#previewInputDescription_' + selector).keypress(function (e) {\r
+ if (e.which === 13) {\r
+ blockDescription = false;\r
+ $('#previewSpanDescription_' + selector).html($('#previewInputDescription_' + selector).val());\r
+ $('#previewSpanDescription_' + selector).show();\r
+ $('#previewInputDescription_' + selector).hide();\r
+ }\r
+ });\r
+\r
+ $('#previewSpanTitle_' + selector).mouseover(function () {\r
+ $('#previewSpanTitle_' + selector).css({\r
+ "background-color": "#ff9"\r
+ });\r
+ });\r
+\r
+ $('#previewSpanTitle_' + selector).mouseout(function () {\r
+ $('#previewSpanTitle_' + selector).css({\r
+ "background-color": "transparent"\r
+ });\r
+ });\r
+\r
+ $('#previewSpanDescription_' + selector).mouseover(function () {\r
+ $('#previewSpanDescription_' + selector).css({\r
+ "background-color": "#ff9"\r
+ });\r
+ });\r
+\r
+ $('#previewSpanDescription_' + selector).mouseout(function () {\r
+ $('#previewSpanDescription_' + selector).css({\r
+ "background-color": "transparent"\r
+ });\r
+ });\r
+\r
+ $('#closePreview_' + selector).unbind('click').click(function (e) {\r
+ e.stopPropagation();\r
+ block = false;\r
+ images = '';\r
+ isActive = false;\r
+ firstPosted = false;\r
+ $('#preview_' + selector).fadeOut("fast", function () {\r
+ $('#preview_' + selector).remove();\r
+ $('#profile-rotator').hide();\r
+ $('#' + selector).focus();\r
+ });\r
+\r
+ });\r
+ };\r
+\r
+ /**\r
+ * Convert attachmant bbcode into an array.\r
+ * \r
+ * @param {string} content Text content with the attachment bbcode.\r
+ * @returns {object || null}\r
+ */\r
+ var getAttachmentData = function(content) {\r
+ var data = {};\r
+\r
+ var match = content.match(/(.*)\[attachment(.*?)\](.*?)\[\/attachment\](.*)/ism);\r
+ if (match === null || match.length < 5) {\r
+ return null;\r
+ }\r
+\r
+ var attributes = match[2];\r
+ data.text = trim(match[1]);\r
+\r
+ var type = '';\r
+ var matches = attributes.match(/type='(.*?)'/ism);\r
+ if (matches !== null && typeof matches[1] !== 'undefined') {\r
+ type = matches[1].toLowerCase();\r
+ }\r
+\r
+ matches = attributes.match(/type="(.*?)"/ism);\r
+ if (matches !== null && typeof matches[1] !== 'undefined') {\r
+ type = matches[1].toLowerCase();\r
+ }\r
+\r
+ if (type === '') {\r
+ return null;\r
+ }\r
+\r
+ if (\r
+ type !== 'link'\r
+ && type !== 'audio'\r
+ && type !== 'photo'\r
+ && type !== 'video')\r
+ {\r
+ return null;\r
+ }\r
+\r
+ if (type !== '') {\r
+ data.type = type;\r
+ }\r
+\r
+ var url = '';\r
+\r
+ matches = attributes.match(/url='(.*?)'/ism);\r
+ if (matches !== null && typeof matches[1] !== 'undefined') {\r
+ url = matches[1].toLowerCase();\r
+ }\r
+\r
+ matches = attributes.match(/url="(.*?)"/ism);\r
+ if (matches !== null && typeof matches[1] !== 'undefined') {\r
+ url = matches[1].toLowerCase();\r
+ }\r
+\r
+ if(url !== '') {\r
+ data.url = escapeHTML(url);\r
+ }\r
+\r
+ var title = '';\r
+\r
+ matches = attributes.match(/title='(.*?)'/ism);\r
+ if (matches !== null && typeof matches[1] !== 'undefined') {\r
+ title = matches[1].toLowerCase();\r
+ }\r
+\r
+ matches = attributes.match(/title="(.*?)"/ism);\r
+ if (matches !== null && typeof matches[1] !== 'undefined') {\r
+ title = matches[1].toLowerCase();\r
+ }\r
+\r
+ if (title !== '') {\r
+ data.title = escapeHTML(title);\r
+ }\r
+\r
+ var image = '';\r
+\r
+ matches = attributes.match(/image='(.*?)'/ism);\r
+ if (matches !== null && typeof matches[1] !== 'undefined') {\r
+ image = matches[1].toLowerCase();\r
+ }\r
+\r
+ matches = attributes.match(/image="(.*?)"/ism);\r
+ if (matches !== null && typeof matches[1] !== 'undefined') {\r
+ image = matches[1].toLowerCase();\r
+ }\r
+\r
+ if (image !== '') {\r
+ data.image = escapeHTML(image);\r
+ }\r
+\r
+ var preview = '';\r
+\r
+ matches = attributes.match(/preview='(.*?)'/ism);\r
+ if (matches !== null && typeof matches[1] !== 'undefined') {\r
+ preview = matches[1].toLowerCase();\r
+ }\r
+\r
+ matches = attributes.match(/preview="(.*?)"/ism);\r
+ if (matches !== null && typeof matches[1] !== 'undefined') {\r
+ preview = matches[1].toLowerCase();\r
+ }\r
+\r
+ if (preview !== '') {\r
+ data.preview = escapeHTML(preview);\r
+ }\r
+\r
+ data.text = trim(match[3]);\r
+ data.after = trim(match[4]);\r
+\r
+ return data;\r
+ };\r
+\r
+ /**\r
+ * Process txt content and if it contains attachment bbcode\r
+ * add it to the attachment preview .\r
+ * \r
+ * @param {string} content\r
+ * @returns {void}\r
+ */\r
+ var addBBCodeToPreview =function(content) {\r
+ var attachmentData = getAttachmentData(content);\r
+ if (attachmentData) {\r
+ reAddAttachment(attachmentData);\r
+ // Remove the attachment bbcode from the textarea.\r
+ var content = content.replace(/\[attachment.*\[\/attachment]/ism, '');\r
+ $('#' + selector).val(content);\r
+ $('#' + selector).focus();\r
+ }\r
+ };\r
+\r
+ /**\r
+ * Add an Attachment with data from an old bbcode\r
+ * generated attachment.\r
+ * \r
+ * @param {object} json The attachment data.\r
+ * @returns {void}\r
+ */\r
+ var reAddAttachment = function(json) {\r
+ if (isActive) {\r
+ $('#profile-rotator').hide();\r
+ return;\r
+ }\r
+\r
+ if (json.type !== 'link' && json.type !== 'video' && json.type !== 'photo' || json.url === json.title) {\r
+ $('#profile-rotator').hide();\r
+ return;\r
+ }\r
+\r
+ var obj = {data: json};\r
+ obj = sanitizeInputData(obj);\r
+\r
+ var data = obj.data;\r
+\r
+ resetPreview();\r
+\r
+ processAttachmentTpl(data);\r
+ addTitleDescription(data);\r
+ addHostToAttachment(data.url);\r
+\r
+ // Since we don't have an array of image data,\r
+ // we need to add the preview images in a different way\r
+ // than in function addImagesToAttachment().\r
+ var imageClass = 'attachment-preview';\r
+ var image = '';\r
+\r
+ if (data.image !== '') {\r
+ imageClass = 'attachment-image';\r
+ image = data.image;\r
+ } else {\r
+ image = data.preview;\r
+ }\r
+\r
+ if (image !== '') {\r
+ var appendImage = "<img id='imagePreview_" + selector + "' src='" + image + "' class='" + imageClass + "' ></img>"\r
+ $('#previewImage_' + selector).html(appendImage);\r
+ $('#attachmentImageSrc_' + selector).val(bin2hex(image));\r
+\r
+ // We need to add the image widht and height when it is \r
+ // loaded.\r
+ $('<img/>' ,{\r
+ load : function(){\r
+ $('#attachmentImageWidth_' + selector).val(this.width);\r
+ $('#attachmentImageHeight_' + selector).val(this.height);\r
+ },\r
+ src : image\r
+ });\r
+ }\r
+\r
+ processEventListener();\r
+ $('#profile-rotator').hide();\r
+ };\r
+\r
+ /**\r
+ * Add missing default properties to the input data object.\r
+ * \r
+ * @param {object} obj Input data.\r
+ * @returns {object}\r
+ */\r
+ var sanitizeInputData = function(obj) {\r
+ if (typeof obj.contentType === 'undefined'\r
+ || obj.contentType === null)\r
+ {\r
+ obj.contentType = "";\r
+ }\r
+ if (typeof obj.data.url === 'undefined'\r
+ || obj.data.url === null)\r
+ {\r
+ obj.data.url = "";\r
+ }\r
+ if (typeof obj.data.title === 'undefined'\r
+ || obj.data.title === null\r
+ || obj.data.title === "")\r
+ {\r
+ obj.data.title = defaultTitle;\r
+ }\r
+ if (typeof obj.data.text === 'undefined'\r
+ || obj.data.text === null\r
+ || obj.data.text === "")\r
+ {\r
+ obj.data.text = "";\r
+ }\r
+ if (typeof obj.data.images === 'undefined'\r
+ || obj.data.images === null)\r
+ {\r
+ obj.data.images = "";\r
+ }\r
+\r
+ if (typeof obj.data.image === 'undefined'\r
+ || obj.data.image === null)\r
+ {\r
+ obj.data.image = "";\r
+ }\r
+\r
+ if (typeof obj.data.preview === 'undefined'\r
+ || obj.data.preview === null)\r
+ {\r
+ obj.data.preview = "";\r
+ }\r
+\r
+ return obj;\r
+ };\r
+\r
+ /**\r
+ * Destroy the plugin.\r
+ * \r
+ * @returns {void}\r
+ */\r
+ var destroy = function() {\r
+ $('#' + selector).unbind();\r
+ $('#preview_' + selector).remove();\r
+ binurl;\r
+ block = false;\r
+ blockTitle = false;\r
+ blockDescription = false;\r
+ cache = {};\r
+ images = "";\r
+ isExtern = false;\r
+ photoNumber = 0;\r
+ firstPosted = false;\r
+ isActive = false;\r
+ isCrawling = false;\r
+ selector = "";\r
+ };\r
+\r
+ var trim = function(str) {\r
+ return str.replace(/^\s+|\s+$/g, "");\r
+ };\r
+ var escapeHTML = function(unsafe_str) {\r
+ return unsafe_str\r
+ .replace(/&/g, '&')\r
+ .replace(/</g, '<')\r
+ .replace(/>/g, '>')\r
+ .replace(/\"/g, '"')\r
+ .replace(/\[/g, '[')\r
+ .replace(/\]/g, ']')\r
+ .replace(/\'/g, '''); // ''' is not valid HTML 4\r
+ };\r
+\r
+ // Initialize LinkPreview \r
+ init();\r
+\r
+ return {\r
+ // make crawlText() accessable from the outside.\r
+ crawlText: function(text) {\r
+ crawlText(text);\r
+ },\r
+ addBBCodeToPreview: function(content) {\r
+ addBBCodeToPreview(content);\r
+ },\r
+ destroy: function() {\r
+ destroy();\r
+ }\r
+ };\r
+ };\r
+\r
+ $.fn.linkPreview.defaults = {\r
+ defaultDescription: "Enter a description",\r
+ defaultTitle: "Enter a title"\r
+ };\r
+\r
+ /**\r
+ * Get in a textarea the previous word before the cursor.\r
+ * \r
+ * @param {object} text Textarea elemet.\r
+ * @param {integer} caretPos Cursor position.\r
+ * \r
+ * @returns {string} Previous word.\r
+ */\r
+ function returnWord(text, caretPos) {\r
+ var index = text.indexOf(caretPos);\r
+ var preText = text.substring(0, caretPos);\r
+ // If the last charachter is a space or enter remove it\r
+ // We need this in friendica for the url preview.\r
+ var lastChar = preText.slice(-1)\r
+ if ( lastChar === " "\r
+ || lastChar === "\n"\r
+ || lastChar === "\r"\r
+ )\r
+ {\r
+ preText = preText.substring(0, preText.length -1);\r
+ }\r
+\r
+ // Replace new line with space.\r
+ preText = preText.replace(/\n/g, " ");\r
+\r
+ if (preText.indexOf(" ") > 0) {\r
+ var words = preText.split(" ");\r
+ return words[words.length - 1]; //return last word\r
+ }\r
+ else {\r
+ return preText;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Get in a textarea the previous word before the cursor.\r
+ * \r
+ * @param {string} id The ID of a textarea element.\r
+ * @returns {sting|null} Previous word or null if no word is available.\r
+ */\r
+ function getPrevWord(id) {\r
+ var text = document.getElementById(id);\r
+ var caretPos = getCaretPosition(text);\r
+ var word = returnWord(text.value, caretPos);\r
+ if (word != null) {\r
+ return word\r
+ }\r
+\r
+ }\r
+\r
+ /**\r
+ * Get the cursor posiotion in an text element.\r
+ * \r
+ * @param {object} ctrl Textarea elemet.\r
+ * @returns {integer} Position of the cursor.\r
+ */\r
+ function getCaretPosition(ctrl) {\r
+ var CaretPos = 0; // IE Support\r
+ if (document.selection) {\r
+ ctrl.focus();\r
+ var Sel = document.selection.createRange();\r
+ Sel.moveStart('character', -ctrl.value.length);\r
+ CaretPos = Sel.text.length;\r
+ }\r
+ // Firefox support\r
+ else if (ctrl.selectionStart || ctrl.selectionStart == '0') {\r
+ CaretPos = ctrl.selectionStart;\r
+ }\r
+ return (CaretPos);\r
+ }\r
+})(jQuery);\r
.jothidden {
/*display: none;*/
}
+.modal #jot-sections {
+ max-height: calc(100vh - 22px);
+}
+@media (min-width: 768px) {
+ .modal #jot-sections {
+ max-height: calc(100vh - 62px);
+ }
+}
+#jot-modal #jot-sections,
+#jot-modal #jot-modal-body,
+#jot-modal #profile-jot-form,
+#jot-modal #profile-jot-wrapper,
+#jot-modal #jot-text-wrap,
+#jot-modal #jot-preview-content,
+#jot-modal #tread-wrapper--1,
+#jot-modal #item-Q0,
+#jot-modal #profile-jot-acl-wrapper,
+#jot-modal #acl-wrapper {
+ overflow: hidden;
+ display: flex;
+ flex: auto;
+ flex-direction: column;
+}
#jot-modal .modal-header a, #jot-modal .modal-header .btn-link,
#profile-jot-submit-wrapper a, #profile-jot-submit-wrapper .btn-link {
color: #555;
}
#jot-text-wrap textarea {
min-height: 100px;
+ overflow-y: auto !important;
+ overflow-y: overlay !important;
+}
+/*#jot-attachment-preview {
+ display: none;
+}*/
+#jot-text-wrap .preview textarea {
+ width: 100%;
+}
+#preview_profile-jot-text {
+ position: relative;
+ padding: 0px 10px;
+ margin-top: -2px;
+ border: 2px solid #ededed;
+ border-top: none;
+ box-shadow: none;
+ border-radius: 0 0 4px 4px;
+ background: #fff;
+ color: #555;
+}
+textarea#profile-jot-text:focus + #preview_profile-jot-text {
+ border: 2px solid #6fdbe8;
+ border-top: none;
+}
+.preview hr.previewseparator {
+ margin-top: 0px;
+ border-color: #D2D2D2;
+}
+#previewImgBtn_profile-jot-text,
+.closePreview {
+ position: absolute;
+ top: 15px;
+}
+.closePreview {
+ right: 15px;
+ z-index: 1;
+}
+.previewImgBtn {
+ left: 15px;
+}
+.preview button.previewActionBtn {
+ display:block;
+ height: 25px;
+ width: 25px;
+ border-radius: 50%;
+ color: #fff;
+ border: 2px solid #fff;
+ box-shadow: 0 0 3px gray;
+ background: #777;
+ text-align: center;
+ line-height: 2px;
+ text-decoration: none;
+ padding: 0 0 1px 1px;
+ opacity: 0.7;
+}
+.preview button.previewActionBtn:hover {
+ opacity: 1;
+}
+.preview .closePreview button.previewActionBtn {
+ font-size: 25px;
+}
+#previewInputTitle_profile-jot-text {
+ width: 100%;
}
#profile-jot-wrapper button#profile-jot-submit {
margin-top: 5px;
#profile-jot-wrapper #character-counter {
padding: 10px 15px;
}
-
+.modal .wall-item-container.preview {
+ overflow-y: auto;
+ overflow-y: overlay;
+}
/* ACL */
/*#jot-modal-body {
height: auto;
overflow-y: hidden;
}*/
#acl-search {
- margin-top: 20px;
+ /*margin-top: 20px;*/
/*padding: 8px;*/
/*border: 1px solid #ccc;*/
width: 100%;
#acl-list {
display: block;
border: 1px solid #ccc;
- overflow: auto;
clear: both;
min-height: 62px;
margin-top: 20px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
+ overflow-y: auto;
}
#acl-list-content {
- overflow-y: auto;
- max-height: calc(100vh - 330px);
+ overflow-y: hidden;
height: auto !important;
}
.acl-list-item {
.wall-item-bottom .label a {
color: #fff;
}
+.wall-item-tags .category,
+.wall-item-tags .folder {
+ margin-right: 3px;
+}
/* item social action buttons */
.wall-item-actions {
--- /dev/null
+// We append the linkPreview to a global Variable to make linkPreview
+// accessable on other places. Note: search on other places before you
+// delete or move the variable.
+var linkPreview;
+
+/**
+ * Insert a link into friendica jot.
+ *
+ * @returns {void}
+ */
+function jotGetLink() {
+ var currentText = $("#profile-jot-text").val();
+ var noAttachment = '';
+ reply = prompt(aStr.linkurl);
+ if(reply && reply.length) {
+ // There should be only one attachment per post.
+ // So we need to remove the old one.
+ $('#jot-attachment-preview').empty();
+ $('#profile-rotator').show();
+ if (currentText.includes("[attachment") && currentText.includes("[/attachment]")) {
+ noAttachment = '&noAttachment=1';
+ }
+
+ // We use the linkPreview library to have a preview
+ // of the attachments.
+ if (typeof linkPreview === 'object') {
+ linkPreview.crawlText(reply + noAttachment);
+
+ // Fallback: insert the attachment bbcode directly into the textarea
+ // if the attachment live preview isn't available
+ } else {
+ $.get('parse_url?binurl=' + bin2hex(reply) + noAttachment, function(data) {
+ addeditortext(data);
+ $('#profile-rotator').hide();
+ });
+ }
+ autosize.update($("#profile-jot-text"));
+ }
+}
$("#jot-content").append(jotcache);
// Clear the jotcache.
jotcache = '';
+ // Destroy the attachment linkPreviw for Jot.
+ if (typeof linkPreview === 'object') {
+ linkPreview.destroy();
+ }
});
// Add Colorbox for viewing Network page images.
modal.show();
$("#jot-popup").show();
+ linkPreview = $('#profile-jot-text').linkPreview();
}
});
}
}
function addCommentText(data, id) {
- // get the textfield
- var textfield = document.getElementById("comment-edit-text-" + id);
- // check if the textfield does have the default-value
- commentOpenUI(textfield, id);
- // save already existent content
- var currentText = $("#comment-edit-text-" + id).val();
- //insert the data as new value
- textfield.value = currentText + data;
- autosize.update($("#comment-edit-text-" + id));
+ // get the textfield
+ var textfield = document.getElementById("comment-edit-text-" + id);
+ // check if the textfield does have the default-value
+ commentOpenUI(textfield, id);
+ // save already existent content
+ var currentText = $("#comment-edit-text-" + id).val();
+ //insert the data as new value
+ textfield.value = currentText + data;
+ autosize.update($("#comment-edit-text-" + id));
}
function commentLinkDrop(event, id) {
- var reply = event.dataTransfer.getData("text/uri-list");
- event.target.textContent = reply;
- event.preventDefault();
- if (reply && reply.length) {
- reply = bin2hex(reply);
- $.get('parse_url?noAttachment=1&binurl=' + reply, function(data) {
+ var reply = event.dataTransfer.getData("text/uri-list");
+ event.target.textContent = reply;
+ event.preventDefault();
+ if (reply && reply.length) {
+ reply = bin2hex(reply);
+ $.get('parse_url?noAttachment=1&binurl=' + reply, function(data) {
addCommentText(data, id);
- });
- }
+ });
+ }
}
function commentLinkDropper(event) {
- var linkFound = event.dataTransfer.types.contains("text/uri-list");
- if (linkFound) {
- event.preventDefault();
- }
+ var linkFound = event.dataTransfer.types.contains("text/uri-list");
+ if (linkFound) {
+ event.preventDefault();
+ }
}
<div id="acl-wrapper">
- <button id="acl-showall" class="btn btn-block btn-default"><i class="fa fa-globe"></i> {{$showall}}</button>
+ <div class="form-group form-group-search">
+ <button id="acl-showall" class="btn btn-block btn-default"><i class="fa fa-globe"></i> {{$showall}}</button>
+ </div>
<div class="form-group form-group-search">
<input type="text" id="acl-search" class="form-control form-search">
</div>
-<script type="text/javascript" src="{{$baseurl}}/view/js/ajaxupload.js" ></script>
+<script type="text/javascript" src="{{$baseurl}}/view/js/ajaxupload.js"></script>
+<script type="text/javascript" src="{{$baseurl}}/view/js/linkPreview.js"></script>
+<script type="text/javascript" src="{{$baseurl}}/view/theme/frio/js/jot.js"></script>
<script type="text/javascript">
var editor = false;
<script type="text/javascript">
var ispublic = '{{$ispublic}}';
+ aStr.linkurl = '{{$linkurl}}';
$(document).ready(function() {
}
}
- function jotGetLink() {
- var currentText = $("#profile-jot-text").val();
- var noAttachment = '';
- reply = prompt("{{$linkurl}}");
- if(reply && reply.length) {
- reply = bin2hex(reply);
- $('#profile-rotator').show();
- if (currentText.includes("[attachment") && currentText.includes("[/attachment]")) {
- noAttachment = '&noAttachment=1';
- }
- $.get('parse_url?binurl=' + reply + noAttachment, function(data) {
- addeditortext(data);
- $('#profile-rotator').hide();
- });
- autosize.update($("#profile-jot-text"));
- }
- }
-
function jotVideoURL() {
reply = prompt("{{$vidurl}}");
if(reply && reply.length) {
}
}
-
function jotGetLocation() {
reply = prompt("{{$whereareu}}", $('#jot-location').val());
if(reply && reply.length) {
}
function itemFiler(id) {
-
var bordercolor = $("input").css("border-color");
$.get('filer/', function(data){
.find('#jot-modal-content')
.append(jotcache)
.modal.show;
+
+ // Jot attachment live preview.
+ linkPreview = $('#profile-jot-text').linkPreview();
}
// Activate the jot text section in the jot modal
{{/if}}
{{foreach $item.folders as $cat}}
- <span class="folder label btn-danger sm">{{$cat.name}}</a>{{if $cat.removeurl}} (<a href="{{$cat.removeurl}}" title="{{$remove}}">x</a>) {{/if}} </span>
+ <span class="folder label btn-danger sm">{{$cat.name}}{{if $cat.removeurl}} (<a href="{{$cat.removeurl}}" title="{{$remove}}">x</a>) {{/if}} </span>
{{/foreach}}
{{foreach $item.categories as $cat}}
- <span class="category label btn-success sm">{{$cat.name}}</a>{{if $cat.removeurl}} (<a href="{{$cat.removeurl}}" title="{{$remove}}">x</a>) {{/if}} </span>
+ <span class="category label btn-success sm">{{$cat.name}}{{if $cat.removeurl}} (<a href="{{$cat.removeurl}}" title="{{$remove}}">x</a>) {{/if}} </span>
{{/foreach}}
</div>
{{if $item.edited}}<div class="itemedited text-muted">{{$item.edited['label']}} (<span title="{{$item.edited['date']}}">{{$item.edited['relative']}}</span>)</div>{{/if}}
{{/if}}
{{foreach $item.folders as $cat}}
- <span class="folder label btn-danger sm"><span class="p-category">{{$cat.name}}</span></a>{{if $cat.removeurl}} (<a href="{{$cat.removeurl}}" title="{{$remove}}">x</a>) {{/if}} </span>
+ <span class="folder label btn-danger sm p-category">{{$cat.name}}{{if $cat.removeurl}} (<a href="{{$cat.removeurl}}" title="{{$remove}}">x</a>) {{/if}} </span>
{{/foreach}}
{{foreach $item.categories as $cat}}
- <span class="category label btn-success sm"><span class="p-category">{{$cat.name}}</span></a>{{if $cat.removeurl}} (<a href="{{$cat.removeurl}}" title="{{$remove}}">x</a>) {{/if}} </span>
+ <span class="category label btn-success sm p-category">{{$cat.name}}{{if $cat.removeurl}} (<a href="{{$cat.removeurl}}" title="{{$remove}}">x</a>) {{/if}} </span>
{{/foreach}}
</div>
{{if $item.edited}}<div class="itemedited text-muted">{{$item.edited['label']}} (<span title="{{$item.edited['date']}}">{{$item.edited['relative']}}</span>)</div>{{/if}}