ProShell v2.0
Dashboard
Server Info
Server: 158.106.128.192
PHP: 8.2.33
home
planet5
public_html
bansibaba.com
2026-09-12 16:22:56
Editing: Product.php
Cancel
Save Changes
<?php /** * Class represents records from table products * {autogenerated} * @property int $product_id * @property string $title * @property string $description * @property string $trial_group * @property string $start_date * @property string $currency * @property int $tax_group * @property string $sort_order * @property string $renewal_group * @property date $start_date_fixed * @property string $require_other * @property string $prevent_if_other * @property string $require_other_group * @property string $prevent_if_other_group * @property string $paysys_id * @property string $comment * @property int $default_billing_plan_id * @property int $is_tangible * @property bool $is_disabled * @see Am_Table * @package Am_Invoice */ class Product extends Am_Record_WithData implements IProduct { /** calculate start date by payment date */ const SD_PAYMENT = 'payment'; /** calculate start date by product payments */ const SD_PRODUCT = 'product'; /** calculate start date by renewal groups payments */ const SD_GROUP = 'group'; /** set fixed start date */ const SD_FIXED = 'fixed'; /** @var BillingPlan */ protected $_plan; /** @var array */ protected $_options = array(); public function init() { parent::init(); if (empty($this->currency)) $this->currency = Am_Currency::getDefault (); } /** * @return bool */ function isRecurring(){ return (bool)$this->getBillingPlan()->rebill_times; } /** * Run htmlspecialchars(strip_tags()) for the string * It is useful for strings that may contain html entities but we would * not want to see it here, for example: product title or description * @param string string to escape * @return string escaped string */ static function stripEscape($string){ return htmlspecialchars(strip_tags($string), null, 'UTF-8', false); } /** * Return title of product */ function getTitle($escaped=true){ $title = ___($this->title); return $escaped ? $this->stripEscape($title) : str_ireplace('<script>', '', $title); } /** * Return description of product */ function getDescription($escaped=true){ $title = ___($this->description); return $escaped ? $this->stripEscape($title) : str_ireplace('<script>', '', $title); } /** * @return Am_Currency */ function getCurrency($value = null) { $c = new Am_Currency($this->currency); if ($value) $c->setValue($value); return $c; } function getProductId() { return $this->product_id; } /** * Return short type of the item, ex. for Product returns "product" */ function getType() { return 'product'; } //function getTitle() //function getDescription(); function getFirstPrice() { return $this->getBillingPlan()->first_price; } function getFirstPeriod() { return $this->getBillingPlan()->first_period; } /** * Rebilling mode * @return int 0:"No Rebilling", 1:"Charge Second Price Once", a number:"Rebill x Times: ", 99999:"Unlimited Recurring Billing" */ function getRebillTimes(){ return $this->getBillingPlan()->rebill_times; } function getSecondPrice(){ return $this->getBillingPlan()->second_price; } function getSecondPeriod(){ return $this->getBillingPlan()->second_period; } function getCurrencyCode(){ return $this->currency; } function getTaxGroup() { return $this->tax_group; } /** * Can the item be shipped? Should we calculate shipping * charges for it? */ function getIsTangible(){ return false; } /** * Can qty of the item in the Invoice be not equal to 1? * For subscriptions this must be almost always "false" * For deliverable goods like cups this must be "true" */ function getIsCountable(){ $bp = $this->getBillingPlan(); return $bp->get('qty') != 1 || $bp->get('variable_qty'); } public function getQty() { return $this->getBillingPlan()->get('qty', 1); } public function getIsVariableQty() { return $this->getBillingPlan()->get('variable_qty', 0); } /** param array $categories - array of id# */ function setCategories(array $categories) { $this->getAdapter()->query("DELETE FROM ?_product_product_category WHERE product_id=?d {AND product_category_id NOT IN (?a)}", $this->product_id, $categories ? $categories : DBSIMPLE_SKIP); if (!$categories) return; $vals = array(); foreach ($categories as $id) $vals[] = sprintf("(%d,%d)", $this->product_id, $id); $this->getAdapter()->query("INSERT IGNORE INTO ?_product_product_category (product_id, product_category_id) VALUES " . implode(", ", $vals)); } /** @return array of id# */ function getCategories() { if (empty($this->product_id)) return array(); return $this->getAdapter()->selectCol("SELECT product_category_id FROM ?_product_product_category WHERE product_id=?d", $this->product_id); } /** @return array of BillingPlan */ function getBillingPlans($onlyEnabled = false) { if (empty($this->product_id)) return array(); $ret = $this->getDi()->billingPlanTable->getForProduct($this->product_id, null, $onlyEnabled); foreach ($ret as $r) $r->_setProduct($this); return $ret; } /** @return BillingPlan */ function getBillingPlan($throwException = true) { if (!$this->_plan) if ($this->default_billing_plan_id) $this->_plan = $this->getDi()->billingPlanTable->load($this->default_billing_plan_id); else $this->_plan = current($this->getDi()->billingPlanTable->getForProduct($this->product_id, 1)); if (!$this->_plan && $throwException) throw new Am_Exception_Configuration("No billing plan defined for product# " . $this->pk()); return $this->_plan; } function getBillingOptions() { $ret = array(); foreach ($this->getBillingPlans(true) as $plan) $ret[ $plan->plan_id ] = $plan->title . ' - ' . $plan->getTerms(); return $ret; } /** @return BillingPlan and set it as "current" for this product object */ function createBillingPlan() { if (!$this->pk()) throw new Am_Exception_InternalError("Could not run [createBillingPlan] on not-saved product"); $p = $this->getDi()->billingPlanRecord; $p->product_id = $this->pk(); $this->setBillingPlan($p); return $p; } /** * @param BillingPlan|int $plan * @return Product provides fluent interface */ function setBillingPlan($plan) { if ($plan instanceof BillingPlan) $this->_plan = $plan; else { $p = $this->getDi()->billingPlanTable->load(intval($plan), true); if ($p->product_id != $this->product_id) throw new Am_Exception_InternalError("Billing plan from another product cannot be used"); $this->_plan = $p; } return $this; } public function getBillingPlanId() { return $this->getBillingPlan()->plan_id; } public function getBillingPlanData() { return $this->getBillingPlan()->data()->getAll(); } public function getOptions() { return $this->_options; } public function setOptions(array $options) { $this->_options = (array)$options; } public function delete() { $ret = parent::delete(); $this ->deleteFromRelatedTable('?_billing_plan') ->deleteFromRelatedTable('?_access') ->deleteFromRelatedTable('?_user_status') ->deleteFromRelatedTable('?_product_product_category'); $this->getTable()->getAdapter()->query("DELETE FROM ?_resource_access WHERE fn='product_id' AND id=?d", $this->pk()); return $ret; } static function serializeList($ids) { if (!is_array($ids)) return preg_replace('[^a-z-A-Z0-9_,-:]', '', $ids); return implode(',', $ids); } static function unserializeList($ids) { if ($ids=='') return array(); return explode(',', $ids); } /** @return array start date calculation rules */ public function getStartDate() { $setting = $this->unserializeList(empty($this->start_date) ? '' : $this->start_date); if (!$setting) $setting = array( self::SD_PRODUCT, self::SD_PAYMENT, ); return $setting; } public function setStartDate(array $setting) { return $this->start_date = $this->serializeList($setting); } public function calculateStartDate($paymentDate, Invoice $invoice) { if ($paymentDate instanceof DateTime) $paymentDate = $paymentDate->format('Y-m-d'); $ret = array(); $setting = $this->getStartDate(); $callArgs = array(); if (in_array(self::SD_PRODUCT, $setting)) $callArgs['product_id'] = $this->product_id; if (in_array(self::SD_GROUP, $setting)) $callArgs['renewal_group'] = $this->renewal_group; if (in_array(self::SD_PAYMENT, $setting)) $ret[] = $paymentDate; if (in_array(self::SD_FIXED, $setting) && $this->start_date_fixed) $ret[] = $this->start_date_fixed; if (count($callArgs)) { $callArgs['user_id'] = $invoice->getUserId(); $ret[] = $this->getDi()->accessTable->getLastExpire($callArgs); } $ret = array_filter($ret); if (!$ret) $ret[] = $this->getDi()->sqlDate; return max($ret); } function defaultRender(BillingPlan $plan = null, $short = false) { return sprintf(' <b>%s</b> %s<br/><span class="small">%s</span><br />', $this->getTitle(false), $plan ? ___($plan->getTerms()) : "", $this->getDescription(false) ); } public function addQty($requestedQty, $itemQty) { if (!$this->getIsCountable()) return 1; if ($this->getBillingPlan()->variable_qty) return $itemQty + $requestedQty; else return $this->getBillingPlan()->qty; } public function findItem(array $existingInvoiceItems) { foreach ($existingInvoiceItems as $item) if ($item->item_id == $this->getProductId()) return $item; } /** * * @param string $req * @return array */ protected function parceRequirementsGroup($req) { $catProducts = $this->getDi()->productCategoryTable->getCategoryProducts(); $res = array_filter(explode(',',$req)); foreach ($res as $k => $g) { if (preg_match('/CATEGORY-(\w*)-(\d*)/', $g, $match)) { unset($res[$k]); $status = $match[1]; $catId = $match[2]; foreach ($catProducts[$catId] as $prId) { $res[] = sprintf('%s-%d', $status, $prId); } } } return $res; } function getRequireOther() { return array_unique(array_filter($this->parceRequirementsGroup($this->require_other))); } function getPreventIfOther() { return array_unique(array_filter($this->parceRequirementsGroup($this->prevent_if_other))); } } /** * Products table * @package Am_Invoice */ class ProductTable extends Am_Table_WithData { protected $_key = 'product_id'; protected $_table = '?_product'; protected $_recordClass = 'Product'; protected $useCache = true; /* * Return array of products in the form * product_id => title * Suitable for usage in <SELECT> * @return array */ function getOptions($onlyEnabled = false) { return array_map(array("Am_Controller", "escape"), $this->_db->selectCol("SELECT product_id as ARRAY_KEY, CONCAT('(', product_id, ') ', title) FROM ?_product {WHERE is_disabled = ?} ORDER BY 0+sort_order, title", $onlyEnabled ? 0 : DBSIMPLE_SKIP)); } /** * Return products that are visible in passed context * @param array $productCategories or null to do not limit * @param array|null $activeProductIds active products of the current user or null to skip checks * @param array|null $expiredProductIds active products of the current user or null to skip checks * @return array of Products */ function getVisible($productCategories = null, $activeProductIds = null, $expiredProductIds = null) { if ($activeProductIds === null) { // skip check for product requirements } if ($productCategories) $productCategories = array_filter(array_map('intval', $productCategories)); return $this->selectObjects("SELECT p.* FROM ?_product p {RIGHT JOIN ?_product_product_category ppc ON p.product_id=ppc.product_id AND ppc.product_category_id in (?a)} WHERE p.product_id > 0 AND p.is_disabled = 0 GROUP BY p.product_id ORDER BY 0+p.sort_order, p.title ", $productCategories ? $productCategories : DBSIMPLE_SKIP); } /** * @param array of #ids * @return array of strings */ function getProductTitles(array $ids) { return array_map(array('Product', 'stripEscape'), $this->_db->selectCol("SELECT product_id as ARRAY_KEY,title FROM ?_product WHERE product_id IN (?a)", array_filter(array_map('intval', $ids)))); } /** * Filter products that should not be available on signup/renew pages depends on * require_other and prevent_if_other product settings * @param array $products Product objects that are purchasing now * @param array $haveActiveIds int product# user has active subscriptions to * @param array $haveExpiredIds int product# user has expired subscriptions to * @param bool $select_multiple - is user able to select multiple products on signup form. * @return array Product objects. */ function filterProducts(array $products, array $haveActiveIds = array(), array $haveExpiredIds=array(), $select_multiple=false){ $have = array_unique(array_merge( array_map(create_function('$id', 'return "ACTIVE-$id";'), $haveActiveIds), array_map(create_function('$id', 'return "EXPIRED-$id";'), $haveExpiredIds) )); do{ $changes =0; $all_available = array(); foreach($products as $pr){ $all_available[] = 'ACTIVE-'.$pr->product_id; } foreach($products as $k=>$v){ $po = $v->getPreventIfOther(); $ro = $v->getRequireOther(); if($po && array_intersect($po, $have)) { unset($products[$k]); $changes++; } if($ro){ if(is_array($have) && $have){ if(!array_intersect($ro, ($select_multiple ? array_merge($have, $all_available) : $have))){ unset($products[$k]); $changes++; } }else{ if(!array_intersect($ro, ($select_multiple ? $all_available : array()))){ unset($products[$k]); $changes++; } } } } }while($changes); return $products; } /** * Check if require_other prevent_if_other product settings are statisfied * for current purchase * @param array $products Product objects that are purchasing now * @param array $haveActiveIds int product# user has active subscriptions to * @param array $haveExpiredIds int product# user has expired subscriptions to * @return array empty array of OK, or an array full of error messages */ function checkRequirements(array $products, array $haveActiveIds = array(), array $haveExpiredIds = array()){ $error = array(); $have = array_unique(array_merge( array_map(create_function('$id', 'return "ACTIVE-$id";'), $haveActiveIds), array_map(create_function('$id', 'return "EXPIRED-$id";'), $haveExpiredIds) )); $will_have = array_unique(array_merge( $have, array_map(create_function('Product $p', 'return "ACTIVE-".$p->product_id;'), $products) )); $willHaveActiveId = array_unique(array_merge($haveActiveIds, array_map(create_function('Product $p', 'return $p->product_id;'), $products) )); foreach ($products as $pr){ if ($ro = $pr->getRequireOther()){ if ($ro && !array_intersect($ro, $will_have)) { $ids = array(); foreach ($ro as $s) if (preg_match('/^ACTIVE-(\d+)$/', $s, $args)) $ids[] = $args[1]; if ($ids){ $error[] = sprintf(___('"%s" can be ordered along with these products/subscripton(s) only: %s'), $pr->getTitle(true), implode(',', $this->getProductTitles($ids))); continue; } $ids = array(); foreach ($ro as $s) if (preg_match('/^EXPIRED-(\d+)$/', $s, $args)) $ids[] = $args[1]; if ($ids){ $error[] = sprintf(___('"%s" can only be ordered if you have expired subscription(s) for these products: %s'), $pr->getTitle(true), implode(',',$this->getProductTitles($ids))); continue; } } } if ($ro = $pr->getPreventIfOther()){ if ($ro && array_intersect($ro, $have)) { $ids = array(); foreach ($ro as $s) if (preg_match('/^ACTIVE-(\d+)$/', $s, $args)) $ids[] = $args[1]; $ids = array_intersect($ids, $willHaveActiveId); if ($ids) { $error[] = sprintf(___('"%s" cannot be ordered because you have active subscription(s) to: %s'), $pr->getTitle(true), implode(',',$this->getProductTitles($ids))); continue; } $ids = array(); foreach ($ro as $s) if (preg_match('/^EXPIRED-(\d+)$/', $s, $args)) $ids[] = $args[1]; $ids = array_intersect($ids, $haveExpiredIds); if ($ids) { $error[] = sprintf(___('"%s" cannot be ordered because you have expired subscription(s) to: %s'), $pr->getTitle(true), implode(',',$this->getProductTitles($ids))); continue; } } } } return $error; } /** * return query object with category filter applied if specified * if parameters === 0, it selects products not assigned to any categories * if parameter === null, it selects products regardless of categories * @param int $product_category_id * @param bool|array $include_hidden Include products from hidden categories, * in case of array include hidden categories only with given in array codes. * @return Am_Query */ function createQuery($product_category_id = null, $include_hidden=true) { $q = new Am_Query($this, 'p'); $q->addOrderRaw('0+p.sort_order')->addOrder('title'); $q->addWhere('p.is_disabled=0'); if ($product_category_id > 0) $q->innerJoin('?_product_product_category', 'ppc', 'ppc.product_id = p.product_id AND ppc.product_category_id='.intval($product_category_id)); elseif ((string)$product_category_id === '0') $q->leftJoin('?_product_product_category', 'ppc', 'ppc.product_id = p.product_id')->addHaving('count(ppc.product_category_id)=0'); elseif (is_array($include_hidden)) { $q->leftJoin('?_product_product_category', 'ppc', 'ppc.product_id = p.product_id') ->leftJoin('?_product_category', 'pc', 'pc.product_category_id = ppc.product_category_id')->addHaving('sum(if(pc.code>"" AND pc.code NOT IN (?a), 1, 0)) =0', $include_hidden); } elseif(!$include_hidden){ $q->leftJoin('?_product_product_category', 'ppc', 'ppc.product_id = p.product_id') ->leftJoin('?_product_category', 'pc', 'pc.product_category_id = ppc.product_category_id')->addHaving('sum(if(pc.code>"", 1, 0)) =0'); } return $q; } function getRenewalGroups() { return $this->_db->selectCol("SELECT DISTINCT renewal_group, renewal_group AS ? FROM ?_product WHERE renewal_group <> '' ORDER BY renewal_group", DBSIMPLE_ARRAY_KEY); } }
Upload Files
Cancel
Upload
Create New
Cancel
Create
Change Permissions
Cancel
Save
Change Date
Cancel
Save
Rename Item
Cancel
Save
Confirm Delete
Are you sure you want to delete the selected items?
Cancel
Delete