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;
}
}