module_title = _t('banners', 'Banners'); $this->filesPath = $this->app->path('bnnrs'); $this->filesUrl = $this->app->url('bnnrs'); $this->defaultImagePath = $this->filesPath . 'default.gif'; } public function onNewRequest($request) { parent::onNewRequest($request); $this->viewQuery = ''; $this->cache = []; } /** * Типы баннеров * @param bool $edit * @return array */ public function types($edit = false): array { $data = [ static::TYPE_IMAGE => [ 't' => _t('banners', 'Image'), 'key' => 'image', 'image' => true, 'click_url' => true, 'target_blank' => true, ], static::TYPE_CODE => [ 't' => _t('banners', 'Code'), 'key' => 'code', 'image' => false, 'click_url' => true, 'target_blank' => false ], ]; foreach ($data as $k => &$v) { $v['id'] = $k; } unset($v); return $this->app->filter('banners.types', $data, ['edit' => $edit]); } /** * Данные о устройстве * @param string $device устройство, '' - текущее устройство * @return array */ public function deviceData(string $device = ''): array { if (empty($device)) { $device = bff::device(); } $devices = $this->app->devices(); $result = $devices[bff::DEVICE_DESKTOP] ?? []; if (isset($devices[$device])) { $result = $devices[$device]; if (! empty($result['alias']) && isset($devices[ $result['alias'] ])) { $result = $devices[ $result['alias'] ]; } } return $result; } /** * Устанавливаем строку поискового запроса * @param string $query строка поискового запроса */ public function viewQuery(string $query) { $this->viewQuery = $query; } /** * Получаем код баннера по ключу позиции * @param string $positionKey ключ позиции * @param array $settings доп. параметры * @return string HTML */ public function viewByPosition(string $positionKey, array $settings = []): string { $html = ''; do { $device = bff::device(); $devices = $this->app->devices(); # настройки позиции (по ключу позиции) $positions = $this->cache['positions'] ?? false; if (! $positions) { $positions = $this->cache['positions'] = $this->model->positionsList(); } $positionData = []; $positionID = 0; foreach ($positions as $v) { if ($v['keyword'] == $positionKey) { $positionData = $v; $positionID = $v['id']; } } if (!$positionID || empty($positionData) || !$positionData['enabled']) { break; } # баннеры на позиции (по ID позиции) $bannersList = $this->model->bannersList(array('pos' => $positionID)); if (empty($bannersList)) { break; } # фильтруем баннеры: $this->app->hook('banners.view.by.position.before', [ 'bannersList' => & $bannersList, 'positionData' => $positionData, 'settings' => $settings, ]); # 1. Разрешенные устройства foreach ($bannersList as $k => &$v) { if (! empty($v['devices']) && is_array($v['devices'])) { if (in_array(bff::DEVICE_ANY, $v['devices'])) { continue; } if (in_array($device, $v['devices'])) { continue; } unset($bannersList[$k]); } } unset($v); # 2. № позиции в списке if (isset($settings['list_pos'])) { if ($positionData['filter_list_pos'] && sizeof($bannersList) > 0) { foreach ($bannersList as $k => &$v) { if ($settings['list_pos'] == $v['list_pos']) { continue; } unset($bannersList[$k]); } unset($v); } if (empty($bannersList)) { break; } } # 3. Скрываем для авторизованных пользователей if (static::FILTER_AUTH_USERS && $positionData['filter_auth_users'] && User::logined()) { break; } # 4. Раздел сайта if ($positionData['filter_pages'] && sizeof($bannersList) > 0) { $bIndex = bff::isIndex(); $mainMenu = Site::menu('main'); $active = []; foreach ($mainMenu as $item) { $active[$item->id()] = $item->active(); } foreach ($bannersList as $k => &$v) { if (! $v['pages']) { continue; } if ( $v['pages_all'] || ($bIndex && $v['pages_index']) ) { continue; } foreach ($v['pages'] as $p) { if (! empty($active[ $p ])) { continue 2; } } unset($bannersList[$k]); } unset($v); } if (method_exists($this, 'viewByPositionFilter')) { $this->viewByPositionFilter($bannersList, $positionData, $settings); } # 5. URI if (static::FILTER_URL_MATCH && sizeof($bannersList) > 0 && ($URI = $this->request->uri())) { foreach ($bannersList as $k => &$v) { if (empty($v['url_match'])) { continue; } if (!$v['url_match_exact']) { # частичное совпадение if (mb_strpos($URI, $v['url_match']) === 0) { continue; } } else { # полное совпадение if ($URI == $v['url_match']) { continue; } } unset($bannersList[$k]); } unset($v); } # 6. Локализация if (static::FILTER_LOCALE && sizeof($bannersList) > 0) { $langCurrent = $this->locale->current(); foreach ($bannersList as $k => &$v) { if (empty($v['locale']) || in_array(static::LOCALE_ALL, $v['locale'])) { continue; } if (in_array($langCurrent, $v['locale'])) { continue; } unset($bannersList[$k]); } unset($v); } $this->app->hook('banners.view.by.position.filter', [ 'bannersList' => & $bannersList, 'positionData' => $positionData, 'settings' => $settings, ]); if (empty($bannersList)) { break; } # 7. Проверка количества одновременно отображаемых банеров $total = 1; $shuffle = false; do { # для "№ позиции в списке" всегда выбираем случайно 1 банер if (! empty($positionData['filter_list_pos'])) { $shuffle = true; break; } # если включена ротация выбираем случайно rotation_visible банеров if ($positionData['rotation']) { $shuffle = true; if ($positionData['rotation_visible'] > 1) { $total = $positionData['rotation_visible']; } break; } # если включен слайдер - берем все банеры if ($positionData['slider']) { $total = sizeof($bannersList); break; } # если не включен не один режим - берем первый банер } while (false); if ($shuffle) { shuffle($bannersList); } if (sizeof($bannersList) > $total) { $bannersList = array_slice($bannersList, 0, $total); } $slider = $positionData['slider'] && count($bannersList) > 1; foreach ($bannersList as $v) { $v['alt'] = HTML::escape($v['alt']); $v['title'] = HTML::escape($v['title']); $v['query'] = $this->viewQuery; $v['showURL'] = $this->url('show', ['id' => $v['id']]); $v['clickURL'] = $this->url('click', ['id' => $v['id']]); $v['position_width'] = $positionData['width']; $v['position_height'] = $positionData['height']; $d = $this->deviceData($device); if ($d['id'] == bff::DEVICE_PHONE) { $v['position_width'] = $positionData['width_mobile']; $v['position_height'] = $positionData['height_mobile']; } if ($v['type'] == static::TYPE_CODE) { $type_data = ''; if (isset($v['type_data']['code_' . $d['id']]['html'])) { $type_data = $v['type_data']['code_' . $d['id']]['html']; } if (empty($type_data)) { foreach ($devices as $vv) { if (isset($v['type_data']['code_' . $vv['id']]['html'])) { $type_data = $v['type_data']['code_' . $vv['id']]['html']; } } } $v['type_data'] = strtr($type_data, [ '{query}' => (!empty($this->viewQuery) ? HTML::escape($this->viewQuery, 'js') : ''), '{click_url}' => HTML::escape($v['clickURL']), '{show_url}' => HTML::escape($v['showURL']), ]); } elseif ($v['type'] == static::TYPE_IMAGE) { if (isset($v['type_data']['img_' . $d['id']])) { $type_data = $v['type_data']['img_' . $d['id']]; } if (empty($type_data)) { foreach ($devices as $vv) { if (isset($v['type_data']['img_' . $vv['id']])) { $type_data = $v['type_data']['img_' . $vv['id']]; } } } $v['img_data'] = $type_data ?? []; } $v['slider'] = $slider; if ($html && ! $slider) { $html .= '

'; } $html .= $this->template('view', $v, [ 'path' => $this->module_dir_tpl_core, ]); } if ($slider) { $data = []; if (is_array($positionData)) { $data = $positionData; } elseif (is_object($positionData) && method_exists($positionData, 'toArray')) { $data = $positionData->toArray(); } $data['slides'] = $html; $html = $this->template('view.slider', $data, [ 'path' => $this->module_dir_tpl_core, ]); } } while (false); return $html; } /** * Доступна ли ротация баннеров на указанной позиции * @param int $positionID ID позиции * @return bool */ public function checkPositionRotation($positionID) { $positionData = $this->model->positionData($positionID); if (empty($positionData)) { return false; } if ( empty($positionData['slider']) && !$positionData['rotation'] && $positionData['banners_enabled'] > 0 ) { return false; } return true; } /** * Загрузка файла изображения * @param int $bannerID ID баннера * @param int $positionID ID позиции * @param string $fieldName имя file-поля * @return bool|string */ protected function imgUpload($bannerID, $positionID, $fieldName = 'img') { $positionData = $this->model->positionData($positionID); if (empty($positionData)) { return false; } if (!isset($_FILES[$fieldName]) || $_FILES[$fieldName]['error'] == UPLOAD_ERR_NO_FILE) { return false; } $fileData = $_FILES[$fieldName]; $fileTemp = $fileData['tmp_name']; $fileExtension = Files::getExtension($fileData['name']); $fileExtensionSVG = ($fileExtension === 'svg'); $fileExtensionsAllowed = ['jpg', 'gif', 'png', 'svg']; if (!in_array($fileExtension, $fileExtensionsAllowed, true)) { $this->errors->set(_t('banners', 'Allowed image formats: [formats]', ['formats' => join(', ', $fileExtensionsAllowed)])); return false; } $fileName = func::generator(8) . '.' . $fileExtension; $imageSize = getimagesize($fileTemp); if (empty($imageSize) && ! $fileExtensionSVG) { return false; } $imageWidth = $imageSize[0] ?? 0; $szView = [ 'filename' => $this->buildPath($bannerID, $fileName, static::szView), ]; if ($this->input->post($fieldName . '_resize', TYPE_BOOL)) { # сохраняем, урезаем до требуемых размеров (настройки позиции) $szView['width'] = (!empty($positionData['width']) ? ($positionData['width'] * 2 /* x2 for retina */) : false); $szView['height'] = (!empty($positionData['height']) ? ($positionData['height'] * 2 /* x2 for retina */) : false); } else { # сохраняем в исходном размере $szView['original_sizes'] = true; } $thumbnailSettings = [ [ 'filename' => $this->buildPath($bannerID, $fileName, static::szThumbnail), 'width' => ($imageWidth > 100 ? 100 : $imageWidth), 'height' => false, ], $szView, ]; $thumbnail = new Thumbnail($fileTemp, false, false, ['svg' => $fileExtensionSVG]); if ($thumbnail->save($thumbnailSettings)) { return $fileName; } } /** * Удаление изображения баннера * @param int $bannerID ID баннера * @param string $fileName имя файла */ protected function imgDelete($bannerID, $fileName) { foreach ([static::szThumbnail, static::szView] as $sizePrefix) { $filePath = $this->buildPath($bannerID, $fileName, $sizePrefix); if (file_exists($filePath)) { unlink($filePath); } } } /** * Подготовка настроек баннера типа TYPE_FLASH * @param array|string $typeData настройки * @return array|mixed * @deprecated */ public function flashData($typeData) { return []; } /** * Строим полный путь к файлу баннера * @param int $bannerID ID баннера * @param string $fileName имя файла * @param string $filePrefix префикс файла * @return string */ public function buildPath($bannerID, $fileName, $filePrefix) { return $this->filesPath . $bannerID . $filePrefix . $fileName; } /** * Строим URL файла баннера * @param int $bannerID ID баннера * @param string $fileName имя файла * @param string $filePrefix префикс файла * @return string */ public function buildUrl($bannerID, $fileName, $filePrefix) { return $this->filesUrl . $bannerID . $filePrefix . $fileName; } /** * Удаление баннера / нескольких баннеров по ID * @param int|array $bannerID ID баннера / нескольких баннеров */ protected function deleteBanner($bannerID) { if (empty($bannerID)) { return; } if (is_array($bannerID)) { foreach ($bannerID as $id) { $this->deleteBanner($id); } return; } $banner = $this->model->banner($bannerID); if ($banner) { # удаляем изображение/flash switch ($banner['type']) { case static::TYPE_IMAGE: { $this->imgDelete($bannerID, $banner['img']); } break; case static::TYPE_TEASER: { $this->imgDelete($bannerID, $banner['img']); } break; } # удаляем баннер (+статистику) $banner->delete(); $this->model->cacheReset(); } } /** * Формируем список разделов меню "main" * @param array $opts * @return array */ public function getPages(array $opts = []) { $mainMenu = Site::menu('main'); $items = []; foreach ($mainMenu as $menu) { $items[] = [ 'id' => $menu->id(), 'title' => $menu->title(), ]; } array_unshift($items, [ 'id' => static::PAGE_INDEX, 'title' => _t('banners', 'Home Page'), ]); array_unshift($items, [ 'id' => static::PAGE_ANY, 'title' => _t('banners', 'All sections of the site'), ]); return $items; } /** * Формирование списка директорий/файлов требующих проверки на наличие прав записи * @return array */ public function writableCheck() { return array_merge(parent::writableCheck(), [ $this->filesPath => 'dir', # файлы баннеров ]); } /** * Определить размеры изображения * @param int $bannerID * @param array $opts * @return array */ public function imageWidthHeight($bannerID, array $opts = []) { $data = []; do { if (! $bannerID) { break; } $banner = $this->model->banner($bannerID); if ($banner['type'] != static::TYPE_IMAGE) { break; } $data = $banner['type_data'] ?? []; if (empty($data)) { break; } $devices = $this->app->devices(); foreach ($devices as $v) { if (! empty($v['alias'])) { continue; } $key = 'img_' . $v['id']; if (empty($data[$key]['name'])) { continue; } $name = $data[$key]['name']; $file = $this->buildPath($bannerID, $name, self::szView); if (! file_exists($file)) { continue; } $ext = Files::getExtension($file); $result = []; if ($ext === 'svg') { $dom = new DOMDocument(); $dom->preserveWhiteSpace = false; $res = $dom->load($file, LIBXML_NOBLANKS | LIBXML_NOERROR); if ($res === false) { continue; } if ($dom->documentElement->tagName !== 'svg') { continue; } $width = (string)$dom->documentElement->getAttribute('width'); $height = (string)$dom->documentElement->getAttribute('height'); if (empty($width) || empty($height)) { continue; } $result['width'] = (int)$width; $result['height'] = (int)$height; } else { $size = getimagesize($file); if (empty($size)) { continue; } $result['width'] = $size[0] ?? 0; $result['height'] = $size[1] ?? 0; } if (empty($result)) { continue; } $data[$key] = array_merge($data[$key], $result); } if (! empty($opts['update'])) { $this->model->bannerSave($bannerID, ['type_data' => $data]); } } while (false); return $data; } /** * Добавить позицию банера * @param string $keyword * @param string|array $title * @param string|int $width * @param string|int $height * @param array $data * @return int|bool */ public function positionAdd($keyword, $title, $width = '0', $height = '100', $data = []) { if (empty($keyword)) { return false; } $data = $this->defaults($data, [ 'keyword' => $keyword, 'title' => $this->locale->fill([], $title), 'width' => $width, 'height' => $height, 'width_mobile' => $width, 'height_mobile' => $height, 'rotation' => 1, 'rotation_visible' => 1, 'filter_pages' => 0, 'filter_auth_users' => 0, 'filter_region' => 0, 'filter_category' => 0, 'filter_category_module' => '', 'filter_list_pos' => 0, 'enabled' => 1 ]); $exist = $this->model->position()->where('keyword', $keyword)->count(); if ($exist) { return true; } return $this->model->positionSave(0, $data); } public function typeDataImg($data, $device = '') { $img = ''; if (isset($data['type_data']['img_' . $device]['name'])) { $img = $data['type_data']['img_' . $device]['name']; } if (empty($img)) { $devices = $this->app->devices(); foreach ($devices as $v) { if (isset($data['type_data']['img_' . $v['id']]['name'])) { $img = $data['type_data']['img_' . $v['id']]['name']; break; } } } return $img; } }