module_title = _t('site', 'Settings');
}
public function onNewRequest($request)
{
parent::onNewRequest($request);
$this->languagesSwitcherIndex = 1;
}
/**
* Ссылка на системную настройку
* @param string $module
* @param string|array $opts
* @param string $template
* @return string
*/
public function settingsSystemLink($module, $opts = [], $template = '')
{
if (is_string($opts)) {
$opts = ['sett' => $opts];
}
func::array_defaults($opts, [
'tab' => 'def',
'sett' => '',
'e' => true,
'local' => false,
'local-attr' => [],
]);
$hash = '#' . strval($opts['tab']);
if (! empty($opts['sett'])) {
$hash .= ':' . $opts['sett'];
}
if (!empty($opts['local'])) {
HTML::attributeAdd($opts['local-attr'], 'class', 'j-local-link');
return HTML::attributes(array_merge($opts['local-attr'], [
'href' => HTML::escape($hash, $opts['e']),
'data-module' => strval($module),
]));
}
$link = tpl::adminLink('settingssystemmanager&tab=' . strval($module) . $hash, 'site', $opts['e']);
if (!empty($template) && is_string($template)) {
switch ($template) {
case 'disabled': {
$link = _t('site', 'This function is disabled in the System Settings and is not displayed to site users.', [
'setting_link' => 'href="' . $link . '"',
]);
} break;
}
}
return $link;
}
/**
* Форма редактирования системных настроек модуля
* @param Module $module объект модуля
* @param array $settings настройки
* @param array $options @ref доп. настройки
* @return string|array
*/
public function settingsSystemForm(Module $module, array $settings, array &$options = [])
{
if (empty($settings) && empty($options['tabs'])) {
return '';
}
# дополнительные настройки + фильтры (хуки)
if (empty($options['extra'])) {
$options['extra'] = [];
}
$options['extra'] = $this->app->filter('site.settings.system.extra.' . $module->module_name, $options['extra'], $module);
if (!empty($options['extra']) && is_array($options['extra'])) {
foreach ($options['extra'] as $k => $v) {
if (!empty($k) && is_array($v) && isset($v['type']) && isset($v['default'])) {
$settings[$k] = $v;
}
}
}
# табы
$tabs = (!empty($options['tabs']) && is_array($options['tabs']) ? $options['tabs'] : []);
if (! empty($settings)) {
$tabs['def'] = $this->defaults($tabs['def'] ?? [], ['t' => _t('', 'General'), 'p' => 0]);
}
$theme = $this->app->theme();
if (!empty($options['dataOnly'])) {
$data = [];
foreach ($settings as $key => &$v) {
if (empty($v['type']) || $v['type'] === TYPE_PASS || !isset($v['default'])) {
continue;
}
if ($theme !== false && $theme->configExists($key)) {
$data[$key] = $theme->config($key, $v['default']);
continue;
}
$data[$key] = $this->config($key, $v['default'], $v['type']);
} unset($v);
$options['data'] = $data;
return '';
}
$fordev = false;
foreach ($settings as $k => &$v) {
if (! empty($v['fordev'])) {
if (! $this->isAdminFordev()) {
unset($settings[$k]);
continue;
}
if (! $fordev) {
$fordev = true; # добавляем таб "Разработчику"
$tabs['fordev'] = ['t' => '', 'p' => 1000];
}
$v['tab'] = 'fordev';
}
if (!isset($v['tab']) || empty($v['tab']) || !isset($tabs[$v['tab']])) {
# таб по умолчанию
$v['tab'] = 'def';
}
} unset($v);
# сортируем табы в порядке priority
if (sizeof($tabs) > 1) {
$priorityOrder = [];
$i = 1;
foreach ($tabs as $k => $v) {
$priorityOrder[$k] = $v['p'] ?? $i;
$i++;
}
array_multisort($priorityOrder, SORT_ASC, $tabs);
}
$data = [
'module' => $module,
'settings' => &$settings,
'options' => &$options,
'tabs' => &$tabs,
'theme' => $theme,
];
return $this->template('admin/settings.sys.form', $data, [
'path' => $this->module_dir_tpl_core,
]);
}
/**
* Получение данных, для формирования системных настроек
* @param array $opts
* @return array
*/
public function settingsSystemManagerData($opts = [])
{
$data = ['tabs' => []];
$modulesList = $this->app->getModulesList();
foreach (['test'] as $v) {
if (isset($modulesList[$v])) {
unset($modulesList[$v]);
}
}
$indexes = [];
$uniques = function ($name) use (&$indexes) {
if (! isset($indexes[$name])) {
$indexes[$name] = 0;
return $name;
}
$indexes[$name]++;
return $name . $indexes[$name];
};
$tabsMethod = 'settingsSystemTabs';
$defaultCallMethod = 'settingsSystem';
$i = 30;
foreach ($modulesList as $moduleName => $moduleParams) {
$module = $this->app->module($moduleName);
$callMethods = [];
if (method_exists($module, $tabsMethod)) {
$callMethods = call_user_func([$module, $tabsMethod]);
}
if (empty($callMethods) || ! is_array($callMethods)) {
$callMethods = [$moduleName => $defaultCallMethod];
}
foreach ($callMethods as $tabName => $method) {
if (! method_exists($module, $method)) {
continue;
}
$options = $opts;
$options['name'] = $tabName;
$form = $module->$method($options);
$name = $uniques($options['name'] ?? $tabName);
$data['tabs'][$name] = [
'name' => $name,
'title' => (!empty($options['title']) ? $options['title'] : $module->module_title),
'form' => $form,
'module' => $moduleName,
'priority' => (!empty($options['priority']) ? $options['priority'] : $i++),
'data' => $options['data'] ?? [],
'formTabs' => $options['tabs'] ?? [],
];
}
}
return $data;
}
/**
* Получение всех системных настроек и их значений
* @return array
*/
public function settingsSystemData()
{
$data = $this->settingsSystemManagerData(['dataOnly' => true]);
$forms = [];
$result = [];
foreach ($data['tabs'] as $k => $v) {
if (! empty($v['data'])) {
$result = array_merge($result, $v['data']);
}
if (! empty($v['formTabs'])) {
foreach ($v['formTabs'] as $vv) {
if (! empty($vv['form'])) {
$forms[] = ['m' => $v['module'], 'f' => $vv['form']];
}
}
}
}
foreach ($forms as $v) {
$module = $this->app->module($v['m']);
$form = tplAdmin::settingsForm($module, 'admin/settings.' . $v['f'] . '.form', ['action' => 'settingsSystemAjaxForm']);
$data = $form->settingsSystemData();
if (! empty($data)) {
$result = array_merge($result, $data);
}
}
return $result;
}
/**
* Системные настройки: настройки системы
* @param array $extend @ref
* @param string $tab
*/
public function settingsSystemSystem(&$extend = [], $tab = 'system')
{
if (! isset($extend['settings'])) {
$extend['settings'] = [];
}
$languagesList = $this->locale->getLanguages(false);
if (sizeof($languagesList) >= 1) {
$extend['settings']['locale.accepted.languages'] = [
'title' => _t('site', 'Language auto-detection'),
'type' => TYPE_BOOL,
'input' => 'select',
'default' => true,
'options' => [
true => ['title' => _t('', 'Enabled'), 'description' => _t('site', 'Autodetect localization when the user first visits the site')],
false => ['title' => _t('', 'Disabled')],
],
'tab' => $tab,
];
}
$extend['settings']['date.timezone'] = [
'title' => _t('site', 'Time Zone'),
'type' => TYPE_STR,
'input' => 'select',
'default' => 'Europe/Kiev',
'options' => function () {
$list = timezone_identifiers_list();
$options = [];
foreach ($list as $v) {
$options[$v] = ['title' => $v];
}
return $options;
},
'tab' => $tab,
];
if ($this->app->request()->scheme(true) == 'https' || $this->config('https.only')) {
$extend['settings']['https.only'] = [
'title' => _t('site', 'HTTPS Mode'),
'children' => ['https.redirect'],
'type' => TYPE_BOOL,
'input' => 'select',
'default' => false,
'options' => [
true => ['title' => _t('', 'Enabled')],
false => ['title' => _t('', 'Disabled')],
],
'tab' => $tab,
];
$extend['settings']['https.redirect'] = [
'title' => _t('site', 'HTTP Redirect'),
'title-tip' => _t('site', 'Redirect from the HTTP version of the site to HTTPS'),
'parent' => ['id' => 'https.only', 'value' => true],
'type' => TYPE_BOOL,
'input' => 'select',
'default' => false,
'options' => [
true => ['title' => _t('', 'Enabled')],
false => ['title' => _t('', 'Disabled')],
],
'tab' => $tab,
];
}
$statisticURL = '';
if (in_array(BFF_PRODUCT, ['do', 'mp', 'cp'], true)) {
$statisticURL = 'https://tamaranga.com/docs/doska/site-system-settings-28-189.html#p2';
} elseif (BFF_PRODUCT === 'freelance') {
$statisticURL = 'https://tamaranga.com/docs/freelance/settings-system-manager-55-269.html#p2';
} elseif (BFF_PRODUCT === 'city') {
$statisticURL = 'https://tamaranga.com/docs/portal/site-system-settings-75-287.html#p2';
}
if (! empty($statisticURL)) {
$extend['settings']['hh.stat.allowed'] = [
'title' => _t('site', 'System operation statistics'),
'type' => TYPE_BOOL,
'input' => 'select',
'default' => true,
'options' => [
true => ['title' => _t('', 'Enabled'),
'description' => _t('site', 'Allow collection and subsequent analysis [a]of system performance statistics[/a]', [
'a' => '',
'/a' => '',
]),
],
false => ['title' => _t('', 'Disabled')],
],
'tab' => $tab,
'onchange' => 'site/changeStatAllowed/',
];
}
if ($this->isAdminFordev()) {
$size = config::get('site.cache.size', '{}');
$size = json_decode($size, true);
$size = ! empty($size) && isset($size['size']) ? tpl::filesize($size['size']) : tpl::filesize(0);
$resetScript = '';
$extend['settings']['site.cache.reset'] = [
'title' => _t('site', 'Site Cache'),
'input' => 'html',
'content' => $size . ' ' . $resetScript,
'tab' => $tab,
];
}
}
/**
* Системные настройки: локализация
* @param array $extend @ref
* @param string $tab
*/
public function settingsSystemLocale(&$extend = [], $tab = 'locale')
{
if (! isset($extend['settings'])) {
$extend['settings'] = [];
}
$languagesList = $this->locale->getLanguages(false);
if (sizeof($languagesList) >= 1) {
$extend['settings']['locale.accepted.languages'] = [
'title' => _t('site', 'Language auto-detection'),
'type' => TYPE_BOOL,
'input' => 'select',
'default' => true,
'options' => [
true => [
'title' => _t('', 'Enabled'),
'description' => _t('site', 'Autodetect localization when the user first visits the site'),
],
false => ['title' => _t('', 'Disabled')],
],
'tab' => $tab,
];
}
}
/**
* Get counters & code to view
* @param int|null $position
* @return array
*/
public function countersAndCode(?int $position = null)
{
return $this->app->filter('site.counters.view', $this->model->countersView($position), $position);
}
/**
* Add site counters to layout
* @param Layout $layout
* @param array|null $counters
* @return void
*/
public function countersToLayout(Layout $layout, ?array $counters = null)
{
$counters = $counters ?? $this->model->countersViewByPosition();
if (empty($counters)) {
return;
}
if (! empty($counters[static::COUNTERS_POS_HEAD])) {
$layout->afterHead(function () use (&$counters) {
$code = '';
foreach ($counters[static::COUNTERS_POS_HEAD] as $data) {
$code .= $data['code'];
}
return $code;
});
}
if (! empty($counters[static::COUNTERS_POS_BODY_START])) {
$layout->beforeBody(function () use (&$counters) {
$code = '';
foreach ($counters[static::COUNTERS_POS_BODY_START] as $data) {
$code .= $data['code'];
}
return $code;
});
}
if (! empty($counters[static::COUNTERS_POS_BODY_FINISH])) {
$layout->afterBody(function () use (&$counters) {
$code = '';
foreach ($counters[static::COUNTERS_POS_BODY_FINISH] as $data) {
$code .= $data['code'];
}
return $code;
});
}
}
/**
* Получаем данные об основной валюте
* @deprecated use {@see Currency::default()}
* @param string|bool $valueKey ключ требуемых данных, false - все данные
* @return mixed
*/
public function currencyDefault($valueKey = 'title_short')
{
if ($valueKey == 'id') {
return $this->config('currency.default', 1, TYPE_UINT);
}
return $this->currencyData(0, $valueKey);
}
/**
* Получаем данные о необходимой валюте
* @deprecated use {@see Currency::data()}
* @param int $currencyID ID валюты, если 0 - возвращаем данные об основной валюте
* @param string|bool $valueKey ключ требуемых данных, false - все данные
* @param bool $enabledOnly только включенные валюты
* @return mixed
*/
public function currencyData($currencyID = 0, $valueKey = false, $enabledOnly = true)
{
$data = $this->model->currencyData(false, false, $enabledOnly);
if (empty($currencyID)) {
$currencyID = $this->currencyDefault('id');
}
if (!isset($data[$currencyID])) {
return false;
}
return ($valueKey !== false && isset($data[$currencyID][$valueKey]) ?
$data[$currencyID][$valueKey] :
$data[$currencyID]
);
}
/**
* Инициализация компонента Publicator для статических страниц
* @return \bff\db\Publicator
*/
public function pagesPublicator()
{
$aSettings = [
'title' => false,
'langs' => $this->locale->getLanguages(),
'images_path' => $this->app->path('pages', 'images'),
'images_path_tmp' => $this->app->path('tmp', 'images'),
'images_url' => $this->app->url('pages', 'images'),
'images_url_tmp' => $this->app->url('tmp', 'images'),
'photo_sz_view' => ['width' => 960],
'images_original' => true,
// gallery
'gallery_sz_view' => [
'width' => 960, 'height' => false,
'vertical' => ['width' => false, 'height' => 640],
'quality' => 95,
'sharp' => []
], // no sharp
];
if ($this->pagesPublicatorEnabled()) {
$configSettings = $this->config('pages.publicator.settings', []);
if (!empty($configSettings) && is_array($configSettings)) {
$aSettings = array_merge($aSettings, $configSettings);
}
}
return $this->attachComponent('publicator', new Publicator($this->module_name, $aSettings));
}
/**
* Использовать Publicator для статических страниц
* @return bool|int
*/
public function pagesPublicatorEnabled()
{
return $this->config('pages.publicator', false); # bool или int
}
/**
* Расширения для статических страниц
* @return string
*/
public function pagesUrlExtension()
{
return $this->config('pages.extension', '.html');
}
/**
* Защита от спама (частой отправки сообщений / выполнения действий)
* @param string $key ключ выполняемого действия
* @param int $timeout допустимая частота выполнения действия, в секундах
* @param bool $setError устанавливать ошибку
* @param array $opts
* @return bool true - частота отправки превышает допустимый лимит, false - все ок
*/
public function preventSpam($key, $timeout = 20, $setError = true, array $opts = [])
{
if ($this->app->hooksAdded('site.prevent.spam')) {
$hook = $this->app->filter('site.prevent.spam', [
'key' => &$key,
'timeout' => &$timeout,
'setError' => &$setError,
'opts' => &$opts,
]);
if (isset($hook['result'])) {
return $hook['result'];
}
} elseif ($this->app->isTest()) {
return false;
}
$timeout = intval($timeout);
if ($timeout <= 0) {
return false;
}
$userID = $opts['userId'] ?? User::id();
$ipAddress = $opts['ip'] ?? $this->request->remoteAddress();
$last = $this->model->requestGet($key, $userID, $ipAddress, false);
if ($last > 0 && ((microtime(true) - $last) < $timeout)) {
if ($setError) {
if ($timeout <= 70) {
$this->errors->set(_t('', 'Please try again in one minute'));
} else {
$this->errors->set(_t('', 'Please try again in a few minutes'));
}
}
return true;
} else {
$this->model->requestSet($key, $userID, $ipAddress);
}
return false;
}
/**
* Защита от спама на основе допустимого кол-ва повторов выполненного действия
* В случае привышения этого кол-ва включаем режим ожидание ($timeout)
* @param string $key ключ выполняемого действия
* @param int $limit допустимое кол-во повторов
* @param int $timeout таймаут ожидания (cooldown) при достижении лимита попыток, в секундах
* @param bool $setError устанавливать ошибку
* @param array $opts
* @return bool true - достигнут лимит повторов, false - все ок
*/
public function preventSpamCounter($key, $limit, $timeout = 20, $setError = true, array $opts = [])
{
if ($this->app->hooksAdded('site.prevent.spam.counter')) {
$hook = $this->app->filter('site.prevent.spam.counter', [
'key' => &$key,
'limit' => &$limit,
'timeout' => &$timeout,
'setError' => &$setError,
'opts' => &$opts,
]);
if (isset($hook['result'])) {
return $hook['result'];
}
} elseif ($this->app->isTest()) {
return false;
}
$limit = intval($limit);
$timeout = intval($timeout);
if ($limit <= 0 || $timeout <= 0) {
return false;
}
$userID = $opts['userId'] ?? User::id();
$ipAddress = $opts['ip'] ?? $this->request->remoteAddress();
$last = $this->model->requestGet($key, $userID, $ipAddress, true);
# первое выполнение действия
if (empty($last)) {
$this->model->requestSet($key, $userID, $ipAddress, 1);
return false;
}
$filter = ['user_action' => $key];
if ($userID) {
$filter['user_id'] = $userID;
} else {
$filter['user_ip'] = $ipAddress;
}
$counter = intval($last['counter']);
if ($counter < $limit) {
# повтор: лимит не достигнут
$this->model->requestUpdate($filter, [
'counter' => $counter + 1,
'created' => $this->db->now(),
]);
return false;
} elseif ($counter === $limit) {
# повтор: лимит достигнут
# пропускаем + помечаем период ожидания для последующих попыток
$this->model->requestUpdate($filter, [
'counter' => $counter + 1,
'created' => date('Y-m-d H:i:s', strtotime('+ ' . $timeout . ' seconds')),
]);
return false;
} else {
# период ожидания: просим подождать
if (strtotime($last['created']) > time()) {
if ($setError) {
if ($timeout <= 70) {
$this->errors->set(_t('', 'Please try again in one minute'));
} else {
$this->errors->set(_t('', 'Please try again in a few minutes'));
}
}
return true;
} else {
# завершаем период ожидания, сбрасываем счетчик попыток
$this->model->requestUpdate($filter, [
'counter' => 1,
'created' => $this->db->now(),
]);
return false;
}
}
}
/**
* Формирование URL для работы с сайтом в выключенном режиме
* @param bool $secretOnly только ключ
* @return string
*/
public function offlineUrl($secretOnly = false)
{
$secret = hash('sha256', $this->config('crypt.key', '') . $this->config('site.title'));
if ($secretOnly) {
return $secret;
}
return $this->router->url('index', ['offline' => $secret]);
}
/**
* Обработка копирования данных локализации
* @param string $from Ключ языка
* @param string $to Ключ языка
* @param array $opts [rewrite]
*/
public function onLocaleDataCopy($from, $to, $opts = [])
{
$rewrite = ! empty($opts['rewrite']);
# настройки сайта
$configUpdate = [];
$fromPostfixLen = mb_strlen('_' . $from);
$config = config::all();
foreach ($config as $k => $v) {
if (mb_strrpos($k, '_' . $from) === mb_strlen($k) - $fromPostfixLen) {
$key = mb_substr($k, 0, -$fromPostfixLen) . '_' . $to;
if ($rewrite || empty($config[$key])) {
$configUpdate[$key] = $v;
}
}
}
config::save($configUpdate);
$pages = $this->app->theme()->initPagesSettingsForms();
foreach ($pages as $k => $v) {
if (! $v['form']) {
continue;
}
$f = $v['form'];
/** @var Form $f */
$f->onLocaleDataCopy($from, $to, $opts);
}
}
/**
* Формирование списка директорий/файлов требующих проверки на наличие прав записи
* @return array
*/
public function writableCheck()
{
$dirs = [
$this->app->path('tmp', 'images') => 'dir-only', # временная директория загрузки изображений
$this->app->path('images') => 'dir-only', # /public_html/files/images/
];
$tmp1 = ini_get('upload_tmp_dir');
if (!empty($tmp1)) {
$dirs[$tmp1] = ['type' => 'dir-only', 'title' => _t('site', 'temporary files directory during upload')]; # временная директория загрузки файлов
}
$tmp2 = sys_get_temp_dir();
if (!empty($tmp2) && $tmp1 !== $tmp2) {
$dirs[$tmp2] = ['type' => 'dir-only', 'title' => _t('site', 'temporary files directory')]; # директория tmp файлов
}
if ($this->pagesPublicatorEnabled()) {
$dirs[$this->app->path('pages', 'images')] = 'dir-only'; # статические страницы: публикатор
}
# файлы локализации
$dirs[$this->locale->gettextDomain('path')] = 'file'; # domain.php
foreach ($this->locale->getLanguages(true) as $lng) {
$dirs[$this->locale->gettextPath($lng)] = 'dir-only'; # файлы переводов
}
# минимизация статики: js, css
$dirs[$this->app->path('min')] = ['type' => 'dir+files', 'title' => _t('site', 'Directory for minified js/css')];
# плагины:
$dirs[$this->app->pluginsPath()] = ['type' => 'dir-only'];
$dirs[$this->app->publicPath('plugins')] = ['type' => 'dir-only'];
# темы:
$dirs[$this->app->themesPath()] = ['type' => 'dir-only'];
$dirs[$this->app->publicPath('themes')] = ['type' => 'dir-only'];
# custom
$dirs[$this->app->publicPath('custom')] = ['type' => 'dir-only'];
# расширения: настройки + изображения
$dirs[$this->app->path('extensions', 'images')] = 'dir-only';
# расширения: файлы
$dirs[$this->app->path('extensions')] = 'dir-only';
$dirs[$this->app->basePath('files/extensions')] = 'dir-only';
# vendor
$dirs[$this->app->basePath('vendor')] = 'dir-only';
return array_merge(parent::writableCheck(), $dirs);
}
/**
* Очистка директорий временных файлов
* @param array $dirs полные путь директорий временных файлов
* @param int $days кол-во дней от момента загрузки файлов, спустя которые их необходимо удалять
*/
public function temporaryDirsCleanup(array $dirs, $days = 3)
{
$days = ($days < 1 ? 1 : intval($days)) * 86400;
$time = time();
foreach ($dirs as $v) {
$files = Files::getFiles($v);
foreach ($files as $f) {
if ((filectime($f) + $days) < $time) {
@unlink($f);
}
}
}
}
/**
* Favicon
* @param array $options доп. параметры
* @return string
*/
public function favicon(array $options = []): string
{
$list = $this->app->filter('site.favicon.list', [
'ico' => ['rel' => 'icon', 'href' => $this->app->url('/favicon.ico'), 'type' => 'image/x-icon'],
]);
$html = '';
if (!empty($list)) {
foreach ($list as $v) {
if (!empty($v['href'])) {
$html .= '';
}
}
}
return $html;
}
/**
* Название сайта
* Фильтр: "site.title.{language}"
* @param string $position позиция вывода заголовка (не указана - '')
* @param string $language ключ языка заголовка (не указан - текущий)
* @param string $default заголовок по-умолчанию (не указан - SITEHOST)
* @return string
*/
public function title($position = '', $language = '', $default = SITEHOST): string
{
if (empty($language)) {
$language = $this->locale->getCurrentLanguage();
}
$result = $this->config('site.title', $default);
switch ($position) {
case 'seo.template.macros': {
$result = $this->config('site.title.seo', [], TYPE_ARRAY)[$language] ?? $default;
} break;
case 'sendmail.template.macros': {
$result = $this->config('site.title.sendmail', [], TYPE_ARRAY)[$language] ?? $default;
} break;
}
return (string)$this->app->filter('site.title', $result, $position, $language, $default);
}
/**
* Заголовок сайта в шапке
* Фильтр: "site.title.header.{language}", "site.title.header.admin.{language}"
* @param string $position позиция вывода заголовка (не указана - '')
* @param bool|null $adminPanel для админ. панели (null - определять по контексту)
* @param string|null $language ключ языка заголовка (null - текущий)
* @param string $default заголовок по-умолчанию
* @return string
*/
public function titleHeader($position = 'header', $adminPanel = null, $language = null, $default = '')
{
if (is_null($adminPanel)) {
$adminPanel = $this->isAdminPanel();
}
if (empty($language)) {
$language = $this->locale->current();
}
if ($adminPanel) {
return (string)$this->app->filter('site.title.header.admin', $this->config('site.title.header.admin', [], TYPE_ARRAY)[$language] ?? $default, $position, $adminPanel, $language, $default);
} else {
return (string)$this->app->filter('site.title.header', $this->config('site.title.header', [], TYPE_ARRAY)[$language] ?? $default, $position, $adminPanel, $language, $default);
}
}
/**
* Формирование текста копирайта
* @param array $options доп. параметры
* @return string
*/
public function copyright(array $options = [])
{
if (empty($options['lng'])) {
$options['lng'] = $this->locale->current();
}
return strtr($this->app->filter('site.copyright.text', $this->config('site.copyright.text', [], TYPE_ARRAY)[ $options['lng'] ] ?? '', $options), [
'{year}' => date('Y'),
]);
}
/**
* Список доступных языков сайта для переключения
* @param bool $adminPanel для вывода в админ. панели
* @param array $options доп. параметры
* @return array
*/
public function languagesList($adminPanel = false, array $options = [])
{
$exclude = (!$adminPanel ? $this->locale->getHiddenLocalesList() : []);
$list = $this->app->filter('site.languages.list', $this->locale->getLanguages(false, $exclude), $options);
if (is_array($list)) {
func::sortByPriority($list);
}
return $list;
}
/**
* Переключатель языка
* @param array $options доп.параметры
* @param string|null $currentLanguage
* @return string HTML
*/
public function languagesSwitcher(array $options = [], ?string $currentLanguage = null)
{
$data = [];
$data['prefix'] = 'language-' . ($this->languagesSwitcherIndex++);
$data['options'] = $options;
$data['lang'] = $currentLanguage ?? $this->locale->current();
$data['languages'] = $this->languagesList();
foreach ($data['languages'] as $k => &$v) {
$v['active'] = ($k == $data['lang']);
$v['url'] = Url::currentWithLocale($k);
} unset($v);
$data['template'] = (!empty($options['template']) ? $options['template'] : 'languages');
$data = $this->app->filter('site.languages.switcher', $data, $options);
return $this->view->template($data['template'], $data);
}
/**
* Menu to render (current theme & language)
* @param string $id
* @param string|null $template
* @param bool $force
* @return \bff\view\Menu
*/
public function menu(string $id, ?string $template = null, bool $force = false)
{
if (! array_key_exists($id, $this->menusRegistered) || $force) {
$theme = $this->app->theme();
if (empty($this->menusRegistered)) {
$this->menusRegister($theme);
}
$menu = $theme->menu($id);
$settings = $this->menuSettings($id);
foreach ($settings as $k => $v) {
if (empty($v['is_system'])) {
continue;
}
if ($menu->get($k) === null) {
unset($settings[$k]);
}
}
$menu->extend($settings);
$this->app->hook('site.menu.' . $id, $menu);
$this->menusRegistered[$id] = $menu;
} else {
$menu = $this->menusRegistered[$id];
}
if ($template) {
$menu->setTemplate($template);
}
return $menu;
}
/**
* Register menus in modules & extensions
* @param \Theme $theme
* @return void
*/
protected function menusRegister($theme)
{
$frontend = $this->isFrontend();
# Modules
foreach ($this->app->getModulesList() as $module) {
if ($file = $this->view->resolveFileInPath('menu.php', $module['path'])) {
if ($frontend && ! empty($module['extension']) && ! $module['extension_active']) {
continue;
}
require $file;
}
}
# Plugins & Addons
foreach (Dev::getPluginsList() as $plugin) {
if (! $plugin->isActive()) {
continue;
}
if ($file = $this->view->resolveFileInPath('menu.php', $plugin->module_dir)) {
require $file;
} else {
$plugin->menu($theme);
}
}
}
/**
* Menu admin settings
* @param string $id
* @param string|null $lang
* @return \bff\view\MenuItem[]
*/
protected function menuSettings(string $id, ?string $lang = null)
{
$lang = $lang ?? $this->locale->current();
$settings = Cache::rememberForever('site:menu:' . $id . ':' . $lang, function () use ($id, $lang) {
$records = $this->model->menuListing(
['group_key' => $id],
['orderBy' => 'num', 'lang' => $lang]
);
$settings = [];
foreach ($records as $item) {
# System created items (modules/extensions)
if ($item['is_system']) {
$settings[$item['item_key']] = $item;
continue;
}
# Admin created items
do {
if ($item['type'] != static::MENU_TYPE_ITEM) {
break;
}
if (! empty($item['pid'])) {
break;
}
if (empty($item['title'])) {
break;
}
if (empty($item['url'])) {
break;
}
$data = [];
foreach (['title', 'target', 'url', 'style', 'icon', 'num', 'enabled'] as $key) {
$data[$key] = $item[$key];
}
$settings[$item['id']] = $data;
} while (false);
}
return $settings;
});
# Macros
# currenty available for admin items only
foreach ($settings as $key => $item) {
if (empty($item['is_system'])) {
$settings[$key]['url'] = $this->menuMacrosReplace($item['url'], $lang);
}
}
return $settings;
}
/**
* Reset all menus cache
*/
public function menusResetCache()
{
$theme = $this->app->theme();
$this->menusRegister($theme);
$languages = $this->locale->getLanguages();
foreach ($theme->menus() as $menu) {
foreach ($languages as $lang) {
Cache::delete('site:menu:' . $menu->id() . ':' . $lang);
}
}
}
/**
* Замена макросов меню в строке
* @param string $string
* @param string|null $lang
* @return string
*/
protected function menuMacrosReplace($string, ?string $lang = null)
{
return strtr($string, [
static::MENU_MACROS_SITEURL => $this->getMenuMacrosReplacement(static::MENU_MACROS_SITEURL, $lang),
Url::HOST_PLACEHOLDER => $this->getMenuMacrosReplacement(Url::HOST_PLACEHOLDER, $lang),
]);
}
/**
* Получаем замену макроса меню по его ключу
* @param string $key ключ макроса
* @param string|null $lang ключ языка
* @return string
*/
protected function getMenuMacrosReplacement($key, ?string $lang = null)
{
switch ($key) {
case static::MENU_MACROS_SITEURL:
return Url::to('/', ['lang' => false]);
case Url::HOST_PLACEHOLDER:
return Url::to('', ['lang' => $lang, 'scheme' => false]);
}
return $key;
}
}