<?php
|
|
|
|
// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
|
|
//
|
|
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
|
|
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
|
|
// $Id$
|
|
|
|
use Tiki\Package\ComposerManager;
|
|
|
|
class PreferencesLib
|
|
{
|
|
const DEFAULT_HIDDEN_PREFERENCES = [
|
|
'feature_editcss',
|
|
'feature_edit_templates',
|
|
'feature_purifier',
|
|
'smarty_security_functions',
|
|
'smarty_security_modifiers',
|
|
'smarty_security_dirs',
|
|
'tiki_allow_trust_input',
|
|
'feature_create_webhelp',
|
|
];
|
|
|
|
private $data = [];
|
|
private $usageArray;
|
|
private $file = '';
|
|
private $files = [];
|
|
// Fake preferences controlled by the system
|
|
private $system_modified = [ 'tiki_release', 'tiki_version_last_check'];
|
|
// prefs with system info etc
|
|
private $system_info = [ 'fgal_use_dir', 'sender_email' ];
|
|
|
|
/**
|
|
* Returns a list of preferences that can be translated
|
|
*
|
|
* @return array List of preferences
|
|
*/
|
|
public function getTranslatablePreferences()
|
|
{
|
|
global $prefs;
|
|
|
|
// Due to performance reasons and the small list of preferences to be translated, returned the current
|
|
// list of translatable preferences as hardcoded list instead of dynamically searching all preferences
|
|
/*
|
|
$translatablePreferences = [];
|
|
foreach ($prefs as $key => $val) {
|
|
$definition = $this->getPreference($key);
|
|
if ($definition['translatable'] === true) {
|
|
$translatablePreferences[] = $key;
|
|
}
|
|
}
|
|
*/
|
|
|
|
$translatablePreferences = [
|
|
'browsertitle',
|
|
'metatag_keywords',
|
|
'metatag_description',
|
|
];
|
|
|
|
return $translatablePreferences;
|
|
}
|
|
|
|
/**
|
|
* Set the translated value for a given preference
|
|
*
|
|
* @param string $pref preference to translate
|
|
* @param string $lang target language
|
|
* @param string $val value for the preference
|
|
* @param string $defaultLanguage the default language
|
|
*/
|
|
public function setTranslatedPreference($pref, $lang, $val, $defaultLanguage)
|
|
{
|
|
$tikiLib = TikiLib::lib('tiki');
|
|
if ($lang != $defaultLanguage) {
|
|
$pref .= "_" . $lang;
|
|
}
|
|
|
|
if (empty($val)) {
|
|
$tikiLib->delete_preference($pref);
|
|
} else {
|
|
$tikiLib->set_preference($pref, $val);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve a translated preference, in a given language, or the default if not set
|
|
*
|
|
* @param string $name preference name
|
|
* @param string $lang language to retrieve
|
|
* @return string translated preference with fallback for the default preference or empty string
|
|
* @throws Exception
|
|
*/
|
|
public function getTranslatedPreference($name, $lang)
|
|
{
|
|
global $prefs;
|
|
|
|
$translatedPreference = $name;
|
|
if ($prefs['site_language'] != $lang) {
|
|
$translatedPreference .= '_' . $lang;
|
|
}
|
|
|
|
if (isset($prefs[$translatedPreference])) {
|
|
return $prefs[$translatedPreference];
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
public function getPreference($name, $deps = true, $source = null, $get_pages = false)
|
|
{
|
|
global $prefs, $systemConfiguration;
|
|
static $id = 0;
|
|
$data = $this->loadData($name);
|
|
|
|
if (! isset($data[$name])) {
|
|
return false;
|
|
}
|
|
$defaults = [
|
|
'type' => '',
|
|
'helpurl' => '',
|
|
'help' => '',
|
|
'dependencies' => [],
|
|
'packages_required' => [],
|
|
'extensions' => [],
|
|
'dbfeatures' => [],
|
|
'options' => [],
|
|
'description' => '',
|
|
'size' => 40,
|
|
'detail' => '',
|
|
'warning' => '',
|
|
'hint' => '',
|
|
'shorthint' => '',
|
|
'perspective' => true,
|
|
'parameters' => [],
|
|
'admin' => '',
|
|
'module' => '',
|
|
'permission' => '',
|
|
'plugin' => '',
|
|
'view' => '',
|
|
'public' => false,
|
|
'translatable' => false,
|
|
];
|
|
if ($data[$name]['type'] === 'textarea') {
|
|
$defaults['size'] = 10;
|
|
}
|
|
|
|
$info = array_merge($defaults, $data[$name]);
|
|
|
|
if ($source == null) {
|
|
$source = $prefs;
|
|
}
|
|
|
|
$value = isset($source[$name]) ? $source[$name] : null;
|
|
if (
|
|
! empty($value) &&
|
|
is_string($value) &&
|
|
(strlen($value) > 1 && $value[1] == ':' && strpos($value, '{') !== false) &&
|
|
false !== $unserialized = @unserialize($value)
|
|
) {
|
|
$value = $unserialized;
|
|
}
|
|
|
|
$info['preference'] = $name;
|
|
if (isset($info['serialize'])) {
|
|
$fnc = $info['serialize'];
|
|
$info['value'] = $fnc($value);
|
|
} else {
|
|
$info['value'] = $value;
|
|
}
|
|
|
|
if (! isset($info['tags'])) {
|
|
$info['tags'] = ['advanced'];
|
|
}
|
|
|
|
$info['tags'][] = $name;
|
|
$info['tags'][] = 'all';
|
|
|
|
if ($this->checkPreferenceState($info['tags'], 'hide')) {
|
|
return ['hide' => true];
|
|
}
|
|
|
|
$info['notes'] = [];
|
|
|
|
$info['raw'] = isset($source[$name]) ? $source[$name] : null;
|
|
$info['id'] = 'pref-' . ++$id;
|
|
|
|
if (! empty($info['help']) && isset($prefs['feature_help']) && $prefs['feature_help'] == 'y') {
|
|
if (preg_match('/^https?:/i', $info['help'])) {
|
|
$info['helpurl'] = $info['help'];
|
|
} else {
|
|
$info['helpurl'] = $prefs['helpurl'] . $info['help'];
|
|
}
|
|
}
|
|
|
|
/* FIXME: Dependencies are not enforced currently. TODO: Activate disabled code below to enforce dependencies
|
|
// The value element is deprecated. Use either "configuredValue" or "effectiveValue" instead.
|
|
$info['configuredValue'] = $info['effectiveValue'] = $info['value'];
|
|
*/
|
|
if ($deps && isset($info['dependencies'])) {
|
|
$info['dependencies'] = $this->getDependencies($info['dependencies']);
|
|
/* TODO: test
|
|
if ($info['type'] == 'flag' &&
|
|
$info['effectiveValue'] = 'y' && // Optimization
|
|
array_filter(array_column($info['dependencies'], 'met'), function($boolean) {
|
|
return ! $boolean;
|
|
})) {
|
|
$info['effectiveValue'] = 'n';
|
|
}
|
|
*/
|
|
}
|
|
|
|
if ($deps && isset($info['packages_required']) && ! empty($info['packages_required'])) {
|
|
$info['packages_required'] = $this->getPackagesRequired($info['packages_required']);
|
|
}
|
|
|
|
$info['available'] = true;
|
|
|
|
if (! $this->checkExtensions($info['extensions'])) {
|
|
$info['available'] = false;
|
|
$info['notes'][] = tr('Unmatched system requirement. Missing PHP extension among %0', implode(', ', $info['extensions']));
|
|
}
|
|
|
|
if (! $this->checkDatabaseFeatures($info['dbfeatures'])) {
|
|
$info['available'] = false;
|
|
$info['notes'][] = tr('Unmatched system requirement. The database you are using does not support this feature.');
|
|
}
|
|
|
|
if (! isset($info['default'])) { // missing default in prefs definition file?
|
|
$info['modified'] = false;
|
|
trigger_error(tr('Missing default for preference "%0"', $name), E_USER_WARNING);
|
|
} else {
|
|
$info['modified'] = str_replace("\r\n", "\n", $info['value']) != $info['default'];
|
|
}
|
|
|
|
if ($get_pages) {
|
|
$info['pages'] = $this->getPreferenceLocations($name);
|
|
}
|
|
|
|
if (isset($systemConfiguration->preference->$name)) {
|
|
$info['available'] = false;
|
|
$info['notes'][] = tr('Configuration forced by host.');
|
|
}
|
|
|
|
if ($this->checkPreferenceState($info['tags'], 'deny')) {
|
|
$info['available'] = false;
|
|
$info['notes'][] = tr('Disabled by host.');
|
|
}
|
|
|
|
if (! $info['available']) {
|
|
$info['tags'][] = 'unavailable';
|
|
}
|
|
|
|
if ($info['modified'] && $info['available']) {
|
|
$info['tags'][] = 'modified';
|
|
}
|
|
|
|
$info['tagstring'] = implode(' ', $info['tags']);
|
|
|
|
$info = array_merge($defaults, $info);
|
|
|
|
if (! empty($info['permission'])) {
|
|
if (! isset($info['permission']['show_disabled_features'])) {
|
|
$info['permission']['show_disabled_features'] = 'y';
|
|
}
|
|
$info['permission'] = 'tiki-objectpermissions.php?' . http_build_query($info['permission'], '', '&');
|
|
}
|
|
|
|
if (! empty($info['admin'])) {
|
|
if (preg_match('/^\w+$/', $info['admin'])) {
|
|
$info['admin'] = 'tiki-admin.php?page=' . urlencode($info['admin']);
|
|
}
|
|
}
|
|
|
|
if (! empty($info['module'])) {
|
|
$info['module'] = 'tiki-admin_modules.php?cookietab=3&textFilter=' . urlencode($info['module']);
|
|
}
|
|
|
|
if (! empty($info['plugin'])) {
|
|
$info['plugin'] = 'tiki-admin.php?page=textarea&cookietab=2&textFilter=' . urlencode($info['plugin']);
|
|
}
|
|
|
|
$smarty = TikiLib::lib('smarty');
|
|
$smarty->loadPlugin('smarty_function_icon');
|
|
|
|
if (! empty($info['admin']) || ! empty($info['permission']) || ! empty($info['view']) || ! empty($info['module']) || ! empty($info['plugin'])) {
|
|
$info['popup_html'] = '<ul class="list-unstyled">';
|
|
|
|
if (! empty($info['admin'])) {
|
|
$icon = smarty_function_icon([ 'name' => 'settings'], $smarty->getEmptyInternalTemplate());
|
|
$info['popup_html'] .= '<li><a class="icon" href="' . $info['admin'] . '">' . $icon . ' ' . tra('Settings') . '</a></li>';
|
|
}
|
|
if (! empty($info['permission'])) {
|
|
$icon = smarty_function_icon([ 'name' => 'permission'], $smarty->getEmptyInternalTemplate());
|
|
$info['popup_html'] .= '<li><a class="icon" href="' . $info['permission'] . '">' . $icon . ' ' . tra('Permissions') . '</a></li>';
|
|
}
|
|
if (! empty($info['view'])) {
|
|
$icon = smarty_function_icon([ 'name' => 'view'], $smarty->getEmptyInternalTemplate());
|
|
$info['popup_html'] .= '<li><a class="icon" href="' . $info['view'] . '">' . $icon . ' ' . tra('View') . '</a></li>';
|
|
}
|
|
if (! empty($info['module'])) {
|
|
$icon = smarty_function_icon([ 'name' => 'module'], $smarty->getEmptyInternalTemplate());
|
|
$info['popup_html'] .= '<li><a class="icon" href="' . $info['module'] . '">' . $icon . ' ' . tra('Modules') . '</a></li>';
|
|
}
|
|
if (! empty($info['plugin'])) {
|
|
$icon = smarty_function_icon([ 'name' => 'plugin'], $smarty->getEmptyInternalTemplate());
|
|
$info['popup_html'] .= '<li><a class="icon" href="' . $info['plugin'] . '">' . $icon . ' ' . tra('Plugins') . '</a></li>';
|
|
}
|
|
$info['popup_html'] .= '</ul>';
|
|
}
|
|
|
|
if (isset($prefs['connect_feature']) && $prefs['connect_feature'] === 'y') {
|
|
$connectlib = TikiLib::lib('connect');
|
|
$currentVote = $connectlib->getVote($info['preference']);
|
|
|
|
$info['voting_html'] = '';
|
|
|
|
if (! in_array('like', $currentVote)) {
|
|
$info['voting_html'] .= smarty_function_icon($this->getVoteIconParams($info['preference'], 'like', tra('Like')), $smarty->getEmptyInternalTemplate());
|
|
} else {
|
|
$info['voting_html'] .= smarty_function_icon($this->getVoteIconParams($info['preference'], 'unlike', tra("Don't like")), $smarty->getEmptyInternalTemplate());
|
|
}
|
|
// if (!in_array('fix', $currentVote)) {
|
|
// $info['voting_html'] .= smarty_function_icon($this->getVoteIconParams($info['preference'], 'fix', tra('Fix me')), $smarty);
|
|
// } else {
|
|
// $info['voting_html'] .= smarty_function_icon($this->getVoteIconParams($info['preference'], 'unfix', tra("Don't fix me")), $smarty);
|
|
// }
|
|
// if (!in_array('wtf', $currentVote)) {
|
|
// $info['voting_html'] .= smarty_function_icon($this->getVoteIconParams($info['preference'], 'wtf', tra("What's this for?")), $smarty);
|
|
// } else {
|
|
// $info['voting_html'] .= smarty_function_icon($this->getVoteIconParams($info['preference'], 'unwtf', tra("What's this for?")), $smarty);
|
|
// }
|
|
}
|
|
|
|
if (! $info['available']) {
|
|
$info['parameters']['disabled'] = 'disabled';
|
|
}
|
|
|
|
$info['params'] = '';
|
|
if (! empty($info['parameters'])) {
|
|
foreach ($info['parameters'] as $param => $value) {
|
|
$info['params'] .= ' ' . $param . '="' . $value . '"';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If the unified index is enabled, replace simple object selection preferences with object selectors
|
|
*/
|
|
if ($info['type'] == 'text' && ! empty($info['profile_reference']) && $prefs['feature_search'] == 'y') {
|
|
$objectlib = TikiLib::lib('object');
|
|
$type = $objectlib->getSelectorType($info['profile_reference']);
|
|
|
|
if ($type) {
|
|
$info['selector_type'] = $type;
|
|
|
|
if (empty($info['separator'])) {
|
|
$info['type'] = 'selector';
|
|
} else {
|
|
$info['type'] = 'multiselector';
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (['name', 'preference'] as $key) {
|
|
if (empty($info[$key])) {
|
|
trigger_error(tr('Missing preference "%0" for "%1"', $key, $name));
|
|
}
|
|
}
|
|
|
|
return $info;
|
|
}
|
|
|
|
private function getVoteIconParams($pref, $vote, $label)
|
|
{
|
|
$iconname = [
|
|
'like' => 'thumbs-up',
|
|
'unlike' => 'thumbs-down'
|
|
];
|
|
return [
|
|
'name' => $iconname[$vote],
|
|
'title' => $label,
|
|
'href' => '#', 'onclick' => 'connectVote(\'' . $pref . '\', \'' . $vote . '\', this);return false;',
|
|
'class' => '',
|
|
'iclass' => 'icon connectVoter',
|
|
'istyle' => 'display:none',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Check preference state
|
|
* @param $tags
|
|
* @param $state
|
|
* @return bool
|
|
*/
|
|
private function checkPreferenceState($tags, $state)
|
|
{
|
|
static $rules = null;
|
|
|
|
if (is_null($rules)) {
|
|
global $systemConfiguration;
|
|
$rules = array_merge($this->getDefaultSystemPreferences(), $systemConfiguration->rules->toArray());
|
|
krsort($rules, SORT_NUMERIC);
|
|
|
|
foreach ($rules as &$rule) {
|
|
$parts = explode(' ', $rule);
|
|
$type = array_shift($parts);
|
|
$rule = [$type, $parts];
|
|
}
|
|
|
|
unset($rule);
|
|
}
|
|
|
|
|
|
foreach ($rules as $rule) {
|
|
$intersect = array_intersect($rule[1], $tags);
|
|
|
|
if (count($intersect)) {
|
|
return $rule[0] == $state;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private function checkExtensions($extensions)
|
|
{
|
|
if (count($extensions) == 0) {
|
|
return true;
|
|
}
|
|
|
|
$installed = get_loaded_extensions();
|
|
|
|
foreach ($extensions as $ext) {
|
|
if (! in_array($ext, $installed)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private function checkDatabaseFeatures($features)
|
|
{
|
|
if (in_array('mysql_fulltext', $features)) {
|
|
return TikiDb::get()->isMySQLFulltextSearchSupported();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Unset hidden preferences based on the configuration file settings
|
|
* @param $preferences
|
|
* @return array
|
|
*/
|
|
public function unsetHiddenPreferences($preferences)
|
|
{
|
|
if (empty($preferences)) {
|
|
return [];
|
|
}
|
|
|
|
foreach ($preferences as $key => $preference) {
|
|
$preferenceInfo = $this->getPreference($preference);
|
|
|
|
if (isset($preferenceInfo['hide']) && $preferenceInfo['hide'] === true) {
|
|
unset($preferences[$key]);
|
|
}
|
|
}
|
|
|
|
return $preferences;
|
|
}
|
|
|
|
public function getMatchingPreferences($criteria, $filters = null, $maxRecords = 50, $sort = '')
|
|
{
|
|
$index = $this->getIndex();
|
|
|
|
$query = new Search_Query($criteria);
|
|
$query->setCount($maxRecords);
|
|
|
|
if ($sort) {
|
|
$query->setOrder($sort);
|
|
}
|
|
if ($filters) {
|
|
$this->buildPreferenceFilter($query, $filters);
|
|
}
|
|
$results = $query->search($index);
|
|
|
|
$prefs = [];
|
|
foreach ($results as $hit) {
|
|
$prefs[] = $hit['object_id'];
|
|
}
|
|
|
|
return $prefs;
|
|
}
|
|
|
|
/**
|
|
* @param $handled
|
|
* @param $data
|
|
* @param null $limitation
|
|
*
|
|
* @return array
|
|
*/
|
|
|
|
public function applyChanges($handled, $data, $limitation = null)
|
|
{
|
|
global $user_overrider_prefs;
|
|
$tikilib = TikiLib::lib('tiki');
|
|
|
|
if (is_array($limitation)) {
|
|
$handled = array_intersect($handled, $limitation);
|
|
}
|
|
|
|
$resets = isset($data['lm_reset']) ? (array) $data['lm_reset'] : [];
|
|
|
|
$changes = [];
|
|
foreach ($handled as $pref) {
|
|
if (in_array($pref, $resets)) {
|
|
$tikilib->delete_preference($pref);
|
|
$changes[$pref] = ['type' => 'reset'];
|
|
} else {
|
|
$value = $this->formatPreference($pref, $data);
|
|
$realPref = in_array($pref, $user_overrider_prefs) ? "site_$pref" : $pref;
|
|
$old = $this->formatPreference($pref, [$pref => $tikilib->get_preference($realPref)]);
|
|
if ($old != $value) {
|
|
if ($tikilib->set_preference($pref, $value)) {
|
|
$changes[$pref] = ['type' => 'changed', 'new' => $value, 'old' => $old];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $changes;
|
|
}
|
|
|
|
public function formatPreference($pref, $data)
|
|
{
|
|
$info = $this->getPreference($pref);
|
|
|
|
if (false !== $info) {
|
|
if (empty($info['type'])) {
|
|
$info['type'] = 'text';
|
|
Feedback::error(tr('Preference %0 has no type set', $pref));
|
|
}
|
|
$function = '_get' . ucfirst($info['type']) . 'Value';
|
|
$value = $this->$function($info, $data);
|
|
return $value;
|
|
} else {
|
|
if (isset($data[$pref])) {
|
|
return $data[$pref];
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public function getInput(JitFilter $filter, $preferences = [], $environment = '')
|
|
{
|
|
$out = [];
|
|
|
|
foreach ($preferences as $name) {
|
|
$info = $this->getPreference($name);
|
|
|
|
if ($environment == 'perspective' && isset($info['perspective']) && $info['perspective'] === false) {
|
|
continue;
|
|
}
|
|
|
|
if (isset($info['filter'])) {
|
|
$filter->replaceFilter($name, $info['filter']);
|
|
}
|
|
|
|
if (isset($info['separator'])) {
|
|
$out[ $name ] = $filter->asArray($name, $info['separator']);
|
|
} else {
|
|
$out[ $name ] = $filter[$name];
|
|
}
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
public function getExtraSortColumns()
|
|
{
|
|
global $prefs;
|
|
if (isset($prefs['rating_advanced']) && $prefs['rating_advanced'] == 'y') {
|
|
return TikiDb::get()->fetchMap("SELECT CONCAT('adv_rating_', ratingConfigId), name FROM tiki_rating_configs");
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
private function loadData($name)
|
|
{
|
|
if (in_array($name, $this->system_modified)) {
|
|
return null;
|
|
}
|
|
if (substr($name, 0, 3) == 'tp_') {
|
|
$midpos = strpos($name, '__', 3);
|
|
$pos = strpos($name, '__', $midpos + 2);
|
|
$file = substr($name, 0, $pos);
|
|
} elseif (substr($name, 0, 7) == 'themes_') {
|
|
$pos = strpos($name, '_', 7 + 1);
|
|
$file = substr($name, 0, $pos);
|
|
} elseif (false !== $pos = strpos($name, '_')) {
|
|
$file = substr($name, 0, $pos);
|
|
} elseif (file_exists(__DIR__ . "/prefs/{$name}.php")) {
|
|
$file = $name;
|
|
} else {
|
|
$file = 'global';
|
|
}
|
|
|
|
return $this->getFileData($file);
|
|
}
|
|
|
|
private function getFileData($file, $partial = false)
|
|
{
|
|
if (! isset($this->files[$file])) {
|
|
$this->realLoad($file, $partial);
|
|
}
|
|
|
|
$ret = [];
|
|
if (isset($this->files[$file])) {
|
|
$ret = $this->files[$file];
|
|
}
|
|
|
|
if ($partial) {
|
|
unset($this->files[$file]);
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
private function realLoad($file, $partial)
|
|
{
|
|
$inc_file = __DIR__ . "/prefs/{$file}.php";
|
|
if (substr($file, 0, 3) == "tp_") {
|
|
$paths = \Tiki\Package\ExtensionManager::getPaths();
|
|
$package = str_replace('__', '/', substr($file, 3));
|
|
$inc_file = $paths[$package] . "/prefs/{$file}.php";
|
|
}
|
|
if (preg_match('/^themes_(.*)$/', $file, $matches)) {
|
|
$themeName = $matches[1];
|
|
$themePath = TikiLib::lib('theme')->get_theme_path($themeName);
|
|
$inc_file = $themePath . "prefs/{$file}.php";
|
|
}
|
|
if (file_exists($inc_file)) {
|
|
require_once $inc_file;
|
|
$function = "prefs_{$file}_list";
|
|
if (function_exists($function)) {
|
|
$this->files[$file] = $function($partial);
|
|
} else {
|
|
$this->files[$file] = [];
|
|
}
|
|
}
|
|
}
|
|
|
|
private function getDependencies($dependencies)
|
|
{
|
|
$out = [];
|
|
|
|
foreach ((array) $dependencies as $key => $dep) {
|
|
$info = $this->getPreference($dep, false);
|
|
if ($info) {
|
|
$out[] = [
|
|
'name' => $dep,
|
|
'label' => $info['name'],
|
|
'type' => $info['type'],
|
|
'link' => 'tiki-admin.php?lm_criteria=' . urlencode($info['name']),
|
|
'met' =>
|
|
( $info['type'] == 'flag' && $info['value'] == 'y' )
|
|
|| ( $info['type'] != 'flag' && ! empty($info['value']) )
|
|
];
|
|
} elseif ($key === 'profiles') {
|
|
foreach ((array) $dep as $profile) {
|
|
$out[] = [
|
|
'name' => $profile,
|
|
'label' => $profile,
|
|
'type' => 'profile',
|
|
'link' => 'tiki-admin.php?page=profiles&list=List&profile=' . urlencode($profile),
|
|
'met' => // FIXME: $info is false, the following surely won't behave as intended. This should indicate whether the profile was applied.
|
|
( $info['type'] == 'flag' && $info['value'] == 'y' )
|
|
|| ( $info['type'] != 'flag' && ! empty($info['value']) )
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
private function getPackagesRequired($packages)
|
|
{
|
|
$out = [];
|
|
|
|
foreach ((array) $packages as $key => $dep) {
|
|
$met = class_exists($dep) || file_exists($dep);
|
|
|
|
$package = [
|
|
'name' => $key,
|
|
'label' => $key,
|
|
'type' => 'composer',
|
|
'link' => 'tiki-admin.php?page=packages',
|
|
'met' => $met
|
|
];
|
|
|
|
if ($packageInfo = ComposerManager::getPackageInfo($key)) {
|
|
$package['name'] = $packageInfo['name'];
|
|
$package['label'] = $packageInfo['name'];
|
|
|
|
if (! empty($packageInfo['link'])) {
|
|
$package['link'] = $packageInfo['link'];
|
|
}
|
|
}
|
|
|
|
$out[] = $package;
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* @param bool $fallback Rebuild fallback search index
|
|
* @return Search_Index_Interface|null
|
|
* @throws Exception
|
|
*/
|
|
public function rebuildIndex($fallback = false)
|
|
{
|
|
global $prefs;
|
|
|
|
$index = TikiLib::lib('unifiedsearch')->getIndex('preference', ! $fallback);
|
|
$index->destroy();
|
|
|
|
$typeFactory = $index->getTypeFactory();
|
|
|
|
$indexed = [];
|
|
|
|
foreach ($this->getAvailableFiles() as $file) {
|
|
$data = $this->getFileData($file);
|
|
|
|
foreach ($data as $pref => $info) {
|
|
$prefInfo = $this->getPreference($pref);
|
|
if (! empty($prefInfo['hide'])) {
|
|
continue; // hidden prefs have had their info removed, so no point indexing them
|
|
}
|
|
if ($prefInfo) {
|
|
$info = $prefInfo;
|
|
} else {
|
|
$info['preference'] = $pref;
|
|
if (empty($info['tags'])) {
|
|
$info['tags'] = ['missing'];
|
|
}
|
|
}
|
|
$doc = $this->indexPreference($typeFactory, $pref, $info);
|
|
$index->addDocument($doc);
|
|
|
|
$indexed[] = $pref;
|
|
}
|
|
}
|
|
|
|
// Rebuild fallback index
|
|
list($fallbackEngine) = TikiLib::lib('unifiedsearch')->getFallbackEngineDetails();
|
|
if (! $fallback && $fallbackEngine) {
|
|
$defaultEngine = $prefs['unified_engine'];
|
|
$prefs['unified_engine'] = $fallbackEngine;
|
|
$this->rebuildIndex(true);
|
|
$prefs['unified_engine'] = $defaultEngine;
|
|
}
|
|
|
|
return $index;
|
|
}
|
|
|
|
private function getIndex()
|
|
{
|
|
$index = TikiLib::lib('unifiedsearch')->getIndex('preference');
|
|
|
|
if (! $index->exists()) {
|
|
$index = null;
|
|
return $this->rebuildIndex();
|
|
}
|
|
|
|
return $index;
|
|
}
|
|
|
|
public function indexNeedsRebuilding()
|
|
{
|
|
$index = TikiLib::lib('unifiedsearch')->getIndex('preference');
|
|
return ! $index->exists();
|
|
}
|
|
|
|
public function getPreferenceLocations($name)
|
|
{
|
|
if (! $this->usageArray) {
|
|
$this->loadPreferenceLocations();
|
|
}
|
|
|
|
$pages = [];
|
|
foreach ($this->usageArray as $pg => $pfs) {
|
|
foreach ($pfs as $pf) {
|
|
if ($pf[0] == $name) {
|
|
$pages[] = [$pg, $pf[1]];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (strpos($name, 'wikiplugin_') === 0 || strpos($name, 'wikiplugininline_') === 0) {
|
|
$pages[] = ['textarea', 2]; // plugins are included in textarea admin dynamically
|
|
}
|
|
if (strpos($name, 'trackerfield_') === 0) {
|
|
$pages[] = ['trackers', 3]; // trackerfields are also included in tracker admin dynamically
|
|
}
|
|
|
|
return $pages;
|
|
}
|
|
|
|
private function loadPreferenceLocations()
|
|
{
|
|
global $prefs;
|
|
|
|
// check for or create array of where each pref is used
|
|
$file = 'temp/cache/preference-usage-index';
|
|
if (! file_exists($file)) {
|
|
$prefs_usage_array = [];
|
|
$fp = opendir('templates/admin/');
|
|
|
|
while (false !== ($f = readdir($fp))) {
|
|
preg_match('/^include_(.*)\.tpl$/', $f, $m);
|
|
if (count($m) > 0) {
|
|
$page = $m[1];
|
|
$c = file_get_contents('templates/admin/' . $f);
|
|
preg_match_all('/{preference.*name=[\'"]?(\w*)[\'"]?.*}/i', $c, $m2, PREG_OFFSET_CAPTURE);
|
|
if (count($m2[1]) > 0) {
|
|
// count number of tabs in front of each found pref
|
|
foreach ($m2[1] as & $found) {
|
|
$tabs = preg_match_all('/{\/tab}/i', substr($c, 0, $found[1]), $m3);
|
|
if ($tabs === false) {
|
|
$tabs = 0;
|
|
} else {
|
|
$tabs++;
|
|
}
|
|
if ($prefs['site_layout'] !== 'classic' && $page === 'look' && $tabs > 2) {
|
|
$tabs--; // hidden tab #3 for shadow layers
|
|
}
|
|
$found[1] = $tabs; // replace char offset with tab number
|
|
}
|
|
$prefs_usage_array[$page] = $m2[1];
|
|
}
|
|
}
|
|
}
|
|
file_put_contents($file, serialize($prefs_usage_array));
|
|
} else {
|
|
$prefs_usage_array = unserialize(file_get_contents($file));
|
|
}
|
|
|
|
$this->usageArray = $prefs_usage_array;
|
|
}
|
|
|
|
private function indexPreference($typeFactory, $pref, $info)
|
|
{
|
|
$contents = [
|
|
$info['preference'],
|
|
// also index the parts of the pref name individually, e.g. wikiplugin_plugin_name as wikiplugin plugin name
|
|
str_replace('_', ' ', $info['preference']),
|
|
$info['name'],
|
|
isset($info['description']) ? $info['description'] : '',
|
|
isset($info['keywords']) ? $info['keywords'] : '',
|
|
];
|
|
|
|
if (isset($info['options'])) {
|
|
$contents = array_merge($contents, $info['options']);
|
|
}
|
|
|
|
return [
|
|
'object_type' => $typeFactory->identifier('preference'),
|
|
'object_id' => $typeFactory->identifier($pref),
|
|
'contents' => $typeFactory->plaintext(implode(' ', $contents)),
|
|
'tags' => $typeFactory->plaintext(implode(' ', $info['tags'])),
|
|
];
|
|
}
|
|
|
|
private function _getFlagValue($info, $data)
|
|
{
|
|
$name = $info['preference'];
|
|
if (isset($data[$name]) && ! empty($data[$name]) && $data[$name] != 'n') {
|
|
$ret = 'y';
|
|
} else {
|
|
$ret = 'n';
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
private function _getSelectorValue($info, $data)
|
|
{
|
|
$name = $info['preference'];
|
|
if (! empty($data[$name])) {
|
|
$value = $data[$name];
|
|
|
|
if (isset($info['filter']) && $filter = TikiFilter::get($info['filter'])) {
|
|
return $filter->filter($value);
|
|
} else {
|
|
return $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
private function _getMultiselectorValue($info, $data)
|
|
{
|
|
$name = $info['preference'];
|
|
|
|
if (isset($data[$name]) && ! empty($data[$name])) {
|
|
if (! is_array($data[$name])) {
|
|
$value = explode($info['separator'], $data[$name]);
|
|
} else {
|
|
$value = $data[$name];
|
|
}
|
|
} else {
|
|
$value = [];
|
|
}
|
|
|
|
if (isset($info['filter']) && $filter = TikiFilter::get($info['filter'])) {
|
|
return array_map([ $filter, 'filter' ], $value);
|
|
} else {
|
|
return $value;
|
|
}
|
|
}
|
|
|
|
private function _getTextValue($info, $data)
|
|
{
|
|
$name = $info['preference'];
|
|
|
|
if (isset($info['separator']) && is_string($data[$name])) {
|
|
if (! empty($data[$name])) {
|
|
$value = explode($info['separator'], $data[$name]);
|
|
} else {
|
|
$value = [];
|
|
}
|
|
} else {
|
|
$value = $data[$name];
|
|
}
|
|
|
|
if (isset($info['filter']) && $filter = TikiFilter::get($info['filter'])) {
|
|
if (is_array($value)) {
|
|
$value = array_map([ $filter, 'filter' ], $value);
|
|
} else {
|
|
$value = $filter->filter($value);
|
|
}
|
|
}
|
|
return $this->applyConstraints($info, $value);
|
|
}
|
|
|
|
private function _getPasswordValue($info, $data)
|
|
{
|
|
$name = $info['preference'];
|
|
|
|
if (isset($info['filter']) && $filter = TikiFilter::get($info['filter'])) {
|
|
return $filter->filter($data[$name]);
|
|
} else {
|
|
return $data[$name];
|
|
}
|
|
}
|
|
|
|
private function _getTextareaValue($info, $data)
|
|
{
|
|
$name = $info['preference'];
|
|
|
|
if (isset($info['filter']) && $filter = TikiFilter::get($info['filter'])) {
|
|
$value = $filter->filter($data[$name]);
|
|
} else {
|
|
$value = $data[$name];
|
|
}
|
|
$value = str_replace("\r", "", $value);
|
|
|
|
if (isset($info['unserialize'])) {
|
|
$fnc = $info['unserialize'];
|
|
|
|
return $fnc($value);
|
|
} else {
|
|
return $value;
|
|
}
|
|
}
|
|
|
|
private function _getListValue($info, $data)
|
|
{
|
|
$name = $info['preference'];
|
|
$value = isset($data[$name]) ? $data[$name] : null;
|
|
|
|
$options = $info['options'];
|
|
|
|
if (isset($options[$value])) {
|
|
return $value;
|
|
} else {
|
|
$keys = array_keys($options);
|
|
return reset($keys);
|
|
}
|
|
}
|
|
|
|
private function _getMultilistValue($info, $data)
|
|
{
|
|
$name = $info['preference'];
|
|
$value = isset($data[$name]) ? (array) $data[$name] : [];
|
|
|
|
$options = $info['options'];
|
|
$options = array_keys($options);
|
|
|
|
return array_intersect($value, $options);
|
|
}
|
|
|
|
private function _getRadioValue($info, $data)
|
|
{
|
|
$name = $info['preference'];
|
|
$value = isset($data[$name]) ? $data[$name] : null;
|
|
|
|
$options = $info['options'];
|
|
$options = array_keys($options);
|
|
|
|
if (in_array($value, $options)) {
|
|
return $value;
|
|
} else {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
private function _getMulticheckboxValue($info, $data)
|
|
{
|
|
return $this->_getMultilistValue($info, $data);
|
|
}
|
|
|
|
/**
|
|
* Apply constraints (e.g., min or max) defined in the preference info. Currently only used in text type preference.
|
|
*
|
|
* @param $info array preference info from definition
|
|
* @param $value mixed value submitted for the preference to be changed to
|
|
* @return mixed value preference will be changed to after applying constraints
|
|
*/
|
|
private function applyConstraints($info, $value)
|
|
{
|
|
if (isset($info['constraints'])) {
|
|
$original = $value;
|
|
foreach ($info['constraints'] as $type => $constraint) {
|
|
switch ($type) {
|
|
case 'min':
|
|
if ($value < $constraint) {
|
|
$value = $constraint;
|
|
Feedback::warning(tr(
|
|
'%0 set to minimum of %1 instead of submitted value of %2',
|
|
$info['preference'],
|
|
$constraint,
|
|
$original
|
|
));
|
|
}
|
|
break;
|
|
case 'max':
|
|
if ($value > $constraint) {
|
|
$value = $constraint;
|
|
Feedback::warning(tr(
|
|
'%0 set to maximum of %1 instead of submitted value of %2',
|
|
$info['preference'],
|
|
$constraint,
|
|
$original
|
|
));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
// for export as yaml
|
|
|
|
/**
|
|
* @global TikiLib $tikilib
|
|
* @param bool $added shows current prefs not in defaults
|
|
* @return array (prefname => array( 'current' => current value, 'default' => default value ))
|
|
*/
|
|
// NOTE: tikilib contains a similar method called getModifiedPreferences
|
|
public function getModifiedPrefsForExport($added = false)
|
|
{
|
|
$tikilib = TikiLib::lib('tiki');
|
|
|
|
$prefs = $tikilib->getModifiedPreferences();
|
|
|
|
$defaults = get_default_prefs();
|
|
$modified = [];
|
|
|
|
foreach ($prefs as $pref => $value) {
|
|
if (( $added && ! isset($defaults[$pref])) || (isset($defaults[$pref]) && $value !== $defaults[$pref] )) {
|
|
if (! in_array($pref, $this->system_modified) && ! in_array($pref, $this->system_info)) { // prefs modified by the system and with system info etc
|
|
$preferenceInformation = $this->getPreference($pref);
|
|
$modified[$pref] = [
|
|
'current' => ['serial' => $value, 'expanded' => $preferenceInformation['value'] ?? ''],
|
|
];
|
|
if (isset($defaults[$pref])) {
|
|
$modified[$pref]['default'] = $defaults[$pref];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ksort($modified);
|
|
|
|
return $modified;
|
|
}
|
|
|
|
public function getDefaults()
|
|
{
|
|
$defaults = [];
|
|
|
|
foreach ($this->getAvailableFiles() as $file) {
|
|
$data = $this->getFileData($file, true);
|
|
|
|
foreach ($data as $name => $info) {
|
|
if (isset($info['default'])) {
|
|
$defaults[$name] = $info['default'];
|
|
} else {
|
|
$defaults[$name] = '';
|
|
}
|
|
}
|
|
}
|
|
|
|
return $defaults;
|
|
}
|
|
|
|
private function getAvailableFiles()
|
|
{
|
|
$files = [];
|
|
foreach (glob(__DIR__ . '/prefs/*.php') as $file) {
|
|
if (basename($file) === "index.php") {
|
|
continue;
|
|
}
|
|
$files[] = substr(basename($file), 0, -4);
|
|
}
|
|
foreach (TikiLib::lib('theme')->get_available_themes() as $theme => $label) {
|
|
$themePath = TikiLib::lib('theme')->get_theme_path($theme);
|
|
foreach (glob($themePath . 'prefs/*.php') as $file) {
|
|
if (basename($file) === "index.php") {
|
|
continue;
|
|
}
|
|
$files[] = substr(basename($file), 0, -4);
|
|
}
|
|
}
|
|
foreach (\Tiki\Package\ExtensionManager::getPaths() as $path) {
|
|
foreach (glob($path . '/prefs/*.php') as $file) {
|
|
if (basename($file) === "index.php") {
|
|
continue;
|
|
}
|
|
$files[] = substr(basename($file), 0, -4);
|
|
}
|
|
}
|
|
return $files;
|
|
}
|
|
|
|
public function setFilters($tags)
|
|
{
|
|
global $user;
|
|
|
|
if (! in_array('basic', $tags)) {
|
|
$tags[] = 'basic';
|
|
}
|
|
TikiLib::lib('tiki')->set_user_preference($user, 'pref_filters', implode(',', $tags));
|
|
}
|
|
|
|
public function getEnabledFilters()
|
|
{
|
|
global $user;
|
|
$tikilib = TikiLib::lib('tiki');
|
|
$filters = $tikilib->get_user_preference($user, 'pref_filters', 'basic');
|
|
$filters = explode(',', $filters);
|
|
return $filters;
|
|
}
|
|
|
|
public function getFilters($filters = null)
|
|
{
|
|
if (! $filters) {
|
|
$filters = $this->getEnabledFilters();
|
|
}
|
|
|
|
$out = [
|
|
'basic' => [
|
|
'label' => tra('Basic'),
|
|
'type' => 'positive',
|
|
],
|
|
'advanced' => [
|
|
'label' => tra('Advanced'),
|
|
'type' => 'positive',
|
|
],
|
|
'experimental' => [
|
|
'label' => tra('Experimental'),
|
|
'type' => 'negative',
|
|
],
|
|
'unavailable' => [
|
|
'label' => tra('Unavailable'),
|
|
'type' => 'negative',
|
|
],
|
|
'deprecated' => [
|
|
'label' => tra('Deprecated'),
|
|
'type' => 'negative',
|
|
],
|
|
];
|
|
|
|
foreach ($out as $key => & $info) {
|
|
$info['selected'] = in_array($key, $filters);
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
private function buildPreferenceFilter($query, $input = null)
|
|
{
|
|
$filters = $this->getFilters($input);
|
|
|
|
foreach ($filters as $tag => $info) {
|
|
if ($info['selected']) {
|
|
$positive[] = $tag;
|
|
} elseif ($info['type'] == 'negative') {
|
|
$query->filterContent("NOT $tag", 'tags');
|
|
}
|
|
}
|
|
|
|
if (count($positive)) {
|
|
$query->filterContent(implode(' OR ', $positive), 'tags');
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
|
|
/***
|
|
* Store 10 most recently changed prefs for quickadmin module menu
|
|
*
|
|
* @param string $name preference name
|
|
* @param string $auser optional user
|
|
*/
|
|
|
|
public function addRecent($name, $auser = null)
|
|
{
|
|
global $user;
|
|
|
|
if (! $auser) {
|
|
$auser = $user;
|
|
}
|
|
|
|
$list = (array) $this->getRecent($auser);
|
|
array_unshift($list, $name);
|
|
$list = array_unique($list);
|
|
$list = array_slice($list, 0, 10);
|
|
|
|
TikiLib::lib('tiki')->set_user_preference($auser, 'admin_recent_prefs', serialize($list));
|
|
}
|
|
|
|
/***
|
|
* Get recent prefs list
|
|
*
|
|
* @param null $auser option user
|
|
* @return array array of pref names
|
|
*/
|
|
|
|
public function getRecent($auser = null)
|
|
{
|
|
global $user;
|
|
$tikilib = TikiLib::lib('tiki');
|
|
|
|
if (! $auser) {
|
|
$auser = $user;
|
|
}
|
|
|
|
$recent = $tikilib->get_user_preference($auser, 'admin_recent_prefs');
|
|
|
|
if (empty($recent)) {
|
|
return [];
|
|
} else {
|
|
return unserialize($recent);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Export preferences
|
|
*
|
|
* @param Tiki_Profile_Writer $writer
|
|
* @param string $preferenceName
|
|
* @param bool $all
|
|
* @return bool
|
|
*/
|
|
public function exportPreference(Tiki_Profile_Writer $writer, $preferenceName, $all = null)
|
|
{
|
|
if (isset($preferenceName) && ! $all) {
|
|
$listPrefs = [];
|
|
$listPrefs[$preferenceName] = true;
|
|
} else {
|
|
$listPrefs = $this->getModifiedPrefsForExport(true);
|
|
}
|
|
|
|
if (empty($listPrefs)) {
|
|
return false;
|
|
}
|
|
|
|
foreach ($listPrefs as $preferenceName => $value) {
|
|
if (is_string($preferenceName)) {
|
|
if ($info = $this->getPreference($preferenceName)) {
|
|
if (isset($info['profile_reference'])) {
|
|
$writer->setPreference($preferenceName, $writer->getReference($info['profile_reference'], $info['value']));
|
|
} else {
|
|
$writer->setPreference($preferenceName, $info['value']);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function getPackagePrefs()
|
|
{
|
|
global $prefs;
|
|
$ret = [];
|
|
foreach (array_keys($prefs) as $prefName) {
|
|
if (substr($prefName, 0, 3) == 'tp_') {
|
|
$ret[] = $prefName;
|
|
}
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Get a list of preferences that belong to themes
|
|
*
|
|
* @return array
|
|
* @throws Exception
|
|
*/
|
|
public function getThemePrefs()
|
|
{
|
|
global $prefs;
|
|
$ret = [];
|
|
foreach (array_keys($prefs) as $prefName) {
|
|
if (substr($prefName, 0, 7) == 'themes_') {
|
|
$ret[] = $prefName;
|
|
}
|
|
}
|
|
|
|
$themes = TikiLib::lib('theme')->get_available_themes();
|
|
$preferences = [];
|
|
foreach ($themes as $key => $theme) {
|
|
$themePref = array_filter($ret, function ($pref) use ($key) {
|
|
$pattern = '/^themes_' . $key . '_.*/';
|
|
return preg_match($pattern, $pref);
|
|
});
|
|
|
|
if (! empty($themePref)) {
|
|
$preferences[$theme] = $themePref;
|
|
}
|
|
}
|
|
|
|
return $preferences;
|
|
}
|
|
|
|
/**
|
|
* Filter hidden preferences using an array of preference names
|
|
* @return array
|
|
*/
|
|
public function filterHiddenPreferences($preferences)
|
|
{
|
|
$hiddenPreferences = [];
|
|
|
|
if (! empty($preferences)) {
|
|
foreach ($preferences as $preference) {
|
|
$preferenceDetails = $this->getPreference($preference['name']);
|
|
|
|
if (! empty($preferenceDetails['hide']) && $preferenceDetails['hide'] === true) {
|
|
$hiddenPreferences[] = $preference['name'];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $hiddenPreferences;
|
|
}
|
|
|
|
/**
|
|
* Function responsible for getting the default system preferences.
|
|
* Logic related to getting default system preferences should be set here
|
|
* @return array
|
|
*/
|
|
private function getDefaultSystemPreferences()
|
|
{
|
|
$defaultSystemPreferences = [];
|
|
|
|
foreach (self::DEFAULT_HIDDEN_PREFERENCES as $hiddenPreference) {
|
|
$defaultSystemPreferences[] = 'hide ' . $hiddenPreference;
|
|
}
|
|
|
|
return $defaultSystemPreferences;
|
|
}
|
|
}
|