singletonIf('listings.item.images', function () { return new static(); }); /** @var static $i */ $i = bff('listings.item.images'); $i->setRecordID($itemID); $i->setUserID($userID ?? User::id()); return $i; } /** * Initial settings * @return void */ protected function initSettings() { $this->path = $this->app->path('items', 'images'); $this->pathTmp = $this->app->path('tmp', 'images'); $this->url = $this->app->url('items', 'images'); $this->urlTmp = $this->app->url('tmp', 'images'); $this->tableRecords = Listings::TABLE_ITEMS; $this->tableImages = Listings::TABLE_ITEMS_IMAGES; $this->hashFiles = true; # выполнять подсчет hash-суммы загружаемых файлов изображений $this->folderByID = true; # раскладываем файлы изображений по папкам (id(5)=>0, id(1005)=>1, ...) $this->filenameLetters = 8; # кол-во символов в названии файла $this->limit = Listings::itemsImagesLimit(); # лимит фотографий у объявления $this->maxSize = $this->config('listings.items.images.maxsize', 10485760, TYPE_UINT); # 2мб (2мб: 2097152, 5мб: 5242880) $this->minWidth = $this->config('listings.items.images.width.min', 150, TYPE_UINT); $this->minHeight = $this->config('listings.items.images.height.min', 150, TYPE_UINT); $this->maxWidth = $this->config('listings.items.images.width.max', 5000, TYPE_UINT); $this->maxHeight = $this->config('listings.items.images.height.max', 5000, TYPE_UINT); # настройки водяного знака $watermarkSettings = []; $settings = $this->config('listings.items.images.watermark.file', ''); if (! empty($settings)) { $src = Form::getImagePath($settings, 'o'); if ($src && File::exists($src)) { $watermarkSettings = [ 'watermark' => true, 'watermark_src' => $src, 'watermark_pos_x' => $this->config('listings.items.images.watermark.pos_x'), 'watermark_pos_y' => $this->config('listings.items.images.watermark.pos_y'), ]; } } $watermarkSettings = $this->app->filter('listings.items.images.wm', $watermarkSettings); # URL для динамической нарезки изображений. $this->urlResize = $this->app->filter('listings.items.images.urlResize', ''); # Images sizes $this->sizes = $this->app->filter('listings.items.images.sizes', [ static::szSmall => [ 'width' => 98, 'height' => false, 'vertical' => ['width' => false, 'height' => 98], ], static::szMedium => [ 'width' => 560, 'height' => false, 'vertical' => ['width' => false, 'height' => 446], 'quality' => 30, ], static::szView => [ 'width' => 670, 'height' => false, 'vertical' => ['width' => false, 'height' => 447] + $watermarkSettings, ] + $watermarkSettings, static::szZoom => [ 'width' => 1000, 'height' => false, 'vertical' => ['width' => false, 'height' => 670] + $watermarkSettings, ] + $watermarkSettings, static::szOrginal => [ 'o' => true, 'width' => 1000, 'height' => false, 'vertical' => ['width' => false, 'height' => 670], ], ], $watermarkSettings, $this); # размеры изображений, полный URL которых необходимо кешировать $this->useFav = true; foreach ([static::szSmall, static::szMedium] as $v) { # ключ размера => поле в базе $this->sizesFav[$v] = 'img_' . $v; } } /** * URL изображения по умолчанию * @param string $sizePrefix префикс размера * @return string URL */ public function urlDefault($sizePrefix) { if (! array_key_exists($sizePrefix, $this->urlDefaultCache)) { if (preg_match('/^([a-zA-Z0-9]+)\:([a-z]+)$/i', $sizePrefix, $matches) && !empty($matches[2])) { $this->urlDefaultCache[$sizePrefix] = $this->app->url('/files/images/items/def-' . $matches[1] . '.' . $matches[2]); } else { $this->urlDefaultCache[$sizePrefix] = $this->app->url('/files/images/items/def-' . $sizePrefix . '.png'); } } return $this->urlDefaultCache[$sizePrefix]; } /** * Получаем данные об изображениях указанных объявлений * @param array $itemsID ID объявлений * @return array массив параметров изображений сформированных: [itemID=>data, ...] */ public function getItemsImagesData(array $itemsID): array { if (empty($itemsID)) { return []; } $data = $this->db->select( 'SELECT * FROM ' . $this->tableImages . ' WHERE ' . $this->fRecordID . ' IN (' . join(',', $itemsID) . ') ORDER BY num' ); if (! empty($data)) { $data = func::array_transparent($data, $this->fRecordID, false); foreach ($data as &$images) { foreach ($images as &$image) { $image['vertical'] = ! ($image['width'] > $image['height']); } unset($image); } unset($images); } else { $data = []; } return $data; } /** * Проверяем наличие данных о загруженном изображении по ID изображения * @param int $imageID ID изображения * @return bool true - есть данные */ public function imageDataExists($imageID): bool { if (empty($imageID)) { return false; } $data = $this->db->one_data( 'SELECT id FROM ' . $this->tableImages . ' WHERE id = :imageID AND ' . $this->fRecordID . ' = :recordID LIMIT 1', [':imageID' => $imageID, ':recordID' => $this->recordID] ); return ! empty($data); } /** * Получаем дату самого последнего добавленного изображения * @param bool $buildHash сформировать hash на основе даты * @return int|string */ public function getLastUploaded(bool $buildHash = true) { $lastUploaded = $this->db->one_data( 'SELECT MAX(created) FROM ' . $this->tableImages . ' WHERE ' . $this->fRecordID . ' = :id LIMIT 1', [':id' => $this->recordID] ); if (!empty($lastUploaded)) { $lastUploaded = strtotime($lastUploaded); } else { $lastUploaded = mktime(0, 0, 0, 1, 1, 2000); } return ($buildHash ? $this->getLastUploadedHash($lastUploaded) : $lastUploaded); } /** * Формируем hash на основе даты самого последнего добавленного изображения * @param string $lastUploaded hash даты последнего загруженного изображения * @return string */ public function getLastUploadedHash($lastUploaded): string { $base64 = base64_encode($lastUploaded); return md5(strval($lastUploaded - 1000) . SITEHOST . $base64) . '.' . $base64; } /** * Выполняем проверку, загружались ли новые изображения * @param string $lastUploaded hash даты последнего загруженного изображения * @return bool */ public function newImagesUploaded($lastUploaded): bool { # проверка hash'a if (empty($lastUploaded) || ($dot = strpos($lastUploaded, '.')) !== 32) { return true; } $date = intval(base64_decode(mb_substr($lastUploaded, $dot + 1))); if ($this->getLastUploadedHash($date) !== $lastUploaded) { return true; } # выполнялась ли загрузка новых изображений if ($this->getLastUploaded(false) > intval($date)) { return true; } return false; } /** * Возвращает ключ максимального размера изображений * @return string */ public function getMaxSizeKey() { $sizes = $this->getSizes([static::szOrginal]); end($sizes); return key($sizes); } /** * Обновляем данные о изображении * @param int $imageID ID изображения * @param array $data * @return bool */ public function updateImageData($imageID, array $data): bool { if (empty($imageID) || empty($data)) { return false; } return $this->db->update($this->tableImages, $data, [ $this->tableRecords_id => $imageID, ]); } /** * Проверим существование изображений пользователя совпадающих по hash-сумме * @param array|string $hash * @return bool */ public function filesHashExists($hash) { if (! $this->hashFiles || empty($hash)) { return false; } if (! is_array($hash)) { $hash = [$hash]; } $distance = $this->config('images.hash.distance', 3, TYPE_UINT); foreach ($hash as $h) { $data = $this->db->one_array(' SELECT MIN(d) AS d, COUNT(id) AS cnt FROM ( SELECT I.id, BIT_COUNT(`hash_file` ^ :hash) AS d FROM ' . $this->tableImages . ' I, ' . $this->tableRecords . ' R WHERE I.user_id = :user AND I.' . $this->fRecordID . ' = R.id AND R.status != ' . Listings::STATUS_DELETED . ' AND I.' . $this->fRecordID . ' != :id ' . ($this->externalSave ? ' AND I.' . $this->fRecordID . ' != 0' : '') . ') s', [ ':id' => $this->recordID, ':user' => $this->userID, ':hash' => $h ]); if (! empty($data) && $data['cnt'] > 0 && $data['d'] < $distance) { return true; } } return false; } }