same listing * 'new' -> new listing */ const RELIST_SAME = 'same'; const RELIST_NEW = 'new'; /** * last count operation flags */ const COUNT_OP_NONE = 'none'; const COUNT_OP_ADD = 'add'; const COUNT_OP_SUBTRACT = 'subtract'; /** * operations */ const ADD = 'add'; const SUBTRACT = 'subtract'; /** * max chars for the short description method */ const SHORT_DESC_MAX_CHARS = 255; /** * * serializable fields * * @var array */ protected $_serializable = array('postage_settings', 'offline_payment', 'direct_payment'); /** * * array of available relist methods * * @var array */ protected $_relistMethods = array( self::RELIST_SAME, self::RELIST_NEW, ); /** * * relist method to be used * * @var string */ protected $_relistMethod; /** * * bids rowset * contains all bids placed on this listing * * @var \Ppb\Db\Table\Rowset\Bids */ protected $_bids = null; /** * * offers rowset * contains all offers posted on this listing * * @var \Ppb\Db\Table\Rowset\Offers */ protected $_offers = null; /** * * sales rowset * contains all sales transactions * * @var \Ppb\Db\Table\Rowset\Sales */ protected $_sales = null; /** * * bid increments table service * * @var \Ppb\Service\Table\BidIncrements */ protected $_bidIncrements; /** * * custom fields and custom fields data tables service * * @var \Ppb\Service\CustomFields */ protected $_customFields; /** * * sales listings table service * * @var \Ppb\Service\Table\SalesListings */ protected $_salesListings; /** * * custom fields data table service * * @var \Ppb\Service\CustomFieldsData */ protected $_customFieldsData; /** * * categories table service * * @var \Ppb\Service\Table\Relational\Categories */ protected $_categories; /** * * listing media rowset * * @var \Cube\Db\Table\Rowset\AbstractRowset */ protected $_listingsMedia; /** * * the id of a sale that was created as a result of a purchase * (buy out or assign winner) * * @var int */ protected $_saleId; /** * * tax type object that applies to the listing * or null if no tax will apply * * @var \Ppb\Db\Table\Row\TaxType|null */ protected $_taxType = null; /** * * closed flag * * @var bool */ protected $_closedFlag = false; /** * * class constructor * * @param array $data */ public function __construct(array $data = array()) { if (!isset($data['data']['id'])) { $data['data']['id'] = 0; } parent::__construct($data); } /** * * get bids rowset ordered from the highest to the lowest * * @return \Ppb\Db\Table\Rowset\Bids */ public function getBids() { if (!$this->_bids instanceof Rowset\Bids) { $select = $this->getTable()->select() ->order(array('amount DESC', 'id DESC')); $this->_bids = $this->findDependentRowset('\Ppb\Db\Table\Bids', null, $select); } return $this->_bids; } /** * * clear bids rowset * * @return $this */ public function clearBids() { $this->_bids = null; return $this; } /** * * get offers rowset * * @return \Ppb\Db\Table\Rowset\Offers */ public function getOffers() { if (!$this->_offers instanceof Rowset\Offers) { $select = $this->getTable()->select() ->order(array('id DESC')); $this->_offers = $this->findDependentRowset('\Ppb\Db\Table\Offers', null, $select); } return $this->_offers; } /** * * clear offers rowset * * @return $this */ public function clearOffers() { $this->_offers = null; return $this; } /** * * get sales listings table rowset * * @return \Ppb\Db\Table\Rowset\Sales */ public function getSales() { if (!$this->_sales instanceof Rowset\Sales) { $service = new Service\Sales(); $select = $service->getTable()->getAdapter()->select() ->from(array('l' => 'sales_listings')) ->joinLeft(array('s' => 'sales'), 'l.sale_id = s.id', 's.buyer_id') ->where('l.listing_id = ?', intval($this->getData('id'))) ->where('s.pending = ?', 0) ->order(array('l.id DESC')); $this->_sales = $service->fetchAll($select); } return $this->_sales; } /** * * clear sales rowset * * @return $this */ public function clearSales() { $this->_sales = null; return $this; } /** * * get bid increments table service * * @return \Ppb\Service\Table\BidIncrements */ public function getBidIncrements() { if (!$this->_bidIncrements instanceof Service\Table\BidIncrements) { $this->setBidIncrements( new Service\Table\BidIncrements()); } return $this->_bidIncrements; } /** * * set bid increments table service * * @param \Ppb\Service\Table\BidIncrements $bidIncrements * * @return $this */ public function setBidIncrements(Service\Table\BidIncrements $bidIncrements) { $this->_bidIncrements = $bidIncrements; return $this; } /** * * get custom fields table service * * @return \Ppb\Service\CustomFields */ public function getCustomFieldsService() { if (!$this->_customFields instanceof Service\CustomFields) { $this->setCustomFieldsService( new Service\CustomFields()); } return $this->_customFields; } /** * * set custom fields table service * * @param \Ppb\Service\CustomFields $customFields * * @return $this */ public function setCustomFieldsService(Service\CustomFields $customFields) { $this->_customFields = $customFields; return $this; } /** * * set sales listings table service * * @param \Ppb\Service\Table\SalesListings $salesListings * * @return $this */ public function setSalesListings($salesListings) { $this->_salesListings = $salesListings; return $this; } /** * @return \Ppb\Service\Table\SalesListings */ public function getSalesListings() { if (!$this->_salesListings instanceof Service\Table\SalesListings) { $this->setSalesListings( new Service\Table\SalesListings()); } return $this->_salesListings; } /** * * get categories table service * * @return \Ppb\Service\Table\Relational\Categories */ public function getCategories() { if (!$this->_categories instanceof Service\Table\Relational\Categories) { $this->setCategories( new Service\Table\Relational\Categories()); } return $this->_categories; } /** * * set categories table service * * @param \Ppb\Service\Table\Relational\Categories $categories * * @return $this */ public function setCategories(Service\Table\Relational\Categories $categories) { $this->_categories = $categories; return $this; } /** * * set sale id * * @param int $saleId * * @return $this */ public function setSaleId($saleId) { $this->_saleId = $saleId; return $this; } /** * * get sale id * * @return int */ public function getSaleId() { return $this->_saleId; } /** * * get custom fields data service * * @return \Ppb\Service\CustomFieldsData */ public function getCustomFieldsData() { if (!$this->_customFieldsData instanceof Service\CustomFieldsData) { $this->setCustomFieldsData( new Service\CustomFieldsData()); } return $this->_customFieldsData; } /** * * set custom fields data service * * @param \Ppb\Service\CustomFieldsData $customFieldsData * * @return $this */ public function setCustomFieldsData(Service\CustomFieldsData $customFieldsData) { $this->_customFieldsData = $customFieldsData; return $this; } /** * * get closed flag * * @return bool */ public function getClosedFlag() { return $this->_closedFlag; } /** * * retrieve the listing's selected media types or all if $types = null * if the listing has not been created yet, then use the $_data array, * otherwise get data from the listings_media table * * @param string|array|null $types * * @return array|\Ppb\Db\Table\Rowset\ListingsMedia */ public function getMedia($types = null) { $media = array(); if ($types !== null) { if (!is_array($types)) { $types = array($types); } } else { $types = Service\ListingsMedia::getTypes(); } // if we are creating a listing, we will use the $_data array if (!$this->getData('id')) { foreach ($types as $type) { $files = $this->getData($type); if ($files !== null) { if (!is_array($files)) { $this->addData($type, \Ppb\Utility::unserialize($files)); } $data = array_filter((array)$this->getData($type)); foreach ($data as $value) { $media[] = array( 'id' => null, 'value' => $value, 'type' => $type, ); } } } } else { /** @var \Ppb\Db\Table\Rowset\ListingsMedia $media */ $media = $this->findDependentRowset('\Ppb\Db\Table\ListingsMedia', null, $this->getTable()->select()->where('type IN (?)', $types) ->order('order_id ASC')); } return $media; } /** * * retrieve the listing's main image * * @param bool $absolutePath if true, return the full path of the image * * @return string|null */ public function getMainImage($absolutePath = false) { $images = $this->getMedia('image'); $image = null; if (count($images) > 0) { $image = $images[0]['value']; if ($absolutePath === true) { if (!preg_match('#^http(s)?://(.*)+$#i', $image)) { $settings = $this->getSettings(); $uploadsPath = $settings['site_path'] . \Ppb\Utility::URI_DELIMITER . \Ppb\Utility::getFolder('uploads'); $image = $uploadsPath . \Ppb\Utility::URI_DELIMITER . $image; } } } return $image; } /** * * get listing status (open/closed/scheduled) * * @return string */ public function getStatus() { if ($this->getData('start_time') > date('Y-m-d H:i:s', time())) { return self::SCHEDULED; } else if ($this->getData('closed')) { return self::CLOSED; } else { return self::OPEN; } } /** * * check if listing is active (and not a draft) * * @return bool */ public function isActive() { if ( $this->isDraft() === false && $this->getData('active') == 1 && $this->getData('approved') == 1 && $this->getData('deleted') != 1 ) { return true; } return false; } /** * * check if listing is open * * @return bool */ public function isOpen() { return ($this->getStatus() == self::OPEN) ? true : false; } /** * * check if listing is scheduled * * @return bool */ public function isScheduled() { return ($this->getStatus() == self::SCHEDULED) ? true : false; } /** * * check if listing is closed * * @return bool */ public function isClosed() { return ($this->getStatus() == self::CLOSED) ? true : false; } /** * * generate the meta description of the listing. * * it will be something like: {title} in {category} * * @return string */ public function getMetaDescription() { $breadcrumbs = implode(' > ', $this->getCategories()->getBreadcrumbs( $this->getData('category_id'))); return sprintf('%s in %s', $this->getData('name'), $breadcrumbs); } /** * * get relist method * * @return string */ public function getRelistMethod() { if (!$this->_relistMethod) { $settings = $this->getSettings(); $this->setRelistMethod( (isset($settings['relist_method'])) ? $settings['relist_method'] : null); } return $this->_relistMethod; } /** * * set relist method * * @param string $relistMethod * * @return $this */ public function setRelistMethod($relistMethod = null) { if (!in_array($relistMethod, $this->_relistMethods)) { $relistMethod = array_shift( array_values($this->_relistMethods)); } $this->_relistMethod = $relistMethod; return $this; } /** * * get the available quantity for this listing * this is calculated based on the quantity field minus and pending sales that have the listing added * if quantity = -1 - we have an unlimited quantity of items for the listing * * @param int|null $quantity initial quantity * @param array|null $productAttributes the product attributes for which to check stock levels * * @return int|true true if we have unlimited quantity */ public function getAvailableQuantity($quantity = null, $productAttributes = null) { $stockLevels = \Ppb\Utility::unserialize($this->getData('stock_levels')); $quantityAvailable = 0; if (!empty($stockLevels) && is_array($stockLevels) && $this->getData('listing_type') == 'product') { foreach ($stockLevels as $stockLevel) { if (is_array($stockLevel)) { if (array_key_exists('options', $stockLevel)) { $selected = \Ppb\Utility::unserialize($stockLevel['options']); if ($selected == $productAttributes) { $quantityAvailable = $stockLevel['quantity']; } } } } } else { $quantityAvailable = $this->getData('quantity'); } if ($quantityAvailable == self::UNLIMITED_QUANTITY) { return true; } $settings = $this->getSettings(); if ($settings['pending_sales_listings_expire_hours']) { $quantity += $quantityAvailable; $salesListings = $this->findDependentRowset('\Ppb\Db\Table\SalesListings'); /** @var \Ppb\Db\Table\Row\SaleListing $saleListing */ foreach ($salesListings as $saleListing) { $sale = $saleListing->findParentRow('\Ppb\Db\Table\Sales'); if ($sale['pending']) { $quantity -= $saleListing['quantity']; } } } else { $quantity = $quantityAvailable; } return ($quantity > 0) ? $quantity : 0; } /** * * returns the time left in seconds or 0 for time left or if this doesnt apply for the listing * * @return int */ public function getTimeLeft() { $timeLeft = strtotime($this->getData('end_time')) - time(); return ($timeLeft > 0) ? $timeLeft : 0; } /** * * get the tax type but only if it applies to the selected buyer * * @param \Ppb\Db\Table\Row\User $buyer buyer user model * @param int $billingAddressId billing address id, needed for a sale model * @param string $country country override * @param string $state state override * * @return bool|\Ppb\Db\Table\Row\TaxType */ public function getTaxType(User $buyer = null, $billingAddressId = null, $country = null, $state = null) { if (!$this->getData('apply_tax')) { $this->_taxType = false; } else if ($this->_taxType === null) { /** @var \Ppb\Db\Table\Row\User $seller */ $seller = $this->findParentRow('\Ppb\Db\Table\Users'); $taxType = false; if (($taxTypeId = $seller->canApplyTax()) != false) { $taxTypeService = new Service\Table\TaxTypes(); $taxTypeModel = $taxTypeService->findBy('id', $taxTypeId); if ($taxTypeModel instanceof TaxType) { $locationsIds = \Ppb\Utility::unserialize($taxTypeModel->getData('locations_ids')); if ($country === null && $state === null) { if ($buyer === null) { $buyer = $this->getUser(); } if ($buyer instanceof User) { $address = $buyer->getAddress($billingAddressId); if (isset($address['country']) && isset($address['state'])) { $country = $address['country']; $state = $address['state']; } } } if (in_array($country, $locationsIds) || in_array($state, $locationsIds) ) { $taxType = $taxTypeModel; } } } $this->_taxType = $taxType; } return $this->_taxType; } /** * * get listing owner * * @return \Ppb\Db\Table\Row\User|null */ public function getOwner() { /** @var \Ppb\Db\Table\Row\User $owner */ $owner = $this->findParentRow('\Ppb\Db\Table\Users'); return $owner; } /** * * get the maximum bid from an array of bids * * @param bool $current if set to true, include start_price in the array, * will display the current bid or start price * * @return float */ public function currentBid($current = false) { $amounts = array(0); if ($current === true) { $amounts[] = $this->getData('start_price'); } $bids = $this->getBids(); foreach ($bids as $bid) { $amounts[] = $bid['amount']; } return max($amounts); } /** * * get the latest bid for the selected user (normally for the logged in user) * * @param $userId * * @return \Ppb\Db\Table\Row\Bid|null */ public function yourBid($userId) { $bids = $this->getBids(); foreach ($bids as $bid) { if ($bid['user_id'] == $userId) { return $bid; } } return null; } /** * * return the minimum amount that can be bid on the listing * * @param bool $amount if set to true, it will calculate the minimum bid that will * need to be set, based on the amount input * the variable will always be higher than $bidAmount * * @return float */ public function minimumBid($amount = null) { $settings = $this->getSettings(); $bidAmount = $this->getData('start_price'); $this->clearBids(); if ($this->countDependentRowset('\Ppb\Db\Table\Bids') > 0) { $maximumBid = $this->currentBid(); $bidIncrement = $this->getData('bid_increment'); if ($bidIncrement > 0) { $bidAmount = $maximumBid + $bidIncrement; } else { $incrementsTable = $this->getBidIncrements()->getTable(); $incrementAmount = $incrementsTable->fetchRow( $incrementsTable->select('amount') ->where('tier_from <= ?', $maximumBid) ->where('tier_to > ?', $maximumBid) ); $bidAmount = $maximumBid + $incrementAmount['amount']; } } if ($amount !== null) { if ($amount < $bidAmount) { $amount = $bidAmount; } $reservePrice = $this->getData('reserve_price'); if ($bidAmount < $reservePrice) { $bidAmount = min(array( $reservePrice, $amount)); } if (!$settings['proxy_bidding']) { $bidAmount = $amount; } } return $bidAmount; } /** * * get payment methods available for the listing * convert old gateways field to new serialized field * * @param string $type type of payment methods to retrieve ('direct', 'offline' or null for all) * * @return array */ public function getPaymentMethods($type = null) { $result = array(); if (in_array($type, array(null, 'direct'))) { $paymentGatewaysService = new Service\Table\PaymentGateways(); $directPayment = \Ppb\Utility::unserialize($this->getData('direct_payment')); if (!is_array($directPayment)) { $directPayment = @explode(',', $directPayment); if (is_array($directPayment)) { $this->save(array( 'direct_payment' => serialize($directPayment), )); } } $directPaymentMethods = array_filter((array)$directPayment); if (count($directPaymentMethods) > 0) { // check if the direct payment gateway is still enabled by the seller $userId = $this->getData('user_id'); $rows = $paymentGatewaysService->getData($userId, $directPaymentMethods, true); foreach ($rows as $row) { $className = '\\Ppb\\Model\\PaymentGateway\\' . $row['name']; if (class_exists($className)) { /** @var \Ppb\Model\PaymentGateway\AbstractPaymentGateway $gatewayModel */ $gatewayModel = new $className($userId); if ($gatewayModel->enabled()) { $result[] = array( 'id' => $row['id'], 'type' => 'direct', 'name' => $row['name'], 'logo' => $row['logo_path'], ); } } } } } if (in_array($type, array(null, 'offline'))) { $offlinePaymentMethodsService = new Service\Table\OfflinePaymentMethods(); $offlinePayment = \Ppb\Utility::unserialize($this->getData('offline_payment')); if (!is_array($offlinePayment)) { $offlinePayment = @explode(',', $offlinePayment); if (is_array($offlinePayment)) { $this->save(array( 'offline_payment' => serialize($offlinePayment), )); } } $offlinePaymentMethods = array_filter((array)$offlinePayment); if (count($offlinePaymentMethods) > 0) { $select = $offlinePaymentMethodsService->getTable()->select() ->where('id IN (?)', new Expr(implode(', ', $offlinePaymentMethods))); $rows = $offlinePaymentMethodsService->fetchAll($select, array('order_id ASC', 'name ASC')); foreach ($rows as $row) { $result[] = array( 'id' => $row['id'], 'type' => 'offline', 'name' => $row['name'], 'logo' => $row['logo'], ); } } } return $result; } /** * * create an array of key => value from the custom fields and custom fields data tables * the custom fields data is available in the listing object when its created * * @param string $type * * @return array */ public function getCustomFields($type = self::CUSTOM_FIELDS_TYPE) { $result = array(); $categoryId = $this->getData('category_id'); $addlCategoryId = $this->getData('addl_category_id'); $categoriesFilter = array(0); if ($categoryId) { $categoriesFilter = array_merge($categoriesFilter, array_keys( $this->getCategories()->getBreadcrumbs($categoryId))); } if ($addlCategoryId) { $categoriesFilter = array_merge($categoriesFilter, array_keys( $this->getCategories()->getBreadcrumbs($addlCategoryId))); } $customFields = $this->getCustomFieldsService()->getFields(array( 'type' => $type, 'active' => 1, 'category_ids' => $categoriesFilter, )); $listingsService = new Service\Listings(); $customFieldsData = $listingsService->getCustomFieldsData($this->getData('id')); foreach ($customFields as $row) { foreach ($row as $key => $value) { if (($array = @unserialize($value)) !== false) { $row[$key] = $array; } } $customFieldValuePost = $this->getData('custom_field_' . $row['id']); if (!empty($customFieldValuePost)) { $row['value'] = $customFieldValuePost; } else if (!empty($customFieldsData[$row['id']])) { $row['value'] = $customFieldsData[$row['id']]; } else { $row['value'] = null; } $row['value'] = \Ppb\Utility::unserialize($row['value']); $rowDisplay = array(); $isMultiOptions = false; if (!empty($row['multiOptions'])) { if (count(array_filter($row['multiOptions']['key']))) { $isMultiOptions = true; } } if ($isMultiOptions === true) { foreach ((array)$row['value'] as $customFldValue) { if (($customFldKey = array_search($customFldValue, $row['multiOptions']['key'])) !== false) { $customFldValue = trim($row['prefix'] . ' ' . $row['multiOptions']['value'][$customFldKey] . ' ' . $row['suffix']); } if (!empty($customFldValue)) { $rowDisplay[] = $customFldValue; } } } else if (is_string($row['value'])) { $rowDisplay[] = trim($row['prefix'] . ' ' . $row['value'] . ' ' . $row['suffix']); } $row['display'] = $rowDisplay; $result[] = $row; } return $result; } /** * * returns an array used by the url view helper to generate the listing details page uri * * @return array */ public function link() { return array( 'module' => 'listings', 'controller' => 'listing', 'action' => 'details', 'id' => $this->getData('id'), 'name' => $this->getData('name') ); } /** * * check if the listing exists * should return false if it doesnt exist or if it is marked deleted and a user other * than its owner access the method, or if its inactive * * @param bool $extended if set to true, it will return false if the item is marked deleted or inactive * otherwise it will return false only if the item doesnt exist in the database * * @return bool */ public function exists($extended = true) { $listing = $this->getData(); if (isset($listing['id'])) { unset($listing['id']); } if (empty($listing)) { return false; } if ($extended) { $user = $this->getUser(); $userId = (isset($user['id'])) ? $user['id'] : null; if ($userId !== $listing['user_id'] && ($listing['deleted'] || ($listing['active'] != 1) || !$listing['approved']) ) { return false; } } return true; } /** * * check if the listing exists and if the logged in / specified user is the owner of the listing * * @param \Ppb\Db\Table\Row\User $user * * @return bool */ public function isOwner(User $user = null) { $exists = $this->exists(false); if ($exists === true) { if ($user === null) { $user = $this->getUser(); } $userId = (isset($user['id'])) ? $user['id'] : null; if ($userId !== $this->getData('user_id')) { return false; } } return $exists; } /** * * check if the listing exists and if the logged in / specified user has added it to the watch list * * @param \Ppb\Db\Table\Row\User $user * * @return bool */ public function isWatched(User $user = null) { $exists = $this->exists(false); if ($exists === true) { if ($user === null) { $user = $this->getUser(); } $bootstrap = Front::getInstance()->getBootstrap(); $session = $bootstrap->getResource('session'); $userToken = strval($session->getCookie(User::USER_TOKEN)); $userId = (isset($user['id'])) ? $user['id'] : null; $select = $this->getTable()->select(); if ($userId !== null) { $select->where('user_token = "' . $userToken . '" OR user_id = "' . $userId . '"'); } else { $select->where('user_token = ?', $userToken); } $countWatched = $this->countDependentRowset('\Ppb\Db\Table\ListingsWatch', null, $select); if ($countWatched) { return true; } } return false; } /** * * get the number of users watching the listing * * @return int */ public function countWatchers() { return $this->countDependentRowset('\Ppb\Db\Table\ListingsWatch'); } /** * * count the total number of bids and offers the logged in user has posted on the listing * * @return int */ public function getNbBidsOffers() { $user = $this->getUser(); $select = $this->getTable()->select() ->where('user_id = ?', $user['id']) ->group('maximum_bid'); $nbBids = $this->countDependentRowset('\Ppb\Db\Table\Bids', null, $select); $select = $this->getTable()->select() ->where('user_id = ?', $user['id']); $nbOffers = $this->countDependentRowset('\Ppb\Db\Table\Offers', null, $select); return ($nbBids + $nbOffers); } /** * * check if a purchase action can be validated, return true if valid or * an error message string otherwise * * @param string $type the type of purchase action requested to be validated (bid, buy, offer) * * @return bool|string */ public function canPurchase($type = 'bid') { $user = $this->getUser(); $translate = $this->getTranslate(); if (empty($user)) { return $translate->_('Please log in to access the purchase confirmation page.'); } else if ($user['id'] == $this->getData('user_id')) { return $translate->_('You are the owner of the listing.'); } else if (!$this->isActiveAndOpen()) { return $translate->_('The listing is closed.'); } else if (($blockMessage = $this->_isBlockedUserPurchasing()) !== false) { return $blockMessage; } $limitBidsOffersPerUser = $this->findParentRow('\Ppb\Db\Table\Users') ->getGlobalSettings('limit_bids_per_user'); switch ($type) { case 'bid': if ($this->getData('listing_type') == 'product') { return $translate->_('This listing is a product. No bids can be placed.'); } else if ($limitBidsOffersPerUser > 0) { if ($this->getNbBidsOffers() >= $limitBidsOffersPerUser) { $sentence = $translate->_('You have reached the maximum allowed number of bids/offers per user (%s).'); return sprintf($sentence, $limitBidsOffersPerUser); } } break; case 'buy': if ($this->isBuyOut() === false) { return $translate->_('Buy Out is disabled for this listing.'); } else if ($this->canAddToCart() === true) { return $translate->_('The product can only be purchased through a shopping cart.'); } break; case 'offer': if ($this->isMakeOffer() === false) { return $translate->_('Make Offer is disabled for this listing.'); } else if ($limitBidsOffersPerUser > 0) { if ($this->getNbBidsOffers() >= $limitBidsOffersPerUser) { $sentence = $translate->_('You have reached the maximum allowed number of bids/offers per user (%s).'); return sprintf($sentence, $limitBidsOffersPerUser); } } break; } return true; } /** * * check if the listing can be added to the shopping cart * or return an error message otherwise * * the owner can add his own products to the shopping cart, but check out is not allowed. * * @param int|null $quantity if quantity is set, check by quantity as well * @param array|null $productAttributes the product attributes for which to check stock levels * * @return bool|string */ public function canAddToCart($quantity = null, $productAttributes = null) { $settings = $this->getSettings(); $translate = $this->getTranslate(); if (!$settings['enable_shopping_cart']) { return $translate->_('The shopping cart module is disabled.'); } else if ($this->getData('listing_type') != 'product') { return $translate->_('Only products can be added to a shopping cart.'); } else if (!$this->isActiveAndOpen()) { return $translate->_('The listing is closed.'); } else if (($blockMessage = $this->_isBlockedUserPurchasing()) !== false) { return $blockMessage; } else { /** @var \Ppb\Db\Table\Row\User $seller */ $seller = $this->findParentRow('\Ppb\Db\Table\Users'); switch ($settings['shopping_cart_applies']) { case 'store_owners': if (!$seller->storeStatus(true)) { return $translate->_("The seller's store is disabled."); } break; case 'store_listings': if (!$seller->storeStatus(true) || $this->getData('list_in') == 'site') { return $translate->_('Only products listed in store can be added to the shopping cart.'); } break; } if ($quantity !== null) { $availableQuantity = $this->getAvailableQuantity(null, $productAttributes); if ($availableQuantity < $quantity) { return $translate->_('Cannot add to cart - not enough quantity available.'); } } } return true; } /** * * check if a pending offer can be accepted on a listing * * @param int $quantity * @param array|null $productAttributes the product attributes for which to check stock levels * * @return bool */ public function canAcceptOffer($quantity = 1, $productAttributes = null) { if ($this->isMakeOffer()) { $availableQuantity = $this->getAvailableQuantity(null, $productAttributes); if ($quantity <= $availableQuantity) { return true; } } return false; } /** * * drafts can be edited always * @7.10 scheduled listings can always be edited, just like drafts * * active open listings can be edited, if: * - for auctions: * no activity (bids, offers, sale). * the remaining duration needs to be greater than the minimum limit set in admin * - for products: * there can be activity, but in this case only selected fields can be edited * * @return bool */ public function canEdit() { if ($this->isDraft() || $this->isScheduled()) { return true; } $startTime = strtotime($this->getData('start_time')); if (!$this->isClosed() && $this->getData('active')) { // auctions ending time limit setting if ($this->isOpen() && $this->getData('listing_type') == 'auction') { $settings = $this->getSettings(); // the below snippet is needed for when checking if the listing can be edited right on the Listing\Create action $endTime = strtotime($this->getData('end_time')); if (!$endTime) { $endTime = $startTime + $this->getData('duration') * 86400; } $timeRemaining = $endTime - time(); if ($timeRemaining < $settings['auctions_editing_hours'] * 3600) { return false; } } return ($this->getData('listing_type') == 'product') ? true : !$this->hasActivity(); } return false; } /** * * check if the poster can close an open listing * products can be closed at any time, while auctions can be closed depending on the * close auction before end time setting * * @return bool */ public function canClose() { if ($this->isOpen()) { $settings = $this->getSettings(); if ($this->getData('listing_type') == 'auction') { if ($settings['close_auctions_end_time'] || !$this->hasActivity()) { return true; } } else { return true; } } return false; } /** * * a listing can be deleted if not scheduled/closed or for auctions if there is no activity * * @return bool */ public function canDelete() { if ($this->isOpen()) { if ($this->getData('listing_type') == 'auction' && $this->hasActivity()) { return false; } } return true; } /** * * check if there was any activity on the listing (bidding, offers, purchasing) * * @return bool */ public function hasActivity() { return (count($this->getBids()) || count($this->getOffers()) || count($this->getSales()) ) ? true : false; } /** * * check if buy out is enabled for the listing * * @return bool */ public function isBuyOut() { if ($this->getData('listing_type') == 'product') { return true; } else { $buyoutPrice = $this->getData('buyout_price'); if ($buyoutPrice > 0) { $settings = $this->getSettings(); if ($settings['enable_buyout']) { $maximumBid = $this->currentBid(); if ($settings['always_show_buyout'] && $maximumBid < $buyoutPrice) { return true; } else if (!$this->countDependentRowset('\Ppb\Db\Table\Bids') || $maximumBid < $this->getData('reserve_price')) { return true; } } } } return false; } /** * * check if the make offer feature is enabled for the listing * * @return bool */ public function isMakeOffer() { $settings = $this->getSettings(); if ($settings['enable_make_offer'] && $this->getData('enable_make_offer')) { return true; } return false; } /** * * check if the listing is active and open * * @return bool */ public function isActiveAndOpen() { if ($this->isActive() && $this->isOpen()) { return true; } return false; } /** * * determine and set the 'approved' flag for the listing * * @param int|null $value * * @return $this */ public function updateApproved($value = null) { $settings = $this->getSettings(); if ($value !== null) { $data['approved'] = $value; } else { if ($settings['enable_listings_approval']) { $data['approved'] = 0; $mail = new \Admin\Model\Mail\Admin(); $mail->listingApproval($this)->send(); } else { $data['approved'] = 1; } } $this->save($data); return $this; } /** * * update listing quantity field * * @param int $quantity * @param mixed $productAttributes * @param string $operation * * @return int remaining quantity (for stock levels it will return -1) */ public function updateQuantity($quantity, $productAttributes = null, $operation = self::SUBTRACT) { $stockLevels = \Ppb\Utility::unserialize($this->getData('stock_levels')); $productAttributes = \Ppb\Utility::unserialize($productAttributes); $quantityRemaining = self::UNLIMITED_QUANTITY; if (!empty($stockLevels) && $this->getData('listing_type') == 'product') { foreach ($stockLevels as $key => $stockLevel) { if ($stockLevel['options'] == $productAttributes) { $quantityAvailable = $stockLevel['quantity']; if ($quantityAvailable != self::UNLIMITED_QUANTITY) { $stockLevels[$key]['quantity'] = $this->_quantityOperator($quantityAvailable, $quantity, $operation); } } } $this->save(array( 'stock_levels' => serialize($stockLevels), )); } else { $quantityAvailable = $this->getData('quantity'); if ($quantityAvailable != self::UNLIMITED_QUANTITY) { $quantityRemaining = $this->_quantityOperator($quantityAvailable, $quantity, $operation); $this->save(array( 'quantity' => $quantityRemaining, )); } } return $quantityRemaining; } /** * * apply operations to the quantity field * * @param int $quantityAvailable * @param int $quantity * @param string $operation * * @return int */ protected function _quantityOperator($quantityAvailable, $quantity, $operation = self::ADD) { if ($quantityAvailable != self::UNLIMITED_QUANTITY) { return ($operation == self::ADD) ? ($quantityAvailable + $quantity) : ($quantityAvailable - $quantity); } return self::UNLIMITED_QUANTITY; } /** * * determine and set the 'active' flag for the listing * * @param int $active * * @return $this */ public function updateActive($active = 1) { $this->save(array( 'active' => (int)$active, )); return $this; } /** * * close a listing * when the cron closes the listing, the end time will not be modified. * * @param bool $automatic whether the listing is closed automatically (by the cron job) or not * * @return $this */ public function close($automatic = false) { $data = array(); if (strtotime($this->getData('start_time')) < time() && $this->getData('closed') == 0) { $data['closed'] = 1; if ($automatic === false) { $data['end_time'] = new Expr('now()'); } $this->save($data); $this->_closedFlag = true; } return $this; } /** * * relist a listing * * - the quantity field is calculated from the quantity sum in the sales listings table * and the listing quantity field * * @7.9 if duration === null => custom end time; else duration - if 0 = unlimited * * @param bool $autoRelist auto relist flag * * @return int the id of the new listing */ public function relist($autoRelist = false) { // calculate quantity $service = $this->getSalesListings(); if (!$this->getData('stock_levels')) { $quantity = $this->getTable()->getAdapter()->fetchOne( $service->getTable()->select(array('Qty' => new Expr('sum(quantity)'))) ->where('listing_id = ?', $this->getData('id')) ); $quantity += $this->getData('quantity'); } else { $quantity = 0; } // by default all relisted listings are activated [admin default] // for the front end, we will run the method that will charge setup fees and set the approval flag $params = array( 'start_time_type' => 0, 'closed' => 0, 'is_relisted' => 1, 'end_time_type' => 0, 'active' => 1, 'approved' => 1, 'deleted' => 0, 'nb_clicks' => 0, 'draft' => 0, 'counted_at' => new Expr('null'), 'last_count_operation' => self::COUNT_OP_NONE, 'quantity' => $quantity, ); $duration = $this->getData('duration'); if ($duration === null) { $difference = strtotime($this->getData('end_time')) - strtotime($this->getData('start_time')); $params['end_time_type'] = 1; $params['end_time'] = date('Y-m-d H:i:s', time() + (($difference < 86400) ? 86400 : $difference)); // minimum 1 day duration } else { $endTime = ($duration > 0) ? time() + $duration * 86400 : null; $params['end_time'] = ($endTime) ? date('Y-m-d H:i:s', $endTime) : new Expr('null'); } if ($autoRelist !== false) { $nbRelists = $this->getData('nb_relists') - 1; if ($nbRelists > 0) { $params['nb_relists'] = $nbRelists; } else { $params['nb_relists'] = 0; $params['auto_relist_sold'] = 0; } } $listingsService = new Service\Listings(); $listingsService->setUser( $this->findParentRow('\Ppb\Db\Table\Users')); $listingId = null; switch ($this->getRelistMethod()) { case 'new': $listing = $listingsService->findBy('id', $this->getData('id'), true, true); $data = $listing->getData(); unset($data['id']); if (!$autoRelist || !$this->getData('auto_relist_sold')) { $this->delete(); } // need to also copy custom fields and $listingId = $listingsService->save(array_merge($data, $params)); break; case 'same': $params['start_time'] = date('Y-m-d H:i:s', time()); $tableColumns = $this->getTable()->info(Table\AbstractTable::COLS); $params = array_intersect_key($params, array_flip(array_values($tableColumns))); $this->save($params); // delete all data from the bids, offers and sales_listings tables $dependentTables = array( '\Ppb\Db\Table\Bids', '\Ppb\Db\Table\Offers', ); if (!$autoRelist || !$this->getData('auto_relist_sold')) { $dependentTables[] = '\Ppb\Db\Table\SalesListings'; } foreach ($dependentTables as $dependentTable) { $rowset = $this->findDependentRowset($dependentTable); /** @var \Cube\Db\Table\Row $row */ foreach ($rowset as $row) { $row->delete(); } } $listingId = $this->getData('id'); break; } return $listingId; } /** * * processes post listing setup actions: * charges listing setup fees in account mode * activates and approves listing based on different settings * * @param \Ppb\Db\Table\Row\Listing $savedListing in case we edit a listing * * @return string returns any related output messages */ public function processPostSetupActions(Listing $savedListing = null) { /** @var \Cube\View $view */ $view = Front::getInstance()->getBootstrap()->getResource('view'); $message = null; /** @var \Ppb\Db\Table\Row\User $user */ $user = $this->findParentRow('\Ppb\Db\Table\Users'); $listingSetupService = new Service\Fees\ListingSetup( $this, $user); // apply listing setup voucher if available if ($voucherCode = $this->getData('voucher_code')) { $listingSetupService->setVoucher($voucherCode); $voucher = $listingSetupService->getVoucher(); if ($voucher instanceof Voucher) { $voucher->updateUses(); } } if ($savedListing instanceof Listing) { $listingSetupService->setSavedListing($savedListing); } $listingFees = $listingSetupService->calculate(); // apply listing setup fee $totalAmount = $listingSetupService->getTotalAmount(); $userPaymentMode = $user->userPaymentMode(); if ($totalAmount > 0 && $userPaymentMode == 'live') { $this->updateActive(0); } else { $this->updateActive(); if ($totalAmount > 0) { $user->updateBalance($totalAmount); $accountingService = new Service\Accounting(); $accountingService->setListingId($this->getData('id')) ->setUserId($user['id']) ->saveMultiple($listingFees); if ($view->isHelper('amount')) { $translate = $this->getTranslate(); $sentence = $translate->_('The amount of %s has been debited from your account balance.'); $message = sprintf($sentence, $view->amount($totalAmount)); } } } $this->updateApproved(); return $message; } /** * * process category counter for the listing object * will work based on the counted_at and last_count_operation flags and will be called by the cron job * * @param bool|string $force force count * * @return bool true if counted, false if unmodified */ public function processCategoryCounter($force = false) { $counted = false; $operation = self::COUNT_OP_ADD; if ($force === false) { if ($this->isActiveAndOpen()) { $operation = self::COUNT_OP_ADD; } else if ($this->getData('list_in') != 'store') { $operation = self::COUNT_OP_SUBTRACT; } } $lastCountOperation = $this->getData('last_count_operation'); if ( $force === true || ($operation == self::COUNT_OP_ADD && $lastCountOperation != self::COUNT_OP_ADD) || ($operation == self::COUNT_OP_SUBTRACT && $lastCountOperation == self::COUNT_OP_ADD) ) { $this->countCategoriesCounter($operation); $counted = true; } $this->save(array( 'last_count_operation' => $operation, 'counted_at' => date('Y-m-d H:i:s', time()), )); return $counted; } /** * * do the requested category counting operation * 7.2 - added a fix which was causing this method to loop indefinitely if a category that was to be counted didn't exist * 7.7 - only count the additional category if the setting is enabled in admin * * @param $operation * * @return $this */ public function countCategoriesCounter($operation) { $settings = $this->getSettings(); $ids = array( $this->getData('category_id'), ); if ($settings['addl_category_listing']) { $ids[] = $this->getData('addl_category_id'); } foreach ($ids as $id) { if ($id > 0) { do { /** @var \Ppb\Db\Table\Row\Category $category */ $category = $this->getCategories()->findBy('id', $id); $id = 0; if (count($category) > 0) { if ($operation == self::COUNT_OP_SUBTRACT) { $category->subtractCounter($this->getData('listing_type')); } else if ($operation == self::COUNT_OP_ADD) { $category->addCounter($this->getData('listing_type')); } $id = $category['parent_id']; } } while ($id > 0); } } return $this; } /** * * The method will post a bid, an offer or create a sale if the buy out method is used * * @param array $data place bid related data * @param string $type the type of purchase action (bid|buy|offer) * @param int $userId force user id * * @return string return message that is to be output */ public function placeBid(array $data, $type = 'bid', $userId = null) { $bootstrap = Front::getInstance()->getBootstrap(); if ($userId === null) { $user = $bootstrap->getResource('user'); $userId = $user['id']; } $view = $bootstrap->getResource('view'); $translate = $this->getTranslate(); $message = null; switch ($type) { case 'bid': $service = new Service\Bids(); $data['user_id'] = $userId; $data['listing_id'] = $this->getData('id'); $service->save($data); $message = $service->getMessage(); $usersService = new Service\Users(); $user = $usersService->findBy('id', $userId); $mail = new \Listings\Model\Mail\OwnerNotification(); $mail->newBid($this->getData('id'), $user, $data['amount'])->send(); break; case 'buy': $service = new Service\Sales(); $quantity = 1; if (isset($data['quantity'])) { $quantity = ($data['quantity'] > 0) ? $data['quantity'] : $quantity; } $price = $this->getProductPrice($data['product_attributes']); $data = array( 'buyer_id' => $userId, 'seller_id' => $this->getData('user_id'), 'postage_id' => (int)$data['postage_id'], 'shipping_address_id' => $data['shipping_address_id'], 'apply_insurance' => (bool)$data['apply_insurance'], 'voucher_details' => $data['voucher_details'], 'listings' => array( array( 'listing_id' => $this->getData('id'), 'price' => $price, 'quantity' => $quantity, 'product_attributes' => $data['product_attributes'], ) ), ); $service->save($data); $message = sprintf($translate->_('You have successfully purchased this item - price: %s, quantity purchased: %s.'), $view->amount($price, $this->getData('currency')), $quantity); $this->setSaleId( $service->getSaleId()); break; case 'offer': $service = new Service\Offers(); $data['user_id'] = $userId; $data['type'] = 'offer'; if (empty($data['receiver_id'])) { $data['receiver_id'] = $this->getData('user_id'); } $data['listing_id'] = $this->getData('id'); $quantity = 1; if (isset($data['quantity'])) { $quantity = ($data['quantity'] > 0) ? $data['quantity'] : $quantity; } $data['quantity'] = $quantity; $id = $service->save($data); $message = sprintf($translate->_('Your offer, in the amount of %s, has been posted successfully.'), $view->amount($data['amount'], $this->getData('currency'))); /** @var \Ppb\Db\Table\Row\Offer $row */ $row = $service->findBy('id', $id); $mail = new \Listings\Model\Mail\UserNotification(); $mail->newOffer($this, $row)->send(); break; } return $message; } /** * * get the price of the product based on the selected product attributes and their price variations * * @param mixed $productAttributes * * @return float */ public function getProductPrice($productAttributes = null) { $price = $this->getData('buyout_price'); $stockLevels = \Ppb\Utility::unserialize($this->getData('stock_levels')); $productAttributes = \Ppb\Utility::unserialize($productAttributes); if (!empty($stockLevels) && $this->getData('listing_type') == 'product') { foreach ($stockLevels as $key => $stockLevel) { if ($stockLevel['options'] == $productAttributes) { $price += $stockLevel['price']; break; } } } return $price; } /** * * this method assigns a set bid as a winning bid on a listing, or it assigns it automatically when: * - we have a standard auction with a high bid greater or equal to the reserve price * (fixed in V7.3, before it required the bid amount to be greater than the reserve) * - we have a first bidder auction (later) * * @param \Ppb\Db\Table\Row\Bid $bid the bid that should be assigned as winning bid, or null if the bid should be selected automatically * * @throws \RuntimeException * @return int|false the id of the resulted sale or false if no winner has been assigned */ public function assignWinner(Bid $bid = null) { if ($bid !== null) { if ($bid['listing_id'] !== $this->getData('id')) { throw new \RuntimeException("The listing id of the bid object inserted in the assignWinner() method is invalid"); } } else { $this->clearBids(); $bids = $this->getBids(); if (count($bids) > 0) { switch ($this->getData('listing_type')) { case 'auction': $highBid = $bids[0]; if ($highBid['amount'] >= $this->getData('reserve_price')) { $bid = $highBid; } break; } } } if ($bid instanceof Bid) { $service = new Service\Sales(); $data = array( 'buyer_id' => $bid['user_id'], 'seller_id' => $this->getData('user_id'), 'listings' => array( array( 'listing_id' => $this->getData('id'), 'price' => $bid['amount'], 'quantity' => 1, ) ), ); $service->save($data); $this->setSaleId( $service->getSaleId()); return $this->getSaleId(); } return false; } /** * * add a new click to the listing * * @7.9: do not purge the queries cache - as such do not use the save() method * * @return $this */ public function addClick() { $nbClicks = $this->_data['nb_clicks']; $table = $this->getTable(); $tableName = $table->getPrefix() . $table->getName(); $table->getAdapter()->update($tableName, array( 'nb_clicks' => new Expr('nb_clicks + 1') ), $this->_getWhereQuery()); $this->_data['nb_clicks'] = $nbClicks + 1; return $this; } /** * * add the product to the shopping cart * first we check if a shopping cart is active and if it is, add the item to the existing shopping cart * * the user id, insurance and postage are only saved when updating the shopping cart or checking out * * carts need to match the exact same fields like when combining invoices: * * @param int|null $quantity * @param array|null $productAttributes * * @return $this */ public function addToCart($quantity = null, $productAttributes = null) { $quantity = ($quantity < 1) ? 1 : $quantity; $bootstrap = Front::getInstance()->getBootstrap(); $session = $bootstrap->getResource('session'); $userToken = strval($session->getCookie(User::USER_TOKEN)); $salesService = new Service\Sales(); $select = $salesService->getTable()->select() ->where('user_token = ?', $userToken) ->where('seller_id = ?', $this->getData('user_id')) ->where('sale_data REGEXP \'"currency";s:[[:digit:]]+:"' . $this->getData('currency') . '"\'') ->where('sale_data REGEXP \'"country";s:[[:digit:]]+:"' . $this->getData('country') . '"\'') ->where('sale_data REGEXP \'"state";s:[[:digit:]]+:"' . $this->getData('state') . '"\'') ->where('sale_data REGEXP \'"address";s:[[:digit:]]+:"' . $this->getData('address') . '"\'') ->where('sale_data REGEXP \'"apply_tax";s:[[:digit:]]+:"' . $this->getData('apply_tax') . '"\'') ->where('pending = ?', 1); if ($pickupOptions = $this->getData('pickup_options')) { $select->where('sale_data REGEXP \'"pickup_options";s:[[:digit:]]+:"' . $pickupOptions . '"\''); } else { $select->where('sale_data REGEXP \'"pickup_options";N\''); } $row = $salesService->fetchAll($select)->getRow(0); $data = array( 'user_token' => $userToken, 'seller_id' => $this->getData('user_id'), 'quantity' => (int)$quantity, 'pending' => 1, 'listings' => array( array( 'listing_id' => $this->getData('id'), 'price' => $this->getProductPrice($productAttributes), 'quantity' => (int)$quantity, 'product_attributes' => (count($productAttributes) > 0) ? serialize($productAttributes) : null, ) ), ); if ($row !== null) { $data['id'] = $row->getData('id'); } $salesService->save($data); return $this; } /** * * save the recently viewed listing in the table * * @return $this */ public function addRecentlyViewedListing() { $settings = $this->getSettings(); if ($settings['enable_recently_viewed_listings']) { $bootstrap = Front::getInstance()->getBootstrap(); $session = $bootstrap->getResource('session'); $user = $this->getUser(); $userId = (isset($user['id'])) ? $user['id'] : null; $userToken = strval($session->getCookie(User::USER_TOKEN)); if (!empty($userToken)) { $recentlyViewedListingsService = new Service\RecentlyViewedListings(); $select = $recentlyViewedListingsService->getTable()->select() ->where('user_token = ?', $userToken) ->where('listing_id = ?', $this->getData('id')); $row = $recentlyViewedListingsService->fetchAll($select)->getRow(0); $data = array( 'user_token' => $userToken, 'listing_id' => $this->getData('id'), 'user_id' => $userId, ); if ($row !== null) { $data['id'] = $row->getData('id'); } $recentlyViewedListingsService->save($data); } } return $this; } /** * * save the updated row in the table * add updated_at flag unless already set and if not saving the whole data array * * 7.7 - whenever we use the save method on a listing, if the listing was active and open * before the save operation, we basically remove the counter and ask the cron to count the item again * (code duplicated from the listings service save() method) * * @param array $data partial data to be saved * the complete row is saved if this parameter is null * * @return $this */ public function save(array $data = null) { if ($data === null) { $data = $this->_data; } if (!array_key_exists('updated_at', $data)) { $data['updated_at'] = date('Y-m-d H:i:s', time()); } if (!array_key_exists('last_count_operation', $data)) { if ( $this->isActiveAndOpen() && $this->getData('list_in') != 'store' && $this->getdata('last_count_operation') == self::COUNT_OP_ADD ) { $this->countCategoriesCounter(self::COUNT_OP_SUBTRACT); $data['last_count_operation'] = self::COUNT_OP_NONE; } } parent::save($data); return $this; } /** * * delete or mark a listing as deleted * drafts are also deleted directly, rather than just being marked deleted * also subtract the listing from the category counters if it wasn't already subtracted * * 7.7 - for a pending cart, delete related rows from the sales listings table * * @param bool $admin if true - admin delete (delete all related files as well) * * @return bool|integer returns true if marked deleted, or number of affected rows if using admin delete */ public function delete($admin = false) { $this->addData('deleted', 1) ->processCategoryCounter(); $salesListings = $this->findDependentRowset('\Ppb\Db\Table\SalesListings'); /** @var \Ppb\Db\Table\Row\SaleListing $saleListing */ foreach ($salesListings as $saleListing) { $sale = $saleListing->findParentRow('\Ppb\Db\Table\Sales'); if ($sale['pending']) { $saleListing->delete(); } } if ($admin === true || $this->getData('draft')) { $this->findDependentRowset('\Ppb\Db\Table\ListingsMedia') ->delete(); $this->getCustomFieldsData() ->delete(self::CUSTOM_FIELDS_TYPE, $this->getData('id')); return parent::delete(); } else { $user = $this->getUser(); if (isset($user['id'])) { $this->save(array( 'deleted' => 1, )); return true; } } return false; } /** * * check if the seller has checked the must pick-up option for the listing * * @return bool */ public function pickUpOnly() { $settings = $this->getSettings(); if ($this->isShipping() && $settings['enable_pickups'] && $this->getData(ShippingModel::FLD_PICKUP_OPTIONS) == ShippingModel::MUST_PICKUP ) { return true; } return false; } /** * * check if shipping is enabled for the listing (global setting) * * @return bool */ public function isShipping() { $settings = $this->getSettings(); if ($settings['enable_shipping']) { return true; } return false; } /** * * check if listing is listed in an adult category * * @return bool */ public function isAdult() { $ruleKeys = array('Category', 'AddlCategory'); foreach ($ruleKeys as $ruleKey) { $category = $this->findParentRow('\Ppb\Db\Table\Categories', $ruleKey); if ($category instanceof Category) { if ($category->getData('adult')) { return true; } } } return false; } /** * * generate a short description for the listing, from the description field * * @param int $maxChars * * @return string */ public function shortDescription($maxChars = null) { /** @var \Cube\View $view */ $view = Front::getInstance()->getBootstrap()->getResource('view'); $description = strip_tags($view->renderHtml($this->getData('description'))); $length = strlen($description); $maxChars = ($maxChars === null) ? self::SHORT_DESC_MAX_CHARS : $maxChars; return substr($description, 0, $maxChars) . (($length > $maxChars) ? ' ... ' : ''); } /** * * returns an array used by the url view helper to generate the purchase link for the listing * it can be a link to the buy out confirm page, or a link to the add to cart action * * @return array */ public function purchaseLink() { if ($this->canAddToCart() === true) { return array( 'module' => 'listings', 'controller' => 'cart', 'action' => 'add', 'id' => $this->getData('id'), ); } return array( 'module' => 'listings', 'controller' => 'purchase', 'action' => 'confirm', 'type' => 'buy', 'id' => $this->getData('id'), ); } /** * * check if free shipping is offered for the listing * * this will work if: * - free postage is offered and free postage amount <= current bid * - item based postage > there is at least one option that has the price set to 0 * - flat rates > the first item value is set to 0 * * @return bool */ public function isFreeShipping() { /** @var \Ppb\Db\Table\Row\User $owner */ $owner = $this->findParentRow('\Ppb\Db\Table\Users'); if ($owner->getPostageSettings(ShippingModel::SETUP_FREE_POSTAGE) && $owner->getPostageSettings(ShippingModel::SETUP_FREE_POSTAGE_AMOUNT) <= $this->currentBid(true) ) { return true; } if ($owner->getPostageSettings(ShippingModel::SETUP_POSTAGE_TYPE) == ShippingModel::POSTAGE_TYPE_ITEM) { $postage = $this->getData(ShippingModel::FLD_POSTAGE); if (isset($postage['price'])) { foreach ($postage['price'] as $key => $value) { if ($value == 0) { return true; } } } } else if ($owner->getPostageSettings(ShippingModel::SETUP_POSTAGE_TYPE) == ShippingModel::POSTAGE_TYPE_FLAT) { if ($owner->getPostageSettings(ShippingModel::SETUP_POSTAGE_FLAT_FIRST) <= 0) { return true; } } return false; } /** * * check if the listing is a draft * * @return bool */ public function isDraft() { return (bool)$this->getData('draft'); } /** * * check if the logged in user is blocked from purchasing by the seller or admin * and return the block message if true * * @return string|false */ protected function _isBlockedUserPurchasing() { $user = $this->getUser(); $dataToBlock = array( 'ip' => $_SERVER['REMOTE_ADDR'], ); if (!empty($user)) { $dataToBlock['username'] = $user['username']; $dataToBlock['email'] = $user['email']; } $blockedUsersService = new Service\BlockedUsers(); $blockedUser = $blockedUsersService->check( BlockedUser::ACTION_PURCHASE, $dataToBlock, $this->getData('user_id')); if ($blockedUser !== null) { $view = Front::getInstance()->getBootstrap()->getResource('view'); return $view->blockStatus($blockedUser)->blockMessage(); } return false; } /** * * call magic method, used for retrieving dependent data * * @param string $name the name of the method from the \Cube\Db\Table\Row\AbstractRow method * @param array $arguments the arguments accepted by the method * * @return mixed */ public function __call($name, $arguments) { $listing = $this; return call_user_func_array( array($listing, $name), $arguments); } }