init(); } public function onNewRequest($request) { parent::onNewRequest($request); $this->cache = []; } /** * Make instance * @return static */ public static function i() { bff()->singletonIf('listings.item.price', function () { return new static(); }); return bff('listings.item.price'); } /** * Currencies list * @return array */ protected function curr() { if (! isset($this->cache['curr'])) { $this->cache['curr'] = Currency::list(); } return $this->cache['curr']; } /** * Default currency id * @return int */ protected function currDefault() { if (! isset($this->cache['currDefault'])) { $this->cache['currDefault'] = Currency::id(); } return $this->cache['currDefault']; } /** * Format price in listings list * @param array $p ['data' => &$data items data] * @param array $opts * @return void */ public function format(array $p, array $opts = []) { $opts = $this->defaults($opts, [ 'viewCurrencyID' => 0, 'lang' => $this->locale->current(), ]); $viewCurrencyID = $opts['viewCurrencyID']; $lang = $opts['lang']; $p['data']['price_mod'] = ''; if (empty($p['data'])) { return; } $catSettings = $p['data']['cat_settings'] ?? []; if (empty($catSettings) && ! empty($p['data']['cat_id'])) { $catSettings = Listings::catData($p['data']['cat_id'])['settings'] ?? []; } $price = $p['data']['price_value'] = $p['data']['price'] ?? 0; $priceOn = $p['data']['price_on'] = ! empty($catSettings['price']['enabled']); $currencyID = $p['data']['price_curr'] ?? 0; $priceEx = $p['data']['price_ex'] ?? 0; $priceExMod = $p['data']['price_ex_mod'] ?? 0; $p['data']['price_helper'] = $this->helper($p['data'], ['viewCurrencyID' => $viewCurrencyID]); $result = ''; do { if (! $priceOn) { break; } $curr = $this->curr(); if (! isset($curr[$currencyID])) { $currencyID = $this->currDefault(); } if (! $this->isZero($priceEx)) { # convert price to required currency if (! empty($viewCurrencyID)) { $p['data']['price_value'] = $price = Currency::convertPrice($price, $currencyID, $viewCurrencyID); $p['data']['price_curr'] = $currencyID = $viewCurrencyID; } $p['data']['price_mod'] = $this->viewMod($priceEx, $priceExMod, $catSettings, ['lang' => $lang]); } else { $price = $p['data']['price_value'] = 0; } $result = $this->view($price, $currencyID, $priceEx, ['lang' => $lang]); } while (false); $p['data']['price'] = $result; $p['data']['price_from'] = $priceEx & static::EX_FROM ? _t('listings', 'from') : ''; } /** * Format price for one listings item * @param float $price * @param int $curr * @param int $ex * @param array $opts [viewCurrencyID, lang] * @return string */ public function view($price, $curr = 0, $ex = 0, array $opts = []) { $opts = $this->defaults($opts, [ 'viewCurrencyID' => 0, 'lang' => $this->locale->current(), ]); # No price (with modifier) if (empty($price)) { if ($ex > static::EX_MOD) { if ($ex & static::EX_FREE) { return _t('listings', 'For free'); } if ($ex & static::EX_EXCHANGE) { return _t('listings', 'Trade'); } if ($ex & static::EX_AGREED) { return _t('listings', 'Negotiable'); } } # No price return ''; } # Convert price to required currency if ($opts['viewCurrencyID']) { $price = Currency::convertPrice($price, $curr, $opts['viewCurrencyID']); $curr = $opts['viewCurrencyID']; } # Format to "{price} {currency}" return Currency::formatPriceAndCurrency($price, $curr, $opts); } /** * Format price with modifier * @param int $ex * @param int $mod * @param array|int $category category setting or id * @param array $opts [lang] * @return mixed|string */ public function viewMod($ex, $mod, $category, array $opts = []) { $lang = $opts['lang'] ?? $this->locale->current(); # Category id => settings if (is_numeric($category)) { $category = Listings::catData($category)['settings'] ?? []; } # Modifiers list if ($ex & static::EX_MOD_MANY) { if ($mod) { $options = $this->modifiersOptions($category, ['lang' => $lang]); return $options[$mod]['title'] ?? ''; } return ''; } # One modifier if ($ex & static::EX_MOD) { return ($category['price']['mod_title'][$lang] ?? '') ?: _t('listings', 'Negotiable'); } return ''; } /** * Check: use modifier only or price too * @param int $ex * @return int */ public function isZero($ex) { return $ex & (static::EX_FREE + static::EX_EXCHANGE + static::EX_AGREED); } /** * Check: use radio before price * @param int $ex * @return bool */ public function isRadio($ex) { return $this->isZero($ex) || ($ex & static::EX_FROM); } /** * Get item price in different currencies using rates * @param array $data * @param array $opts [tag, mod] * @return string */ public function helper(&$data, array $opts = []) { $opts = $this->defaults($opts, [ 'tag' => 'div', 'mod' => false, 'viewCurrencyID' => 0, ]); $currencies = $this->curr(); do { if (! is_array($data)) { break; } $price = $data['price_value'] ?? 0; if (! $price || $price == 0) { break; } $ex = $data['price_ex'] ?? 0; if ($this->isZero($ex)) { break; } $mod = $data['price_ex_mod'] ?? 0; $sett = $data['cat_settings'] ?? []; if (empty($sett)) { $sett = $data['cat_id'] ?? 0; } $curr = $data['price_curr'] ?? 0; if (! $curr) { break; } if (! isset($currencies[$curr])) { break; } $result = []; foreach ($currencies as $v) { $c = $curr; $p = $price; $r = $this->view($p, $c, $ex, ['viewCurrencyID' => $v['id']]); $m = ''; if ($opts['mod']) { $m = $this->viewMod($ex, $mod, $sett); } if (empty($r)) { continue; } $result[ $v['id'] ] = '<' . $opts['tag'] . '>' . $r . ($m ? ' ' . $m : '') . ''; } unset($result[$opts['viewCurrencyID'] ?: $curr]); return join(' ', $result); } while (false); return ''; } /** * Modifiers list html select options * @param array|int $category category data or id * @param array $opts [lang] * @return array [id => ['id'=>int, 'title'=>string], ...] */ public function modifiersOptions($category = [], array $opts = []) { $lang = $opts['lang'] ?? $this->locale->current(); if (empty($category)) { return []; } if (is_numeric($category)) { $category = Listings::catData((int)$category)['settings'] ?? []; } $settings = $category['price'] ?? []; if (empty($settings['enabled'])) { return []; } if (! (($settings['ex'] ?? 0) & static::EX_MOD_MANY)) { return []; } $result = []; foreach ($settings['modifiers'] ?? [] as $v) { if (empty($v['id'])) { continue; } $result[$v['id']] = [ 'id' => $v['id'], 'title' => $this->locale->value($v['t'] ?? []), ]; } if (! empty($settings['mod_empty'])) { $result[0] = [ 'id' => 0, 'title' => ($settings['mod_empty_title'][$lang] ?? '') ?: _t('@listings', 'Without Specifying'), ]; } return $result; } /** * Validate item price (during item form submit) * @param array $item item data * @param array $category category data (optional) * @return bool true - valid */ public function validate(array &$item, array $category = []) { # Wrong item data if (empty($item)) { return false; } # Category data $category = $category ?? []; if (empty($category) && ! empty($item['cat_id'])) { $category = Listings::catData($item['cat_id']); } if (empty($category)) { return false; } $settings = $category['settings']['price'] ?? []; # price is not used in category if (empty($settings['enabled'])) { unset($item['price']); unset($item['price_search']); unset($item['price_curr']); unset($item['price_ex']); unset($item['price_ex_mod']); return true; } if (empty($item['price'])) { $item['price'] = 0; } if (empty($item['price_curr'])) { $item['price_curr'] = 0; } # unknown currency => use default $currencies = $this->curr(); if (! isset($currencies[$item['price_curr']])) { $item['price_curr'] = $this->currDefault(); } # modifiers $ex = $settings['ex'] ?? 0; if (is_array($item['price_ex'])) { $item['price_ex'] = array_sum($item['price_ex']); } $m = $ex & (static::EX_EXCHANGE + static::EX_FREE + static::EX_AGREED + static::EX_FROM); if ($ex & static::EX_MOD_MANY) { $m |= static::EX_MOD_MANY; } elseif ($ex & static::EX_MOD) { $m |= static::EX_MOD; } $item['price_ex'] &= $m; # modifiers list if ($ex & static::EX_MOD_MANY) { $item['price_ex'] |= static::EX_MOD_MANY; $options = $this->modifiersOptions($category['settings']); if (empty($options)) { $item['price_ex_mod'] = 0; } elseif (! isset($options[ ($item['price_ex_mod'] ?? 0) ])) { $options = reset($options); $item['price_ex_mod'] = $options['id'] ?? 0; } } else { unset($item['price_ex_mod']); } if ($this->isZero($item['price_ex'])) { $item['price'] = 0; } else { if (empty($item['price'])) { $this->errors->set(_t('', 'Fill in all required fields, please'), 'price'); } } # convert price to default currency price (to use in search) $item['price_search'] = Currency::convertPriceToDefault($item['price'], $item['price_curr']); return true; } }