security->publicMethod($this, [
'catsList', 'searchSuggest', 'img', 'comments',
]);
}
/**
* Список выбора категорий
* @param string $listType тип списка
* @param array $opt параметры [
* 'parentID' - ID parent-категории
* 'query' - текст для поиска
* 'inputName' - [cat_id]
* ]
* @return mixed|void
*/
public function catsList($listType = '', array $opt = [])
{
$opt = $this->defaults($opt, [
'init' => false,
'parentID' => 0,
'query' => '',
]);
$parentID = $opt['parentID'];
$query = $opt['query'];
$showAll = false;
if ($this->isAJAX()) {
$listType = $opt['listType'] ?? $this->input->getpost('act', TYPE_STR);
$parentID = $this->input->post('parent', TYPE_UINT);
$query = $this->input->post('query', TYPE_STR);
$showAll = $this->input->post('showAll', TYPE_BOOL);
}
$catData = function ($id, $iconSize) {
return $this->model->catData($id, [
'id','pid','title','keyword','landing_url',
'numlevel','numleft','numright',
'icon_' . $iconSize . ' as icon',
'items', 'subs', 'settings',
]);
};
$url = function ($keyword, $landingUrl) use (&$query) {
return $this->url('items.search', ['keyword' => $keyword, 'landing_url' => $landingUrl, 'q' => $query]);
};
$rootID = static::CATS_ROOTID;
$icon = $this->categoryIcon();
$iconLarge = $icon::BIG;
$iconSmall = $icon::SMALL;
switch ($listType) {
case 'search': # фильтр категории
$selectedID = 0;
$showAll = Request::isPhone();
if ($parentID > $rootID) {
$parentData = $catData($parentID, $iconLarge);
if (!empty($parentData)) {
if (!$parentData['subs']) {
# в данной категории нет подкатегорий
# формируем список подкатегорий ее parent-категории
$parentData = $catData($parentData['pid'], $iconLarge);
if (!empty($parentData)) {
$selectedID = $parentID;
$parentID = $parentData['id'];
}
}
}
}
$data = $this->model->catsList($listType, $parentID, $iconLarge);
if (!empty($data)) {
$searchShowEmptyCats = $this->seoSearchFilterShowEmptyCats();
foreach ($data as $k => &$v) {
if (! $v['items'] && ! $searchShowEmptyCats && $v['pid'] > $rootID) {
unset($data[$k]);
}
$v['l'] = $url($v['k'], $v['lpu']);
$v['i'] = $icon->url($v['id'], $v['i'], $iconLarge);
$v['active'] = ($v['id'] == $selectedID);
}
unset($v);
}
$tplData = [
'cats' => $data,
'query' => $query,
'showAll' => $showAll,
'langViewAll' => _t('filter', 'view all listings'),
'urlViewAll' => Listings::url('items.search', ['q' => $query]),
'langAll' => _t('filter', 'All listings'),
];
if ($parentID > $rootID) {
if (!empty($parentData)) {
$parentData['link'] = $url($parentData['keyword'], $parentData['landing_url']);
$parentData['main'] = ($parentData['pid'] == $rootID);
if ($parentData['main']) {
$parentData['icon'] = $icon->url($parentData['id'], $parentData['icon'], $iconLarge);
} else {
# глубже второго уровня, получаем настройки основной категории
$parentsID = $this->model->catParentsID($parentData, false);
if (!empty($parentsID[1])) {
$main = $this->model->catData($parentsID[1], [
'id', 'icon_' . $iconLarge . ' as icon',
]);
$parentData['icon'] = $icon->url($parentsID[1], $main['icon'], $iconLarge);
}
}
$tplData['step'] = 2;
$tplData['parent'] = $parentData;
$data = $this->template('search/cats.desktop', $tplData);
if ($this->isAJAX()) {
return $this->ajaxResponseForm(['html' => $data]);
} else {
return $data;
}
} else {
$this->errors->impossible();
return $this->ajaxResponseForm(['html' => '']);
}
} else {
$geo = Geo::filter();
if (!empty($geo['id'])) {
$total = $this->model->itemsCountByFilter(['cat_id' => 0, 'region_id' => $geo['id'], 'delivery' => 0, 'no_region' => 0], false, true, 60);
$total += $this->model->itemsCountByFilter(['cat_id' => 0, 'region_id' => '0', 'delivery' => 0, 'no_region' => 1], false, true, 60);
$countryID = Geo::regionCountry($geo['id']);
if ($countryID) {
$total += $this->model->itemsCountByFilter(['cat_id' => 0, 'region_id' => $countryID, 'delivery' => 1, 'no_region' => 0], false, true, 60);
}
} else {
$total = $this->model->itemsCountByFilter(['cat_id' => 0, 'region_id' => 0, 'delivery' => 0, 'no_region' => 0], false, true, 60);
}
$tplData['step'] = 1;
$tplData['total'] = $total;
$tplData['showAll'] = false;
return $this->template('search/cats.desktop', $tplData);
}
case 'form': # форма объявления: выбор категории
if ($opt['init']) {
/**
* Формирование данных об основных категориях
* для jListingsItemForm.init({catsMain:DATA});
*/
$data = $this->model->catsList('form', $parentID, $iconSmall);
if (! empty($data)) {
foreach ($data as $k => $v) {
$data[$k]['i'] = $icon->url($v['id'], $v['i'], $iconSmall);
}
}
return $data;
}
$selectedID = 0;
if ($parentID > $rootID) {
$parentData = $catData($parentID, $iconSmall);
if (!empty($parentData)) {
if (!$parentData['subs']) {
# в данной категории нет подкатегорий
# формируем список подкатегорий ее parent-категории
$parentData = $catData($parentData['pid'], $iconSmall);
if (!empty($parentData)) {
$selectedID = $parentID;
$parentID = $parentData['id'];
}
}
}
}
$catsList = $this->model->catsList($listType, $parentID, $iconSmall);
if (!empty($catsList)) {
foreach ($catsList as $k => $v) {
$catsList[$k]['i'] = $icon->url($v['id'], $v['i'], $iconSmall);
$catsList[$k]['active'] = ($v['id'] == $selectedID);
}
}
if ($parentID > $rootID) {
if (!empty($parentData)) {
$parentData['main'] = ($parentData['pid'] == $rootID);
if ($parentData['main']) {
$parentData['icon'] = $icon->url($parentData['id'], $parentData['icon'], $iconSmall);
} else {
# глубже второго уровня, получаем иконку основной категории
$parentsID = $this->model->catParentsID($parentData, false);
if (!empty($parentsID[1])) {
$main = $this->model->catData($parentsID[1], [
'id', 'icon_' . $iconSmall . ' as icon',
]);
$parentData['icon'] = $icon->url($parentsID[1], $main['icon'], $iconSmall);
}
}
$data = ['cats' => $catsList, 'parent' => $parentData, 'step' => 2, 'showAll' => $showAll];
$data = $this->template('item/form.cat.select', $data);
if ($this->isAJAX()) {
return $this->ajaxResponseForm([
'html' => $data,
'cats' => $catsList,
'pid' => $parentData['pid'],
]);
} else {
return $data;
}
} else {
$this->errors->impossible();
return $this->ajaxResponseForm(['html' => '']);
}
} else {
$opt['cats'] = $catsList;
$opt['cat_id'] = $opt['cat_id'] ?? 0;
$opt['step'] = 1;
$opt['showAll'] = $showAll;
if ($opt['cat_id']) {
if (empty($opt['cat_data'])) {
$opt['cat_data'] = $this->itemFormByCategory($opt['cat_id']);
}
if (empty($opt['cat_path'])) {
$opt['cat_path'] = [];
$catPath = $this->model->catParentsData($opt['cat_id'], ['id','title','icon_s']);
foreach ($catPath as $v) {
$opt['cat_path'][$v['id']] = $v['title'];
}
$catParent = reset($catPath);
$opt['cat_data']['icon'] = $icon->url($catParent['id'], $catParent['icon_s'], $iconSmall);
}
}
return $this->template('item/form.cat.select', $opt);
}
case 'suggest': # форма объявления: выбор категории
if (empty($query)) {
return $this->ajaxResponseForm([]);
}
$pre = $this->input->post('pre', TYPE_UINT);
$fields = ['id', 'pid', 'title', 'numlevel', 'keyword', 'landing_url', 'subs', 'icon_' . $iconSmall . ' as i'];
$cats = $this->app->filter('listings.category.select.suggest', [], [
'query' => $query,
'pre' => $pre,
'fields' => $fields,
'parentID' => $parentID,
]);
if (empty($cats)) {
if ($pre) {
return $this->ajaxResponseForm([]);
}
$filter = [
'enabled' => 1,
':sub' => 'C.numright - C.numleft = 1',
':ft_q' => $query,
];
if ($parentID != static::CATS_ROOTID) {
$cat = $this->model->catData($parentID, ['numleft', 'numright']);
if (! empty($cat['numleft']) && ! empty($cat['numright'])) {
$filter[':pid'] = [
'C.numleft >= :left AND C.numright <= :right',
':left' => $cat['numleft'],
':right' => $cat['numright']
];
}
}
$cats = $this->model->catsDataByFilter($filter, $fields, ['limit' => 15]);
}
$parents = func::array_transparent($cats, 'id', true);
$pids = $parents;
while (! empty($pids)) {
$ids = [];
foreach ($pids as $v) {
$ids[ $v['pid'] ] = 1;
}
if (empty($ids)) {
break;
}
$pids = $this->model->catsDataByFilter([
'id' => array_keys($ids),
'numlevel' => ['>', 0],
], $fields);
$pids = func::array_transparent($pids, 'id', true);
$parents = $parents + $pids;
}
foreach ($cats as & $v) {
$pid = $v['pid'];
$p = [];
$v['mid'] = static::CATS_ROOTID;
$v['cats'] = [['id' => $v['id'], 't' => $v['title']]];
while (isset($parents[$pid])) {
$a = $parents[$pid];
$pid = $a['pid'];
$p[$pid] = $a['title'];
if (! empty($a['i']) && empty($v['i'])) {
$v['i'] = $icon->url($a['id'], $a['i'], $iconSmall);
}
$v['cats'][] = ['id' => $a['id'], 't' => $a['title']];
$v['mid'] = $a['id'];
}
$v['parents'] = array_reverse($p, true);
$v['cats'] = array_reverse($v['cats']);
} unset($v);
$data = [
'step' => 3,
'cats' => $cats,
'pre' => $pre,
];
return $this->ajaxResponseForm(['html' => $this->template('item/form.cat.select', $data)]);
}
}
/**
* Быстрый поиск объявлений по строке
*/
public function searchSuggest()
{
$limit = $this->config('listings.search.suggest.items.limit', 3, TYPE_UINT);
if (! $limit) {
return $this->ajaxResponseForm(['items' => [], 'cnt' => 0]);
}
$query = $this->input->post('q', [TYPE_NOTAGS, 'len' => 80]);
$query = $this->input->cleanSearchString($query, 80);
$f = $this->input->postm([
'c' => TYPE_UINT, # категория
'ct' => TYPE_UINT, # тип категории
]);
$data = [];
$filter = [
'is_publicated' => 1,
'status' => static::STATUS_PUBLICATED,
];
if ($f['c'] > 0 && $this->config('listings.search.suggest.items.category', false, TYPE_BOOL)) {
$filter[':cat-filter'] = $f['c'];
}
$regionID = Geo::filter('id'); # user
if ($regionID > 0) {
$filter[':region-filter'] = $regionID;
}
$filter[':query'] = $query;
$data['items'] = $this->model->itemsQuickSearch($filter, [
'limit' => $limit,
'orderBy' => 'publicated_order DESC',
]);
$data['cnt'] = sizeof($data['items']);
foreach ($data['items'] as &$v) {
if (sizeof($v['img']) > 4) {
$v['img'] = array_slice($v['img'], 0, 4);
}
} unset($v);
$data['items'] = $this->template('search/suggest', $data);
$data = $this->app->filter('listings.search.suggest', $data, ['query' => $query, 'f' => $f]);
return $this->ajaxResponseForm($data);
}
/**
* Страница результата добавления / редактирование / управления ОБ
* @return string
*/
public function status()
{
$action = $this->input->get('action', TYPE_STR);
$id = $this->input->get('id', TYPE_STR);
return $this->itemStatus($action, $id);
}
/**
* Страница результата добавления / редактирование / управления ОБ
* @param string $state ключ результата
* @param int $itemID ID объявления
* @param array $data дополнительные данные
* @return string|mixed HTML
*/
protected function itemStatus($state, $itemID, array $data = [])
{
$title = '';
do {
# получаем данные об объявлении
if (!$itemID) {
$state = false;
break;
}
$itemData = $this->model->itemData($itemID, [
'id','user_id','company_id','title','link',
'status','status_prev','activate_key',
'svc', 'cat_id1','cat_id','cat_id_virtual',
]);
if (!empty($itemData['cat_id_virtual'])) {
$itemData['cat_id'] = $itemData['cat_id_virtual'];
}
if (empty($itemData)) {
$state = false;
break;
};
$params = $this->input->getm([
'ak' => TYPE_STR,
'activated' => TYPE_STR,
'pub' => TYPE_BOOL,
'from' => TYPE_STR
]);
$redirect = $this->app->filter('listings.item.status.redirect', false, [
'params' => & $params,
'state' => $state,
'itemID' => $itemID,
'data' => $data,
'itemData' => $itemData,
]);
if ($redirect) {
return $this->redirect($redirect);
}
if ($state == 'new' || $state == 'edit') {
# проверяем владельца
$userID = User::id();
$itemUserID = $itemData['user_id'];
if ($userID && $itemUserID != $userID) {
break;
}
if ($itemData['status'] == static::STATUS_NOTACTIVATED) {
# проверка корректности перехода, по совпадению части подстроки ключа активации
$activateCodePart = $params['ak'];
if (stripos($itemData['activate_key'], $activateCodePart) === false) {
return $this->redirect()->route('index'); # не совпадают
}
# Шаг активации объявления + телефона
$registerPhone = Users::registerPhone([Users::REGISTER_TYPE_BOTH, Users::REGISTER_TYPE_PHONE]);
$data['new_user'] = false;
if (!$userID) {
# Запрещаем изменение номера телефона в случае если объявление добавляет
# неавторизованный пользователь от имени зарегистрированного с наличием одного и более активированных ОБ
$nUserItemsCounter = $this->model->itemsCount(['user_id' => $itemUserID, 'status' => ['!=',static::STATUS_NOTACTIVATED]]);
if ($nUserItemsCounter > 1) {
$data['new_user'] = true;
}
}
if ($registerPhone && $this->isAJAX()) {
$userData = Users::model()->userData($itemUserID, [
'email', 'name', 'activated', 'activate_key',
'phone_number', 'phone_number_verified',
]);
if (!$this->security->validateReferer() || empty($userData)) {
$this->errors->reloadPage();
return $this->ajaxResponseForm();
}
$response = [];
$userPhone = $userData['phone_number'];
$confirmation = new \modules\users\views\register\PhoneConfirmation();
switch ($this->input->postget('act')) {
# Проверка кода подтверждения
case 'code-validate':
{
$code = $this->input->postget('code', TYPE_NOTAGS);
if ($code != $userData['activate_key'] && mb_strtolower($code) !== $userData['activate_key']) {
$this->errors->set(_t('users', 'Incorrect verification code'), 'phone');
break;
}
# Активируем аккаунт + объявления
if (empty($userData['activated'])) {
$activated = Users::userActivate($itemUserID, [
'verifyPhone' => true,
# Generate password because no password input in item add form
'password' => ($password = $this->security->generatePassword()),
]);
if ($activated) {
# Авторизуем
if (! $userID) {
Users::authById($itemUserID);
}
# Отправляем письмо об успешной регистрации
Users::sendPhoneRegistrationNotification($itemUserID, $userData['email'], $userData['phone_number'], $password);
} else {
$this->errors->set(_t('users', 'Registration error, contact the administrator'));
break;
}
} else {
# Активируем объявления пользователя
$this->onUserRegistered($itemUserID);
# Авторизуем
if (! $userID) {
Users::authById($itemUserID);
}
# Помечаем успешное подтверждение номера телефона
if (! $userData['phone_number_verified']) {
Users::model()->userSave($itemUserID, ['phone_number_verified' => 1]);
}
}
$response['redirect'] = static::url('item.status', [
'action' => 'new',
'id' => $itemID,
'activated' => 1,
]);
} break;
# Повторная отправка кода подтверждения
case 'code-resend':
{
$confirmation->codeResend($itemUserID, $userPhone);
} break;
# Смена номера телефона
case 'phone-change':
{
if ($data['new_user']) {
$this->errors->reloadPage();
break;
}
$phone = $this->input->postget('phone', [TYPE_NOTAGS, 'len' => 30]);
if ($confirmation->phoneChange($itemUserID, $phone, $userPhone)) {
$response['phone'] = '+' . $phone;
}
} break;
}
return $this->ajaxResponseForm($response);
}
$state = 'new.notactivated' . ($registerPhone ? '.phone' : '');
$title = _t('listings', 'Thank you! It remains only to activate the listing!');
} elseif ($itemData['status'] == static::STATUS_BLOCKED) {
$state = 'edit.blocked.wait';
$title = _t('listings', 'You have successfully edited the listing!');
} else {
if ($state == 'new') {
$state = 'new.publicated';
if (!empty($data['activated']) || $params['activated']) {
# активировали
$title = _t('listings', 'You have successfully activated the listing!');
$this->app->hook('listings.item.status.new.activated', $itemID, $itemData);
} else {
$title = _t('listings', 'You have successfully created the listing!');
$this->app->hook('listings.item.status.new.created', $itemID, $itemData);
}
} else {
# изменился статус публикации
if ($params['pub']) {
if (
$itemData['status'] == static::STATUS_PUBLICATED &&
$itemData['status_prev'] == static::STATUS_PUBLICATED_OUT
) {
# опубликовали
$state = 'edit.publicated';
$title = _t('listings', 'You have successfully published the listing!');
} elseif (
$itemData['status'] == static::STATUS_PUBLICATED_OUT &&
$itemData['status_prev'] == static::STATUS_PUBLICATED
) {
# сняли с публикации
$state = 'edit.publicated.out';
$title = _t('listings', 'You have successfully removed the listing from the published!');
} else {
$state = 'edit.normal';
$title = _t('listings', 'You have successfully edited the listing!');
}
} else {
# отредактировали без изменения статуса
$state = 'edit.normal';
$title = _t('listings', 'You have successfully edited the listing!');
}
}
}
} elseif ($state == 'promote.success') {
$title = _t('listings', 'Promotion of the listing');
} else {
return $this->errors->error404();
}
} while (false);
if ($state === false) {
return $this->errors->error404();
}
$data['user'] = Users::model()->userData($itemData['user_id'], ['email','phone_number','phone_number_verified','activated','name']);
$data['state'] = $state;
$data['item'] = & $itemData;
$data['back'] = Request::referer(Url::to());
$data['from'] = $params['from'];
$data['link_mylist'] = Listings::url('my.items', ['from' => 'add']);
if (! empty($itemData['company_id']) && bff::moduleExists('business')) {
$data['link_mylist'] = Business::url('my.listings', ['from' => 'add']);
}
$this->app->setMeta($title);
$this->seo()->robotsIndex(false);
return $this->showShortPage($title, $this->template('item/status', $data));
}
/**
* Продвижение объявления
*/
public function promote()
{
if (! bff::servicesEnabled('listings')) {
return $this->errors->error404();
}
$data = [];
$pageTitle = _t('listings', 'Promotion of the listing');
$from = $this->input->postget('from', TYPE_NOTAGS);
$userID = User::id();
$svc = $this->input->postget('svc', TYPE_STR);
$itemID = $this->input->getpost('id', TYPE_UINT); # ID объявления
if ($this->input->get('success')) {
return $this->itemStatus('promote.success', $itemID);
}
$itemData = $this->model->itemsList(['id' => $itemID], false, ['fields' => ['user_id','company_id','status','blocked_reason','cat_id','geo_city']]);
$itemData = reset($itemData);
if (empty($itemData['imgs'])) {
$images = $this->itemImages();
$itemData['img_s'] = $images->urlDefault($images::szSmall);
$itemData['img_m'] = $images->urlDefault($images::szMedium);
}
$payWaysList = Bills::getPayWaysList(!empty($userID), ['promotePage' => true]);
$manager = $this->itemServices();
$userBalance = User::balance();
if ($this->isPOST()) {
$response = [];
do {
$ps = $this->input->getpost('ps', TYPE_STR);
if (! $ps || !array_key_exists($ps, $payWaysList)) {
$this->errors->reloadPage();
break;
}
if (
! $itemID ||
empty($itemData) ||
in_array($itemData['status'], [
static::STATUS_BLOCKED, static::STATUS_DELETED, static::STATUS_PUBLICATED_OUT,
])
) {
$this->errors->reloadPage();
break;
}
if (empty($svc)) {
$this->errors->set(_t('listings', 'Select Service'));
break;
}
$response = $manager->activateOrPay($svc, $itemID, $userID, $ps, [
'redirect' => $this->url('item.promote', [
'id' => $itemID,
'success' => 1,
'from' => $from,
]),
]);
} while (false);
return $this->ajaxResponseForm($response);
}
# SEO
$this->seo()->robotsIndex(false);
$this->app->setMeta($pageTitle);
if (!$itemID || empty($itemData)) {
return $this->showForbidden($pageTitle, _t('listings', 'Listing not found, or the link is incorrect'));
}
# Проверка доступности возможности продвижения
if ($from !== 'new' && !$this->itemViewPromoteAvailable($this->isItemOwner($itemID, $itemData['user_id']))) {
return $this->showForbidden($pageTitle, _t('listings', 'Listing not found, or the link is incorrect'));
}
# проверяем статус ОБ
if ($itemData['status'] == static::STATUS_DELETED) {
return $this->showForbidden($pageTitle, _t('listings', 'Listing has been removed'));
} elseif ($itemData['status'] == static::STATUS_BLOCKED) {
return $this->showForbidden(
$pageTitle,
_t('listings', 'Listing was blocked by moderator, reason: [reason]', [
'reason' => $itemData['blocked_reason'],
])
);
} elseif ($itemData['status'] == static::STATUS_PUBLICATED_OUT) {
return $this->showForbidden(
$pageTitle,
_t('listings', 'You need to publish a listing for further promotion.')
);
}
$data['item'] = & $itemData;
# SEO: корректируем ссылку
$this->urlCorrection($this->url('item.promote'));
# способы оплаты
$data['curr'] = Currency::default();
$data['ps'] = &$payWaysList;
reset($payWaysList);
$data['ps_active_key'] = key($payWaysList);
foreach ($payWaysList as $k => &$v) {
$v['active'] = ($k == $data['ps_active_key']);
}
unset($v);
# список услуг
$services = $manager->getItemPromoteServices($itemID, $userID, $itemData);
foreach ($services as & $v) {
$v['active'] = $v['service_key'] == $svc;
if (! empty($v['disabled'])) {
$v['active'] = false;
}
} unset($v);
$data['services'] = & $services;
$data['user_balance'] = & $userBalance;
$data['svc'] = $svc;
$data['from'] = $from;
View::setPageData([
'listings_promote_id' => $itemID,
'listings_promote_data' => &$itemData,
]);
return $this->template('item/promote', $data);
}
/**
* Активация объявления
* @return string HTML
*/
public function activate()
{
$pageTitle = _t('listings', 'Listing activation');
$activateKey = $this->input->get('c', TYPE_STR); # ключ активации + ID объявления
list($activateKey, $itemID) = explode('_', (!empty($activateKey) && (strpos($activateKey, '_') !== false) ? $activateKey : '_'), 2);
$itemID = $this->input->clean($itemID, TYPE_UINT);
# SEO
$this->seo()->robotsIndex(false);
$this->app->setMeta($pageTitle);
$data = $this->model->itemData($itemID, [
'user_id','cat_id','company_id','status','activate_key',
'activate_expire','blocked_reason','publicated_period',
]);
if (empty($data)) {
# не нашли такого объявления
return $this->showForbidden(
$pageTitle,
_t('listings', 'Listing not found. Your activation link may have expired.', [
'link_add' => 'href="' . $this->url('item.add') . '"',
])
);
}
if ($data['activate_key'] != $activateKey || strtotime($data['activate_expire']) < microtime(true)) {
# ключ активации неверный или срок его действия истек
return $this->showForbidden(
$pageTitle,
_t('listings', 'The activation link has expired or it is not valid. Please add a new listing.', [
'link_add' => 'href="' . $this->url('item.add') . '"',
])
);
}
if ($data['status'] == static::STATUS_DELETED) {
# объявление было удалено
return $this->showForbidden($pageTitle, _t('listings', 'Listing has been removed'));
}
if ($data['status'] == static::STATUS_BLOCKED) {
# объявление было заблокировано
return $this->showForbidden(
$pageTitle,
_t('listings', 'Listing was blocked by moderator, reason: [reason]', [
'reason' => $data['blocked_reason'],
])
);
}
# 2. Получаем данные о пользователе:
$userData = Users::model()->userData($data['user_id'], [
'user_id','email','name','activated','blocked','blocked_reason',
]);
if (empty($userData)) {
# попытка активации объявления при отсутствующем профиле пользователя (публиковавшего объявление)
return $this->showForbidden($pageTitle, _t('listings', 'Activation error, contact support.'));
}
$userID = $userData['user_id'];
# аккаунт заблокирован
if ($userData['blocked']) {
return $this->showForbidden(
$pageTitle,
_t('listings', 'Your account is blocked. For detailed information, please contact support.')
);
}
# активируем аккаунт
if (! $userData['activated']) {
$userActivated = Users::userActivate($userID, [
'verifyEmail' => true,
'password' => ($password = $this->security->generatePassword()),
]);
if ($userActivated) {
Users::sendAutoRegistrationNotification($userID, $userData['name'], $userData['email'], $password);
}
} else {
$this->activateUserItems($userID, $itemID);
}
# авторизуем, если текущий пользователь неавторизован
if (User::guest()) {
Users::authById($userID);
}
return $this->itemStatus('new', $itemID, ['activated' => true]);
}
/**
* Список категорий для страницы "Карта сайта"
*/
public function catsListSitemap()
{
$iconSize = CategoryIcon::SMALL;
$data = $this->model->catsListSitemap($iconSize);
if (! empty($data)) {
foreach ($data as &$v) {
$v['link'] = $this->url('items.search', ['keyword' => $v['keyword'], 'landing_url' => $v['landing_url']]);
} unset($v);
$data = $this->db->transformRowsToTree($data, 'id', 'pid', 'subs');
$icon = $this->categoryIcon(0);
foreach ($data as &$v) {
$v['icon'] = $icon->url($v['id'], $v['icon'], $iconSize);
} unset($v);
}
return $data;
}
public function ajax()
{
$userID = User::id();
$response = [];
switch ($this->input->getpost('act', TYPE_STR)) {
# форма добавления/редактирования (в зависимости от настроек категорий)
case 'item-form-cat':
{
$categoryID = $this->input->post('id', TYPE_UINT);
$response['id'] = $categoryID;
$data = $this->itemFormByCategory($categoryID);
if (empty($data)) {
$this->errors->unknownRecord();
break;
} else {
if ($data['types'] === false) {
$data['types'] = [];
}
$data['types'] = HTML::selectOptions($data['types'], 0, false, 'id', 'title');
}
$response = array_merge($data, $response);
}
break;
# стоимость услуг для формы добавления/редактирования
case 'item-form-svc-block':
{
if (! bff::servicesEnabled()) {
break;
}
$response['html'] = $this->app->filter('listings.item.form.svc.block', '');
}
break;
# дин. свойства: child-свойства
case 'dp-child':
{
$p = $this->input->postm([
'dp_id' => TYPE_UINT, # ID parent-дин.свойства
'dp_value' => TYPE_UINT, # ID выбранного значения parent-дин.свойства
'name_prefix' => TYPE_NOTAGS, # Префикс для name
'search' => TYPE_BOOL, # true - форма поиска ОБ, false - форма доб/ред ОБ
'format' => TYPE_STR, # требуемый формат: 'f-desktop', 'f-phone', ''
]);
if (empty($p['dp_id']) && empty($p['dp_value'])) {
$this->errors->impossible();
} else {
$isFilter = (!empty($p['format']));
$data = $this->dp()->formChildByParentIDValue($p['dp_id'], $p['dp_value'], ['name' => $p['name_prefix'], 'class' => 'form-control'], $p['search'], ($isFilter ? false : 'form.child'));
if ($isFilter && !empty($data)) {
switch ($p['format']) {
case 'f-desktop':
case 'f-phone': {
$response = [
'id' => $data['id'],
'df' => $data['data_field'],
'multi' => $data['multi'],
];
}
break;
}
} else {
$response['form'] = $data;
}
}
}
break;
# избранные объявления (добавление / удаление) - для авторизованных
# для неавторизованных - процесс выполняется средствами javascript+cookie
case 'item-fav':
{
$itemID = $this->input->post('id', TYPE_UINT);
if (!$itemID || !$userID || !$this->isRequestValid()) {
$this->errors->reloadPage();
break;
}
$result = $this->favorites()->add($itemID, $userID);
if ($result !== false) {
$response['added'] = $result['favorite'];
$response['cnt'] = $result['total'];
}
}
break;
# смена статуса объявления
case 'item-status':
{
$response['alerts'] = [];
$itemID = $this->input->postget('id', TYPE_UINT);
if (!$itemID || !$userID || !$this->isRequestValid()) {
$this->errors->reloadPage();
break;
}
$data = $this->model->itemData($itemID, [
'user_id','company_id','cat_id','status',
'publicated_to','publicated_order','publicated_period',
]);
if (empty($data)) {
$this->errors->reloadPage();
break;
}
if (!$this->isItemOwner($itemID, $data['user_id'])) {
$this->errors->set(_t('listings', 'You are not the owner of this listing.'));
break;
}
$action = $this->input->getpost('status', TYPE_STR);
if (! $this->errors->no('listings.ajax.item-status', ['action' => $action, 'id' => $itemID, 'data' => &$data])) {
break;
}
switch ($action) {
case 'unpublicate': # снятие с публикации
{
if ($data['status'] != static::STATUS_PUBLICATED) {
$this->errors->reloadPage();
break;
}
$res = $this->model->itemSave($itemID, [
'status' => static::STATUS_PUBLICATED_OUT,
'status_prev' => $data['status'],
'publicated_to' => $this->db->now(),
]);
if (empty($res)) {
$this->errors->reloadPage();
} else {
$response['message'] = _t('listings', 'Listing was successfully removed from the published');
}
}
break;
case 'publicate': # публикация
{
$res = $this->itemPublicate($itemID);
if (empty($res)) {
if ($this->errors->no()) {
$this->errors->reloadPage();
}
} else {
$response['message'] = _t('listings', 'Listing was successfully published');
}
}
break;
case 'refresh': # продление публикации
{
if ($data['status'] != static::STATUS_PUBLICATED) {
$this->errors->reloadPage();
break;
}
if (
$this->limitsExceed([
'user_id' => $userID,
'company_id' => $data['company_id'],
'cat_id' => $data['cat_id'],
])
) {
break;
}
# от даты завершения публикации
$res = $this->model->itemSave($itemID, [
'publicated_to' => $this->itemPublicationPeriod()->refreshTo($data['publicated_to']),
]);
if (empty($res)) {
if ($this->errors->no()) {
$this->errors->reloadPage();
}
} else {
$response['message'] = _t('listings', 'The time of the listing was successfully extended');
}
}
break;
case 'delete': # удаление
{
$is_soon_left = (strtotime($data['publicated_to']) - time()) < static::PUBLICATION_SOON_LEFT;
if ($data['status'] == static::STATUS_PUBLICATED && ! $is_soon_left) {
$this->errors->set(_t('listings', 'To remove a listing, you must remove it from the published'));
break;
}
$response['message'] = _t('listings', 'Listing was successfully deleted');
$response['redirect'] = $this->url('my.items');
if ($data['status'] == static::STATUS_DELETED) {
break;
}
$res = $this->model->itemSave($itemID, [
# помечаем как удаленное + снимаем с публикации
'deleted' => 1,
'status' => static::STATUS_DELETED,
'status_prev' => $data['status'],
'publicated_to' => $this->db->now(),
]);
if (empty($res)) {
$this->errors->set(_t('listings', 'Could not remove listing, maybe this listing has already been deleted.'));
}
}
break;
}
# проверим превышение лимита, для вывода алерта в кабинете
$this->limitsExceed([
'user_id' => $userID,
'company_id' => $data['company_id'],
], [
'silent' => true,
'getAlert' => function ($html) use (&$response) {
$response['alerts'][] = $html;
}
]);
$response['alerts'] = join('', $response['alerts']);
}
break;
case 'item-svc-up-free': # бесплатное понятие объявления
{
$itemID = $this->input->post('id', TYPE_UINT);
$up = $this->itemServices('up');
if (
! $up ||
! $itemID ||
! $userID ||
! $this->isRequestValid() ||
! $this->isItemOwner($itemID)
) {
$this->errors->reloadPage();
break;
}
$response['message'] = $up->upFree($itemID, false);
}
break;
case 'item-svc-up-auto-form':
{
$itemID = $this->input->post('id', TYPE_UINT);
$up = $this->itemServices('up');
if (! $up || ! $itemID || ! $userID || !$this->isRequestValid()) {
$this->errors->reloadPage();
break;
}
$data = [];
$up->setActivationSettings($itemID, $userID);
$up->onActivationForm($data, ['form' => true]);
if (empty($data['form'])) {
$this->errors->impossible();
break;
}
$response['html'] = $data['form'];
}
break;
default:
{
$this->errors->impossible();
}
break;
}
return $this->ajaxResponseForm($response);
}
/**
* RSS Лента
* @return \bff\http\Response
*/
public function rss()
{
$category = $this->input->get('cat', TYPE_UINT);
$region = $this->input->get('region', TYPE_UINT);
$lang = $this->locale->current();
# SEO: проверка на обязательный завершающий слеш
$this->seo()->urlCorrectionEndSlash();
if (! $this->rssEnabled() || ! $category) {
return $this->errors->error404();
}
$catData = $this->model->catData($category, ['enabled']);
if (empty($catData['enabled'])) {
return $this->errors->error404();
}
# Формируем + кешируем
$minutes = $this->config('listings.rss.cache', 15, TYPE_UINT);
if ($minutes <= 0) {
$minutes = 15;
}
$data = Cache::remember(join('-', ['rss', $category, $region, $lang]), ($minutes * 60), function () use ($category, $region, $lang) {
return $this->rssGenerator($category, $region, $lang);
});
return Response::text($data, 200, [
'Content-Type' => 'application/rss+xml; charset=UTF-8',
'Last-Modified' => gmdate('D, d M Y H:i:s') . ' GMT',
]);
}
/**
* Формируем RSS ленту
* @param int $categoryID ID Категории
* @param int $regionID ID региона
* @param string $lng Ключ языка
* @return string
*/
protected function rssGenerator($categoryID, $regionID, $lng)
{
$sql = [
'is_publicated' => 1,
'status' => static::STATUS_PUBLICATED,
];
if ($categoryID > 0) {
$sql[':cat-filter'] = $categoryID;
}
if ($regionID) {
$sql[':region-filter'] = $regionID;
}
$limit = $this->config('listings.rss.limit', 30, TYPE_UINT);
if ($limit <= 0) {
$limit = 30;
}
$fields = ['I.descr'];
for ($i = $this->catsDepthLimit(); $i > 0; $i--) {
$fields[] = 'I.cat_id' . $i;
}
$data = $this->model->itemsList($sql, false, [
'context' => 'rss',
'orderBy' => 'publicated_order DESC, id DESC',
'limit' => $limit,
'favs' => false,
'lang' => $lng,
'fields' => $fields,
]);
$items = [];
if (! empty($data)) {
$cats = [];
foreach ($data as $v) {
for ($i = $this->catsDepthLimit(); $i > 0; $i--) {
if ($v['cat_id' . $i] && !in_array($v['cat_id' . $i], $cats)) {
$cats[] = $v['cat_id' . $i];
}
}
}
$cats = $this->model->catsDataByFilter(['id' => $cats], ['id','pid','title'], ['lang' => $lng]);
$cats = func::array_transparent($cats, 'id', true);
foreach ($data as $v) {
$catTitles = [];
for ($i = 1; $i <= $this->catsDepthLimit(); $i++) {
if ($v['cat_id' . $i] && isset($cats[ $v['cat_id' . $i] ])) {
$catTitles[] = $cats[ $v['cat_id' . $i] ]['title'];
}
}
$items[] = '