Listings.php 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047
  1. <?php
  2. /**
  3. *
  4. * PHP Pro Bid $Id$ 7TpWuUNndfHVwBDz1rUqzAoDsI0XT6AA/3NqEqrjIMY=
  5. *
  6. * @link http://www.phpprobid.com
  7. * @copyright Copyright (c) 2017 Online Ventures Software & CodeCube SRL
  8. * @license http://www.phpprobid.com/license Commercial License
  9. *
  10. * @version 7.10 [rev.7.10.02]
  11. */
  12. /**
  13. * listings table service class
  14. */
  15. namespace Ppb\Service;
  16. use Cube\Controller\Front,
  17. Cube\Controller\Request\AbstractRequest,
  18. Cube\Db\Select,
  19. Cube\Db\Expr,
  20. Ppb\Service,
  21. Ppb\Db\Table,
  22. Ppb\Form\Element\StockLevels,
  23. Ppb\Db\Table\Row\Listing as ListingModel,
  24. Ppb\Model\Shipping as ShippingModel;
  25. class Listings extends AbstractService
  26. {
  27. /**
  28. * number of seconds in a day
  29. */
  30. const DAY_SECONDS = 86400;
  31. /**
  32. * listings browse statuses
  33. */
  34. const STATUS_OPEN = 'open';
  35. const STATUS_CLOSE = 'close';
  36. const STATUS_RELIST = 'relist';
  37. const STATUS_DRAFTS_LIST = 'draftsList';
  38. const STATUS_ACTIVATE = 'activate';
  39. const STATUS_SUSPEND = 'suspend';
  40. const STATUS_APPROVE = 'approve';
  41. const STATUS_UNDELETE = 'undelete';
  42. const STATUS_DELETE = 'delete';
  43. /**
  44. * custom fields tables "type" column
  45. */
  46. const CUSTOM_FIELDS_TYPE = 'item';
  47. /**
  48. *
  49. * status message generated by the changeStatus() method
  50. *
  51. * @var array
  52. */
  53. protected $_statusMessages = array(
  54. self::STATUS_OPEN => "%s listings have been opened.",
  55. self::STATUS_CLOSE => "%s listings have been closed.",
  56. self::STATUS_RELIST => "%s listings have been relisted.",
  57. self::STATUS_DRAFTS_LIST => "%s drafts have been listed.",
  58. self::STATUS_ACTIVATE => "%s listings have been activated.",
  59. self::STATUS_SUSPEND => "%s listings have been suspended.",
  60. self::STATUS_APPROVE => "%s listings have been approved.",
  61. self::STATUS_UNDELETE => "%s listings have been undeleted.",
  62. self::STATUS_DELETE => "%s listings have been deleted.",
  63. );
  64. /**
  65. * select options
  66. */
  67. const SELECT_ADMIN = 'admin';
  68. const SELECT_MEMBERS = 'members';
  69. const SELECT_LISTINGS = 'listings';
  70. /**
  71. *
  72. * listings select types
  73. *
  74. * @var array
  75. */
  76. protected $_selectTypes = array(
  77. self::SELECT_ADMIN,
  78. self::SELECT_MEMBERS,
  79. self::SELECT_LISTINGS,
  80. );
  81. /**
  82. *
  83. * listings media (images etc) table service
  84. *
  85. * @var \Ppb\Service\ListingsMedia
  86. */
  87. protected $_listingsMedia;
  88. /**
  89. *
  90. * custom fields data table service
  91. *
  92. * @var \Ppb\Service\CustomFieldsData
  93. */
  94. protected $_customFieldsData;
  95. /**
  96. *
  97. * listing types available
  98. * allowed: auction, product, wanted, reverse, first_bidder
  99. *
  100. * @var array
  101. */
  102. protected $_listingTypes = array();
  103. /**
  104. *
  105. * class constructor
  106. */
  107. public function __construct()
  108. {
  109. parent::__construct();
  110. $this->setTable(
  111. new Table\Listings());
  112. }
  113. /**
  114. *
  115. * get custom fields data service
  116. *
  117. * @return \Ppb\Service\CustomFieldsData
  118. */
  119. public function getCustomFieldsDataService()
  120. {
  121. if (!$this->_customFieldsData instanceof Service\CustomFieldsData) {
  122. $this->setCustomFieldsDataService(
  123. new Service\CustomFieldsData());
  124. }
  125. return $this->_customFieldsData;
  126. }
  127. /**
  128. *
  129. * set custom fields data service
  130. *
  131. * @param \Ppb\Service\CustomFieldsData $customFieldsData
  132. *
  133. * @return \Ppb\Service\Listings
  134. */
  135. public function setCustomFieldsDataService(Service\CustomFieldsData $customFieldsData)
  136. {
  137. $this->_customFieldsData = $customFieldsData;
  138. return $this;
  139. }
  140. /**
  141. *
  142. * get status message
  143. *
  144. * @param string $status
  145. *
  146. * @return string|null
  147. */
  148. public function getStatusMessage($status)
  149. {
  150. if (array_key_exists($status, $this->_statusMessages)) {
  151. $translate = $this->getTranslate();
  152. return $translate->_($this->_statusMessages[$status]);
  153. }
  154. return $status;
  155. }
  156. /**
  157. *
  158. * get listings media service
  159. *
  160. * @return \Ppb\Service\ListingsMedia
  161. */
  162. public function getListingsMedia()
  163. {
  164. if (!$this->_listingsMedia instanceof Service\ListingsMedia) {
  165. $this->setListingsMedia(
  166. new Service\ListingsMedia());
  167. }
  168. return $this->_listingsMedia;
  169. }
  170. /**
  171. *
  172. * set the listings media service
  173. *
  174. * @param \Ppb\Service\ListingsMedia $listingsMedia
  175. *
  176. * @return $this
  177. */
  178. public function setListingsMedia(Service\ListingsMedia $listingsMedia)
  179. {
  180. $this->_listingsMedia = $listingsMedia;
  181. return $this;
  182. }
  183. /**
  184. *
  185. * get item types
  186. *
  187. * @return array
  188. */
  189. public function getListingTypes()
  190. {
  191. if (empty($this->_listingTypes)) {
  192. $this->setListingTypes();
  193. }
  194. return $this->_listingTypes;
  195. }
  196. /**
  197. *
  198. * set listing types array
  199. *
  200. * @param array $listingTypes
  201. *
  202. * @return $this
  203. */
  204. public function setListingTypes(array $listingTypes = null)
  205. {
  206. $translate = $this->getTranslate();
  207. if ($listingTypes === null) {
  208. $settings = $this->getSettings();
  209. if ($settings['enable_auctions']) {
  210. $listingTypes['auction'] = $translate->_('Auction');
  211. }
  212. if ($settings['enable_products']) {
  213. $listingTypes['product'] = $translate->_('Product');
  214. }
  215. }
  216. $this->_listingTypes = $listingTypes;
  217. return $this;
  218. }
  219. /**
  220. *
  221. * saves a listing (create or update)
  222. * will save data to any linked tables as well (like images, videos etc)
  223. * will also save shipping options serialized array
  224. *
  225. * 'partial' flag = set by any methods that might use the save method and which shouldn't alter flags
  226. * that are altered only when creating or editing the listing
  227. *
  228. * if the item has been edited and was already counted, subtract the category counters and reset the count flag
  229. *
  230. * @param array $post post array to be saved in the listings table
  231. *
  232. * @return int the id of the listing that was saved
  233. */
  234. public function save($post)
  235. {
  236. $row = null;
  237. $user = $this->getUser();
  238. $data = $this->_prepareSaveData($post);
  239. if (isset($user['id'])) {
  240. $data['user_id'] = $user['id'];
  241. }
  242. if (array_key_exists('id', $data)) {
  243. $select = $this->_table->select()
  244. ->where("id = ?", $data['id']);
  245. if (isset($data['user_id'])) {
  246. $select->where("user_id = ?", $data['user_id']);
  247. }
  248. $row = $this->findBy('id', $data['id'], false, true);
  249. unset($data['id']);
  250. }
  251. if (!isset($post['partial'])) {
  252. if (!$user->isAdmin()) {
  253. $data['approved'] = $this->_setApprovedFlag();
  254. }
  255. $postageSettings = array();
  256. foreach (ShippingModel::$postageFields as $key => $value) {
  257. if (isset($post[$key])) {
  258. $postageSettings[$key] = \Ppb\Utility::unserialize($post[$key]);
  259. }
  260. }
  261. // workaround for bulk lister dimensions field
  262. if (!empty($post['dimensions'])) {
  263. $dimensions = $post['dimensions'];
  264. if (count($dimensions) == 3 && !array_key_exists(ShippingModel::DIMENSION_LENGTH, $dimensions)) {
  265. $dimensions = array(
  266. ShippingModel::DIMENSION_LENGTH => $dimensions[0],
  267. ShippingModel::DIMENSION_WIDTH => $dimensions[1],
  268. ShippingModel::DIMENSION_HEIGHT => $dimensions[2],
  269. );
  270. }
  271. $postageSettings['dimensions'] = $dimensions;
  272. }
  273. $data['postage_settings'] = serialize($postageSettings);
  274. }
  275. $id = null;
  276. if (count($row) > 0) {
  277. $data['rollback_data'] = serialize($row);
  278. if (
  279. $row->isActiveAndOpen() &&
  280. $row->getData('list_in') != 'store' &&
  281. $row['last_count_operation'] == ListingModel::COUNT_OP_ADD
  282. ) {
  283. $row->countCategoriesCounter(ListingModel::COUNT_OP_SUBTRACT);
  284. $data['last_count_operation'] = ListingModel::COUNT_OP_NONE;
  285. }
  286. $data['updated_at'] = new Expr('now()');
  287. unset($data['user_id']);
  288. $this->_table->update($data, "id='{$row['id']}'");
  289. $id = $row['id'];
  290. }
  291. else if (!isset($post['partial'])) {
  292. $data['created_at'] = new Expr('now()');
  293. $this->_table->insert($data);
  294. $id = $this->_table->getAdapter()->lastInsertId();
  295. }
  296. if (!isset($post['partial'])) {
  297. // save all media corresponding to the listing in the listings_media table
  298. $this->getListingsMedia()->save($id, $post);
  299. // save custom fields data in the custom_fields_data table
  300. foreach ($post as $key => $value) {
  301. if (strstr($key, 'custom_field_')) {
  302. $fieldId = str_replace('custom_field_', '', $key);
  303. $this->getCustomFieldsDataService()->save(
  304. $value, self::CUSTOM_FIELDS_TYPE, $fieldId, $id);
  305. }
  306. }
  307. }
  308. return $id;
  309. }
  310. /**
  311. *
  312. * find a row on the listings table by querying a certain column
  313. * if it exists, fetch all linked data:
  314. * - media (images, etc)
  315. * - custom fields
  316. * - TBD
  317. * - postage settings (unserialize from the field)
  318. *
  319. * @param string $name column name
  320. * @param string $value column value
  321. * @param bool $strict if set to true, it will return the listing only if
  322. * the owner is the currently logged in user.
  323. * @param bool $enhanced if set to true, it will retrieve all related data as an array
  324. *
  325. * @return \Ppb\Db\Table\Row\Listing|null
  326. */
  327. public function findBy($name, $value, $strict = false, $enhanced = false)
  328. {
  329. $where = $this->getTable()->getAdapter()->quoteInto("{$name} = ?", $value);
  330. return $this->fetchAll($where, null, null, null, $strict, $enhanced)->getRow(0);
  331. }
  332. /**
  333. *
  334. * fetches all matched rows
  335. *
  336. * @param string|\Cube\Db\Select $where SQL where clause, or a select object
  337. * @param string|array $order
  338. * @param int $count
  339. * @param int $offset
  340. * @param bool $strict if set to true, it will return the listing only if
  341. * the owner is the currently logged in user.
  342. * @param bool $enhanced if set to true, it will retrieve all related data as an array
  343. *
  344. * @return \Ppb\Db\Table\Rowset\Listings
  345. */
  346. public function fetchAll($where = null, $order = null, $count = null, $offset = null, $strict = false, $enhanced = false)
  347. {
  348. /** @var \Ppb\Db\Table\Rowset\Listings $listings */
  349. $listings = parent::fetchAll($where, $order, $count, $offset);
  350. $user = $this->getUser();
  351. /** @var \Ppb\Db\Table\Row\Listing $listing */
  352. foreach ($listings as $key => $listing) {
  353. if ($strict === true && $listing['user_id'] != $user['id']) {
  354. $listings[$key] = null;
  355. }
  356. else if ($enhanced === true) {
  357. // listing media formatted data
  358. /** @var \Ppb\Db\Table\Rowset\ListingsMedia $listingsMediaRowset */
  359. $listingsMediaRowset = $listing->findDependentRowset('\Ppb\Db\Table\ListingsMedia', null,
  360. $this->getTable()->select()->order('order_id ASC'));
  361. $listingsMedia = $listingsMediaRowset->getFormattedData();
  362. foreach ($listingsMedia as $k => $v) {
  363. $listing[$k] = $v;
  364. }
  365. // custom fields data
  366. $customFieldsData = $this->getCustomFieldsData($listing['id']);
  367. foreach ($customFieldsData as $k => $v) {
  368. $listing['custom_field_' . $k] = $v;
  369. }
  370. $listings[$key] = $listing;
  371. }
  372. }
  373. return $listings;
  374. }
  375. /**
  376. *
  377. * creates and returns a new \Cube\Db\Select object used for selecting listings
  378. *
  379. * @param string $selectType the type of select to be created - admin, members, listings
  380. * @param \Cube\Controller\Request\AbstractRequest $request
  381. *
  382. * @throws \InvalidArgumentException
  383. * @return \Cube\Db\Select
  384. */
  385. public function select($selectType, AbstractRequest $request = null)
  386. {
  387. if (!$request instanceof AbstractRequest) {
  388. $request = Front::getInstance()->getRequest();
  389. }
  390. if (!in_array($selectType, $this->_selectTypes)) {
  391. throw new \InvalidArgumentException(
  392. sprintf("Invalid select type submitted. Allowed types: %s", implode(', ', $this->_selectTypes)));
  393. }
  394. $user = $this->getUser();
  395. $settings = $this->getSettings();
  396. $categoriesService = new Service\Table\Relational\Categories();
  397. $type = $request->getParam('type');
  398. $keywords = $request->getParam('keywords');
  399. $listingId = intval($request->getParam('listing_id'));
  400. $filter = $request->getParam('filter');
  401. /* listings sort drop-down */
  402. $sort = $request->getParam('sort');
  403. $showOnly = (array)$request->getParam('show_only');
  404. $listingTypes = (array)$request->getParam('listing_type');
  405. $country = $request->getParam('country');
  406. /* listings module specific params */
  407. $parentId = intval($request->getParam('parent_id'));
  408. $price = $request->getParam('price');
  409. $priceFrom = isset($price[\Ppb\Form\Element\Range::RANGE_FROM]) ? doubleval($price[\Ppb\Form\Element\Range::RANGE_FROM]) : null;
  410. $priceTo = isset($price[\Ppb\Form\Element\Range::RANGE_TO]) ? doubleval($price[\Ppb\Form\Element\Range::RANGE_TO]) : null;
  411. /* members module specific params */
  412. $show = $request->getParam('show');
  413. $userId = $request->getParam('user_id');
  414. $select = $this->getTable()->getAdapter()
  415. ->select()
  416. ->from(array('l' => 'listings'), '*');
  417. $listingTypes = array_filter($listingTypes);
  418. if (empty($listingTypes) || in_array('all', (array)$listingTypes)) {
  419. $listingTypes = array_keys($this->getListingTypes());
  420. }
  421. $select->where("l.listing_type IN (?)", $listingTypes);
  422. switch ($selectType) {
  423. case self::SELECT_LISTINGS:
  424. if (!in_array($filter, array('open', 'closed', 'scheduled'))) {
  425. $filter = 'open';
  426. }
  427. if ($show != 'store') {
  428. $select->where('l.list_in != ?', 'store');
  429. }
  430. break;
  431. case self::SELECT_MEMBERS:
  432. $select->where('l.user_id = ?', $user['id']);
  433. break;
  434. }
  435. switch ($type) {
  436. case 'categories':
  437. if ($parentId) {
  438. $select->reset('order')
  439. ->where('l.catfeat = ?', 1)
  440. ->order(new Expr('rand()'));
  441. }
  442. break;
  443. case 'homepage':
  444. $select->reset('order')
  445. ->where('l.hpfeat = ?', 1)
  446. ->order(new Expr('rand()'));
  447. break;
  448. case 'recent':
  449. $select->reset('order')
  450. ->order('l.start_time DESC');
  451. break;
  452. case 'ending':
  453. $select->reset('order')
  454. ->where('l.end_time IS NOT NULL')
  455. ->order('l.end_time ASC');
  456. break;
  457. case 'popular':
  458. $select->reset('order')
  459. ->order('l.nb_clicks DESC');
  460. break;
  461. case 'seller-other-items':
  462. $select->reset('order')
  463. ->where('l.user_id = ?', (int)$userId)
  464. ->where('l.id != ?', (int)$request->getParam('current_listing_id'))
  465. ->order('l.start_time DESC');
  466. break;
  467. case 'seller':
  468. $select->reset('order')
  469. ->where('l.user_id = ?', (int)$userId)
  470. ->order('l.start_time DESC');
  471. }
  472. switch ($show) {
  473. case 'bids':
  474. $select->join(array('bids' => 'bids'), 'bids.listing_id = l.id', 'bids.id AS bid_id')
  475. ->group('l.id');
  476. break;
  477. case 'offers':
  478. $select->join(array('o' => 'offers'), "o.listing_id = l.id AND o.type='offer'", 'o.id AS offer_id')
  479. ->group('l.id');
  480. break;
  481. case 'sold':
  482. $select = $this->_addSelectPart('sold', $select);
  483. break;
  484. case 'pending':
  485. $select->join(array('o' => 'offers'), "o.listing_id = l.id AND o.type='offer'", 'o.id AS offer_id')
  486. ->joinLeft(array('s' => 'sales_listings'), "s.listing_id = l.id", 's.id AS sale_listing_id')
  487. ->where('s.id IS NULL')
  488. ->group('l.id');
  489. break;
  490. case 'unsold':
  491. $select->joinLeft(array('s' => 'sales_listings'), "s.listing_id = l.id", 's.id AS sale_listing_id')
  492. ->where('s.id IS NULL')
  493. ->group('l.id');
  494. break;
  495. case 'store':
  496. $select->where('l.list_in != ?', 'site');
  497. if ($slug = $request->getParam('store_slug')) {
  498. $usersService = new Users();
  499. $store = $usersService->findBy('store_slug', $slug);
  500. if (!empty($store['id'])) {
  501. $userId = $store['id'];
  502. }
  503. }
  504. if ($userId) {
  505. $select->where('l.user_id = ?', $userId);
  506. }
  507. break;
  508. case 'other-items':
  509. $select->where('l.list_in != ?', 'store')
  510. ->where('l.user_id = ?', $userId);
  511. break;
  512. case 'featured':
  513. $select->where('l.hpfeat = ?', 1);
  514. break;
  515. case 'recent':
  516. if (empty($sort)) {
  517. $sort = 'started_desc';
  518. }
  519. break;
  520. case 'popular':
  521. if (empty($sort)) {
  522. $sort = 'clicks_desc';
  523. }
  524. break;
  525. case 'ending':
  526. $select->where('l.end_time <> ?', 0);
  527. if (empty($sort)) {
  528. $sort = 'ending_asc';
  529. }
  530. break;
  531. case 'by-user':
  532. $select->where('l.user_id = ?', $userId);
  533. }
  534. if (in_array('sold', $showOnly)) {
  535. $filter = 'sold';
  536. }
  537. if (in_array('accept_returns', $showOnly)) {
  538. $select->where('l.postage_settings LIKE ?', '%s:14:"accept_returns";s:1:"1"%');
  539. }
  540. if (in_array('make_offer', $showOnly)) {
  541. $select->where('l.enable_make_offer = ?', 1);
  542. }
  543. if ($country) {
  544. $select->where('l.country = ?', (int)$country);
  545. }
  546. if ($listingId) {
  547. $select->where('l.id = ?', $listingId);
  548. }
  549. if (!empty($keywords)) {
  550. $params = '%' . str_replace(' ', '%', $keywords) . '%';
  551. if (is_numeric($keywords)) {
  552. $select->where('(l.id = "' . intval($keywords) . '" OR l.name LIKE "' . $params . '" OR l.subtitle LIKE "' . $params . '")');
  553. }
  554. else {
  555. $keywords = explode(' ', $keywords);
  556. $conditions = array();
  557. $conditions[] = 'l.name LIKE "%1$s"';
  558. if ($settings['search_subtitle']) {
  559. $conditions[] = 'l.subtitle LIKE "%1$s"';
  560. }
  561. if ($settings['search_description']) {
  562. $conditions[] = 'l.description LIKE "%1$s"';
  563. }
  564. if ($settings['search_category_name']) {
  565. $select->joinLeft(array('mc' => 'categories'), 'mc.id = l.category_id', 'mc.full_name AS main_category_name');
  566. $conditions[] = 'mc.full_name LIKE "%1$s"';
  567. if ($settings['addl_category_listing']) {
  568. $select->joinLeft(array('ac' => 'categories'), 'ac.id = l.addl_category_id', 'ac.full_name AS addl_category_name');
  569. $conditions[] = 'ac.full_name LIKE "%1$s"';
  570. }
  571. }
  572. $cond = implode(' OR ', $conditions);
  573. foreach ((array)$keywords as $keyword) {
  574. $select->where('(' . sprintf($cond, '%' . $keyword . '%') . ')');
  575. // $select->where('(l.name LIKE "%' . $keyword . '%" OR l.subtitle LIKE "%' . $keyword . '%")'); // OBSOLETE
  576. }
  577. // REGEXP SOLUTION - SEARCHES FOR ANY KEYWORD - NOT GOOD
  578. // $select->where("l.name REGEXP '( )*(" . str_replace(' ', ')*( )*(', $keywords) . ")( )*'");
  579. }
  580. }
  581. if ($priceFrom > 0) {
  582. $select->where('l.start_price >= ?', $priceFrom);
  583. }
  584. if ($priceTo > 0) {
  585. $select->where('l.start_price <= ?', $priceTo);
  586. }
  587. switch ($filter) {
  588. case 'open':
  589. $select->where('l.closed = ?', 0)
  590. ->where('l.deleted = ?', 0);
  591. if ($selectType != self::SELECT_MEMBERS) {
  592. $select->where('l.active = ?', 1)
  593. ->where('l.approved = ?', 1);
  594. }
  595. break;
  596. case 'closed':
  597. $select->where('l.closed = ?', 1)
  598. ->where('l.deleted = ?', 0)
  599. ->where('l.end_time <= ?', new Expr('now()'));
  600. if ($selectType != self::SELECT_MEMBERS) {
  601. $select->where('l.active = ?', 1)
  602. ->where('l.approved = ?', 1);
  603. }
  604. if (empty($sort)) {
  605. $sort = 'ending_desc';
  606. }
  607. break;
  608. case 'scheduled':
  609. $select->where('l.closed = ?', 1)
  610. ->where('l.deleted = ?', 0)
  611. ->where('l.start_time > ?', new Expr('now()'));
  612. if ($selectType != self::SELECT_MEMBERS) {
  613. $select->where('l.active = ?', 1)
  614. ->where('l.approved = ?', 1);
  615. }
  616. break;
  617. case 'suspended':
  618. $select->where('l.active != ?', 1)
  619. ->where('l.approved = ?', 1);
  620. break;
  621. case 'awaiting_approval':
  622. $select->where('l.approved = ?', 0);
  623. break;
  624. case 'deleted':
  625. $select->where('l.deleted = ?', 1);
  626. break;
  627. case 'sold':
  628. $select = $this->_addSelectPart('sold', $select);
  629. break;
  630. }
  631. switch ($sort) {
  632. case 'price_asc':
  633. $select->order(new Expr("IF(l.listing_type='product',l.buyout_price, IF(max(b.amount) is null, l.start_price, max(b.amount))) ASC"))
  634. ->joinLeft(array('b' => 'bids'), 'b.listing_id = l.id', new Expr('max(b.amount) as current_bid'))
  635. ->group('l.id');
  636. break;
  637. case 'price_desc':
  638. $select->order(new Expr("IF(l.listing_type='product',l.buyout_price, IF(max(b.amount) is null, l.start_price, max(b.amount))) DESC"))
  639. ->joinLeft(array('b' => 'bids'), 'b.listing_id = l.id', new Expr('max(b.amount) as current_bid'))
  640. ->group('l.id');
  641. break;
  642. case 'started_asc':
  643. $select->order('l.start_time ASC');
  644. break;
  645. case 'started_desc':
  646. $select->order('l.start_time DESC');
  647. break;
  648. case 'ending_asc':
  649. // 7.8: nulls last
  650. $select->order('-l.end_time DESC');
  651. break;
  652. case 'ending_desc':
  653. $select->order('l.end_time DESC');
  654. break;
  655. case 'clicks_asc':
  656. $select->order('l.nb_clicks ASC');
  657. break;
  658. case 'clicks_desc':
  659. $select->order('l.nb_clicks DESC');
  660. break;
  661. case 'relevance':
  662. $select->order('l.nb_clicks DESC');
  663. break;
  664. default:
  665. if (!$select->getPart('order')) {
  666. $select->order('l.created_at DESC');
  667. }
  668. break;
  669. }
  670. $select->where('l.draft = ?', ($filter == 'drafts') ? 1 : 0);
  671. /*
  672. * search by category
  673. *
  674. * @7.9: if no category is selected, do not display items from adult categories
  675. */
  676. $categoriesFilter = array(0);
  677. if ($parentId) {
  678. $categoriesIds = array_keys($categoriesService->getChildren($parentId, true));
  679. if ($settings['addl_category_listing']) {
  680. $select->where('l.category_id IN (?) OR l.addl_category_id IN (?)', $categoriesIds);
  681. }
  682. else {
  683. $select->where('l.category_id IN (?)', $categoriesIds);
  684. }
  685. $categoriesFilter = array_merge($categoriesFilter, array_keys(
  686. $categoriesService->getBreadcrumbs($parentId)));
  687. }
  688. else if ($selectType == self::SELECT_LISTINGS) {
  689. if ($settings['enable_adult_categories']) {
  690. $select->joinLeft(array('mct' => 'categories'), "l.category_id = mct.id", 'mct.adult')
  691. ->where('mct.adult = ? OR mct.adult is null', 0);
  692. if ($settings['addl_category_listing']) {
  693. $select->joinLeft(array('act' => 'categories'), "l.addl_category_id = act.id", 'act.adult')
  694. ->where('act.adult = ? OR act.adult is null', 0);
  695. }
  696. }
  697. }
  698. $customFieldsService = new CustomFields();
  699. $customFields = $customFieldsService->getFields(
  700. array(
  701. 'type' => 'item',
  702. 'active' => 1,
  703. 'searchable' => 1,
  704. 'category_ids' => $categoriesFilter,
  705. ))->toArray();
  706. // pre php 5.5 array_column workaround - equivalent to:
  707. // $customFields = array_column($customFields, 'id');
  708. $customFields = array_map(function ($element) {
  709. return $element['id'];
  710. }, $customFields);
  711. /*
  712. * custom fields search
  713. */
  714. $params = $request->getParams();
  715. foreach ($params as $key => $value) {
  716. if (preg_match('#^(custom_field_)#', $key) && !empty($value)) {
  717. $fieldId = str_replace('custom_field_', '', $key);
  718. if (in_array($fieldId, $customFields)) {
  719. $alias = 'cf' . $fieldId;
  720. $customFieldsSelect = null;
  721. if (is_string($value)) {
  722. if (!empty($value)) {
  723. // search using like, just like with keywords search
  724. //$customFieldsSelect = "{$alias}.value = '{$value}'"; OBSOLETE
  725. $value = explode(' ', $value);
  726. $customFieldsSelect = array();
  727. foreach ((array)$value as $val) {
  728. $customFieldsSelect[] = "{$alias}.value LIKE '%{$val}%'";
  729. }
  730. $customFieldsSelect = implode(' OR ', $customFieldsSelect);
  731. }
  732. }
  733. else if (is_array($value)) {
  734. $checkBoxSrc = array();
  735. $radioSrc = array();
  736. foreach ($value as $val) {
  737. if (!empty($val)) {
  738. $checkBoxSrc[] = $val;
  739. $radioSrc[] = "{$alias}.value = '{$val}'";
  740. }
  741. }
  742. if (count($checkBoxSrc) > 0) {
  743. $customFieldsSelect = "{$alias}.value REGEXP '\"" . implode('"|"',
  744. array_unique($checkBoxSrc)) . "\"' OR "
  745. . implode(' OR ', $radioSrc);
  746. }
  747. }
  748. if (!empty($customFieldsSelect)) {
  749. $select->join(array($alias => 'custom_fields_data'),
  750. "{$alias}.owner_id = l.id AND {$alias}.field_id = '" . (int)$fieldId . "' AND ({$customFieldsSelect})",
  751. $alias . '.id AS ' . $alias . '_id');
  752. }
  753. }
  754. }
  755. }
  756. return $select;
  757. }
  758. /**
  759. *
  760. * get the custom fields data of a certain listing
  761. *
  762. * @param integer $id
  763. *
  764. * @return array
  765. */
  766. public function getCustomFieldsData($id)
  767. {
  768. $result = array();
  769. // custom fields data
  770. $rowset = $this->getCustomFieldsDataService()->fetchAll(
  771. $this->getCustomFieldsDataService()->getTable()->select('value, field_id')
  772. ->where('type = ?', self::CUSTOM_FIELDS_TYPE)
  773. ->where('owner_id = ?', (int)$id));
  774. foreach ($rowset as $row) {
  775. $result[$row['field_id']] = \Ppb\Utility::unserialize($row['value']);
  776. }
  777. return $result;
  778. }
  779. /**
  780. *
  781. * prepare listing data for when saving to the table
  782. * if listing is scheduled, 'closed' = 1
  783. *
  784. * important: the daylight saving changes will automatically be calculated when setting the end time!
  785. * @7.9: if we have an unlimited duration set, then the duration field is set to 0, and if we have a custom end
  786. * time set then the duration field is set as null
  787. *
  788. * @param array $data
  789. *
  790. * @return array
  791. */
  792. protected function _prepareSaveData($data = array())
  793. {
  794. if (isset($data['id']) && empty($data['id'])) {
  795. unset($data['id']);
  796. }
  797. if (isset($data['addl_category_id']) && empty($data['addl_category_id'])) {
  798. $data['addl_category_id'] = new Expr('null');
  799. }
  800. if (!empty($data['buyout_price']) && $data['listing_type'] == 'product') {
  801. $data['start_price'] = $data['buyout_price'];
  802. }
  803. $keysToUnset = array('rollback_data', 'active', 'approved', 'closed', 'deleted', 'nb_clicks');
  804. foreach ($keysToUnset as $keyToUnset) {
  805. if (array_key_exists($keyToUnset, $data)) {
  806. unset($data[$keyToUnset]);
  807. }
  808. }
  809. $startTime = time();
  810. if (isset($data['start_time_type'])) {
  811. switch ($data['start_time_type']) {
  812. case 1: // custom
  813. $startTime = strtotime($data['start_time']);
  814. $data['start_time'] = date('Y-m-d H:i:s', $startTime);
  815. $data['closed'] = 1;
  816. break;
  817. default: // now
  818. $data['start_time'] = new Expr('now()');
  819. break;
  820. }
  821. }
  822. else if (isset($data['start_time'])) {
  823. $startTime = strtotime($data['start_time']);
  824. }
  825. $endTime = null;
  826. $endTimeType = isset($data['end_time_type']) ? $data['end_time_type'] : 0;
  827. switch ($endTimeType) {
  828. case 1: // custom
  829. $endTime = strtotime($data['end_time']);
  830. $data['duration'] = new Expr('null');
  831. break;
  832. default: // duration
  833. // the duration field when using the duration option must be NOT NULL
  834. $data['duration'] = (isset($data['duration'])) ? $data['duration'] : 0;
  835. if ($data['duration'] > 0) {
  836. $endTime = $startTime + $data['duration'] * 86400;
  837. }
  838. break;
  839. }
  840. if ($endTime) {
  841. $data['end_time'] = date('Y-m-d H:i:s', $endTime);
  842. }
  843. else {
  844. $data['end_time'] = new Expr('null');
  845. }
  846. if (!empty($data['stock_levels'])) {
  847. foreach ($data['stock_levels'] as $key => $value) {
  848. if (!empty($value[StockLevels::FIELD_OPTIONS])) {
  849. $data['stock_levels'][$key][StockLevels::FIELD_OPTIONS] = \Ppb\Utility::unserialize($value[StockLevels::FIELD_OPTIONS]);
  850. }
  851. if (!empty($value[StockLevels::FIELD_QUANTITY])) {
  852. $data['stock_levels'][$key][StockLevels::FIELD_QUANTITY] = abs(intval($value[StockLevels::FIELD_QUANTITY]));
  853. }
  854. }
  855. }
  856. return parent::_prepareSaveData($data);
  857. }
  858. /**
  859. *
  860. * return the approved flag that needs to be set for a certain listing
  861. * if the admin edits a listing, that listing will always be approved
  862. *
  863. * @return integer 1 if approved, 0 otherwise
  864. */
  865. protected function _setApprovedFlag()
  866. {
  867. $user = $this->getUser();
  868. if (!empty($user['id'])) {
  869. $settings = $this->getSettings();
  870. if ($settings['enable_listings_approval']) {
  871. return 0;
  872. }
  873. else if ($user['listing_approval']) {
  874. return 0;
  875. }
  876. }
  877. return 1;
  878. }
  879. /**
  880. *
  881. * add select part internal method
  882. *
  883. * @param string $type
  884. * @param \Cube\Db\Select $select
  885. *
  886. * @return \Cube\Db\Select
  887. */
  888. protected function _addSelectPart($type, Select $select)
  889. {
  890. switch ($type) {
  891. case 'sold':
  892. $select->join(array('sl' => 'sales_listings'), "sl.listing_id = l.id", 'sl.id AS sale_listing_id')
  893. ->join(array('s' => 'sales'), "s.id = sl.sale_id AND s.pending = 0", 's.id AS sale_id')
  894. ->order('s.created_at DESC')
  895. ->group('l.id');
  896. break;
  897. }
  898. return $select;
  899. }
  900. }