TYPE_NOTAGS, # Название 'content' => TYPE_STR, # Содержание 'mtitle' => TYPE_NOTAGS, # Meta title 'mkeywords' => TYPE_NOTAGS, # Meta keywords 'mdescription' => TYPE_NOTAGS, # Meta description ]; public $langCurrencies = [ 'title' => TYPE_NOTAGS, # название 'title_short' => TYPE_NOTAGS, # название, сокращенное 'title_decl' => TYPE_NOTAGS, # название, для склонения ]; protected $currencyDataCache = []; public function onNewRequest($request) { parent::onNewRequest($request); $this->currencyDataCache = []; } # -------------------------------------------------------------------- # Страницы /** * Page model * @param int|null $id * @param array $columns * @param array $with * @param string|null $lang * @return \bff\modules\site\models\Page | \bff\db\illuminate\Model | array */ public function page($id = null, $columns = ['*'], array $with = [], $lang = null) { $model = $this->model('Page'); if (! empty($id)) { return $model->one($id, $columns, $with, $lang); } return $model; } /** * Pages listing * @param array $opts * @return array */ public function pagesListing(array $opts = []) { $opts = $this->defaults($opts, [ 'orderBy' => 'title', 'fields' => [], 'keyBy' => '', 'lang' => $this->locale->current(), ]); $lang = $opts['lang']; $query = $this->page(); $query->langDefault($lang); $query = $query->orderBy($opts['orderBy']); $fields = ['id', 'title', 'filename', 'issystem', 'noindex']; if (! empty($opts['fields'])) { $fields = array_unique(array_merge($fields, $opts['fields'])); } $data = $query->tag('site.pages.listing')->get($fields); if (empty($data)) { return []; } if (! empty($opts['keyBy'])) { $data = $data->keyBy($opts['keyBy']); } $data = $data->toArray(); return $data; } /** * Page data to view * @param string $filename * @param string|null $lang * @return array|false */ public function pageDataView($filename, ?string $lang = null) { $page = $this->page(['filename' => $filename]); if (! $page) { return false; } return $page->toArray(); } /** * Page data for sitemap * @param int $id * @param string[] $fields * @return array|\bff\db\illuminate\Model|\bff\modules\site\models\Page */ public function pageDataForSitemap($id, $fields = ['id', 'filename']) { if (empty($fields)) { $fields = ['*']; } else { if (!is_array($fields)) { $fields = [$fields]; } } return $this->page($id, $fields); } /** * Формирование данных о статических страницах для файла Sitemap.xml * @param array $filter фильтр * @param string $priority приоритетность url * @return callable callback-генератор строк вида array [['l'=>'url страницы','m'=>'дата последних изменений'],...] */ public function pagesSitemapXmlData(array $filter = [], string $priority = '') { if (empty($filter)) { $filter['noindex'] = 0; } return function ($count = false, callable $callback = null) use ($filter, $priority) { if ($count) { return $this->db->select_rows_count(static::TABLE_PAGES, $filter); } else { $filter = $this->prepareFilter($filter, '', [ ':format' => '%Y-%m-%d', ]); $alt = SEO::sitemapXMLAltLangs(SEO::SITEMAP_LANG_TRANSLATIBLE, SEO::SITEMAP_LANG_ALL); $this->db->tag('site-pages-sitemap-xml-data', ['filter' => &$filter]) ->select_iterator( 'SELECT filename as l, DATE_FORMAT(modified, :format) as m FROM ' . static::TABLE_PAGES . ' ' . $filter['where'] . ' ORDER BY modified DESC', $filter['bind'], function (&$page) use (&$callback, $priority, $alt) { $page['l'] = Site::url('page', ['filename' => $page['l']], true); if (! empty($alt)) { $page['alt'] = []; foreach ($alt as $l) { $page['alt'][$l] = Url::dynamic($page['l'], [], ['lang' => $l]); } } $page['l'] = Url::dynamic($page['l']); if (! empty($priority)) { $page['p'] = $priority; } $callback($page); } ); } return false; }; } # -------------------------------------------------------------------- # Счетчики /** * Counter model * @param int|null $id * @param array $columns * @param array $with * @return \bff\modules\site\models\Counter | \bff\db\illuminate\Model | array */ public function counter($id = null, $columns = ['*'], array $with = []) { $model = $this->model('Counter'); if (! empty($id)) { return $model->one($id, $columns, $with); } return $model; } /** * Счетчики * @param array $filter фильтр списка счетчиков * @param bool $countOnly только подсчет кол-ва счетчиков * @param string $limit * @param string $orderBy * @return mixed */ public function countersListing(array $filter, $countOnly = false, $limit = '', $orderBy = '') //admin { $counters = $this->counter()->where($filter); if ($countOnly) { return $counters->count(); } if ($orderBy) { $counters->orderByRaw($orderBy); } if ($limit) { $counters->limit($limit); } return $counters->tag('site.counters.listing') ->get(['id', 'created', 'title', 'code', 'enabled'])->toArray(); } /** * Get counters to view (on position) * @param int|null $position * @return array */ public function countersView(?int $position = null) { $query = $this->counter()->enabled(); if (is_numeric($position)) { $query->where('code_position', $position); } return $query->tag('site.counters.view') ->orderBy('num')->get(['id', 'code'])->toArray(); } /** * Получение данных счетчиков с группировкой по позиции отображения на странице * @param array $opts * @return array */ public function countersViewByPosition(array $opts = []): array { $opts = $this->defaults($opts, [ 'cache' => 60, ]); $data = $this->db->select_rows(static::TABLE_COUNTERS, ['id','code','code_position'], [ 'enabled' => 1, ], 'num', '', [], $opts['cache']); if (empty($data)) { return []; } return func::array_transparent($data, 'code_position', false); } # -------------------------------------------------------------------- # Валюты /** * Currency model * @param int|null $id * @param array $columns * @param array $with * @param string|null $lang * @return \bff\modules\site\models\Currency | \bff\db\illuminate\Model | array */ public function currency($id = null, $columns = ['*'], array $with = [], $lang = null) { $model = $this->model('Currency'); if (! empty($id)) { return $model->one($id, $columns, $with, $lang); } return $model; } /** * Currency listing * @return array */ public function currencyListing() { return $this->currency() ->orderBy('num') ->tag('site.currency.listing') ->get(['id', 'enabled', 'rate', 'title']) ->toArray(); } /** * Получаем данные о валюте (всех валютах) * @deprecated use {@see Currency::data()} * @param int|bool $mCurrencyID ID валюты или false - получаем данные о всех доступных валютах * @param bool $bEdit * @param bool $bEnabledOnly только включенные валюты * @return mixed */ public function currencyData($mCurrencyID, $bEdit = false, $bEnabledOnly = true) { if ($mCurrencyID === false) { $cacheKey = $this->locale->getCurrentLanguage() . '-' . ($bEnabledOnly ? 'enabled' : 'any'); if (isset($this->currencyDataCache[$cacheKey])) { return $this->currencyDataCache[$cacheKey]; } $query = $this->currency()->select(); if ($bEnabledOnly) { $query->enabled(); } $query->orderBy('num'); $query->tag('site.currency.data'); $this->currencyDataCache[$cacheKey] = $query->get()->keyBy('id')->toArray(); foreach ($this->currencyDataCache[$cacheKey] as $k => $v) { $this->currencyDataCache[$cacheKey][$k]['a'] = 0; } return $this->currencyDataCache[$cacheKey]; } else { if ($bEdit) { return $this->currency()->editAdmin($mCurrencyID)->toArray(); } else { return $this->currency($mCurrencyID)->toArray(); } } } # -------------------------------------------------------------------- # Пункты меню /** * Menu model * @param int|null $id * @param array $columns * @param array $with * @param string|null $lang * @return \bff\modules\site\models\Menu | \bff\db\illuminate\Model | array */ public function menu($id = null, $columns = ['*'], array $with = [], $lang = null) { $model = $this->model('Menu'); if (! empty($id)) { return $model->one($id, $columns, $with, $lang); } return $model; } /** * @param array $filter * @param array $opts * @return array|int */ public function menuListing(array $filter, array $opts = []) { $opts = $this->defaults($opts, [ 'countOnly' => false, 'oneArray' => false, 'fields' => [], 'orderBy' => 'num', 'limit' => 0, 'offset' => 0, 'cache' => 0, 'keyBy' => '', 'lang' => $this->locale->current(), ]); $lang = $opts['lang']; $query = $this->menu(); $query->langDefault($lang); $query = $query->where($filter); if ($opts['countOnly']) { return $query->count(); } $query->orderByRaw($opts['orderBy']); if ($opts['limit']) { $query->limit($opts['limit']); } if ($opts['offset']) { $query->offset($opts['offset']); } $fields = [ 'id', 'pid', 'group_key', 'item_key', 'is_system', 'type', 'target', 'enabled', 'style', 'url', 'title', 'icon', 'num', ]; if (! empty($opts['fields'])) { $fields = array_unique(array_merge($fields, $opts['fields'])); } $data = $query->get($fields); if (empty($data)) { return []; } if (! empty($opts['keyBy'])) { $data = $data->keyBy($opts['keyBy']); } $data = $data->toArray(); if ($opts['oneArray']) { $data = reset($data); return ! empty($data) ? $data : []; } return $data; } /** * Menu item save * @param int $id * @param array $data * @return mixed */ public function menuSave($id, $data = []) { if (empty($data)) { return false; } if ($id) { return $this->menu()->find($id)->fill($data)->save() ? $id : false; } else { $menu = $this->menu(); $menu->fill($data)->save(); return $menu->getKey(); } } /** * Menu item data * @param int $id * @return array */ public function menuData($id) { if (! $id) { return []; } $data = $this->menu()->editAdmin($id); if (is_object($data)) { $data = $data->toArray(); } return $data; } /** * Menu item delete * @param $id * @return false */ public function menuDelete($id) { if (empty($id)) { return false; } $data = $this->menuData($id); if ($data['type'] == Site::MENU_TYPE_MENU) { $items = $this->menuListing(['pid' => $data['id'], 'group_key' => $data['group_key']]); foreach ($items as $v) { $this->menuDelete($v['id']); } } $this->menu()->find($id)->delete(); return true; } /** * Sync menus items data with database records * @param array $menus @ref * @return void */ public function menusItemsDataSync(array &$menus) { if (empty($menus)) { return; } $records = $this->menuListing([], ['keyBy' => 'id', 'orderBy' => 'num']); foreach ($menus as &$menu) { if (empty($menu['items'])) { continue; } foreach ($menu['items'] as &$item) { $item['record_id'] = 0; $item['is_system'] = 1; /** Search for item record */ foreach ($records as $key => $record) { if ($record['group_key'] == $menu['id'] && $record['item_key'] == $item['id']) { $item['record_id'] = $record['id']; $item['record'] = $record; unset($records[$key]); break; } } /** No item record => create it */ if (empty($item['record_id'])) { $save = [ 'group_key' => $menu['id'], 'item_key' => $item['id'], 'enabled' => 1, 'is_system' => 1, 'type' => Site::MENU_TYPE_ITEM, 'target' => Site::MENU_TARGET_SELF, ]; if (! empty($item['newWindow'])) { $save['target'] = Site::MENU_TARGET_BLANK; } $id = $this->menuSave(0, $save); if ($id) { $item['record_id'] = $id; $item['record'] = $this->menuListing(['id' => $id], ['oneArray' => true]); } } $item['enabled'] = $item['record']['enabled'] ?? 1; $item['num'] = $item['record']['num'] ?? 0; } unset($item); } unset($menu); /** Prepare created items */ foreach ($menus as &$menu) { if (empty($menu['items'])) { continue; } foreach ($records as $key => & $record) { if ($record['group_key'] == $menu['id'] && empty($record['item_key'])) { $record['record'] = $record; $record['record_id'] = $record['id']; $record['is_system'] = 0; } } unset($record); } unset($menu); $records = $this->db->transformRowsToTree($records, 'id', 'pid', 'items'); /** Append created items to menu */ foreach ($menus as &$menu) { if (empty($menu['items'])) { continue; } foreach ($records as $key => $record) { if ($record['group_key'] == $menu['id'] && empty($record['item_key'])) { $menu['items'][ $record['id'] ] = $record; unset($records[$key]); } } /** Sort items by num */ uasort($menu['items'], function ($a, $b) { if (empty($a['record']['num']) || empty($b['record']['num']) || $a['record']['num'] == $b['record']['num']) { return 0; } return $a['record']['num'] < $b['record']['num'] ? -1 : 1; }); } unset($menu); } # -------------------------------------------------------------------- # Проверка запросов /** * Site request model * @param int|null $id * @param array $columns * @param array $with * @return \bff\modules\site\models\Request | \bff\db\illuminate\Model | array */ public function request($id = null, $columns = ['*'], array $with = []) { $model = $this->model('Request'); if (! empty($id)) { return $model->one($id, $columns, $with); } return $model; } /** * Помечаем дату и время последнего выполненного запрос для указанного действия + userID или IP * @param string $actionKey ключ действия * @param int $userID ID пользователя * @param string $ipAddress IP адрес запроса * @param int $retryCounter Счетчик кол-ва повторов */ public function requestSet($actionKey, $userID, $ipAddress, $retryCounter = 0) { $this->request()->fill([ 'user_action' => $actionKey, 'user_id' => $userID, 'user_ip' => $ipAddress, 'counter' => $retryCounter, ])->save(); } /** * Обновляем данные запроса по фильтру * @param array $filter фильтр * @param array $update данные для обновления * @return bool */ public function requestUpdate(array $filter, array $update) { if (empty($filter) || empty($update)) { return false; } return $this->request()->where($filter)->update($update); } /** * Получаем последний выполненный запрос для указанного действия + userID или IP * @param string $actionKey ключ действия * @param int $userID ID пользователя * @param string $ipAddress IP адрес запроса * @param bool $retryCounter проверка кол-ва попыток * @return int|array дата выполнения запроса(данные о последнем запросе) или 0 */ public function requestGet($actionKey, $userID, $ipAddress, $retryCounter = false) { $filter = [ 'user_action' => $actionKey, ]; if ($userID) { $filter['user_id'] = $userID; } else { $filter['user_ip'] = $ipAddress; } $query = $this->request()->where($filter)->tag('site.request.get'); if ($retryCounter) { $data = $query->orderBy('created', 'desc')->first(); return empty($data) ? 0 : $data->toArray(); } else { $data = $query->max('created'); return empty($data) ? 0 : strtotime($data); } } /** * Очистка неактуальных запросов */ public function requestsClear() { $this->request()->where('created', '<', date('Y-m-d H:i:s', strtotime('-2 days')))->delete(); } # -------------------------------------------------------------------- # Дополнительные настройки локали /** * Site languages model * @param int|null $id * @param array $columns * @param array $with * @return \bff\modules\site\models\Languages | \bff\db\illuminate\Model | array */ public function languages($id = null, $columns = ['*'], array $with = []) { $model = $this->model('Languages'); if (! empty($id)) { return $model->one($id, $columns, $with); } return $model; } /** * Получить данные о локалях * @return array */ public function languagesData() { $data = $this->languages() ->tag('site.languages.data') ->get() ->keyBy('lang') ->toArray(); foreach ($data as &$v) { if (empty($v['date_format'])) { $v['date_format'] = $this->locale->dateFormatDefault(); } $v['date_order'] = $this->locale->dateOrder($v['date_format']); if (! empty($v['icon'])) { $v['icon'] = tplForm::getFileURL($v['icon']); } } unset($v); return $data; } public function getLocaleTables() { return [ static::TABLE_PAGES => ['type' => 'table', 'fields' => $this->langPage, 'title' => _t('site', 'Pages')], static::TABLE_CURRENCIES => ['type' => 'table', 'fields' => $this->langCurrencies, 'title' => _t('site', 'Currencies')], ]; } }