* @copyright 2009 Deon George * @link http://osb.leenooks.net * * @link http://www.agileco.com/ * @copyright 2004-2008 Agileco, LLC. * @license http://www.agileco.com/agilebill/license1-4.txt * @author Tony Landis * @package AgileBill * @subpackage Modules:Product */ /** * The main AgileBill Product Class * * @package AgileBill * @subpackage Modules:Product */ class product extends OSB_module { # Holds the array of available attributes for the current product private $attr = array(); /** * Show the product details, used when an admin adds a product to a customers account */ public function admin_details($VAR) { $this->session_id = SESS; if (! empty($VAR['account_id'])) { $this->account_id = $VAR['account_id']; $db = &DB(); $rs = $db->Execute(sqlSelect($db,'session','id',sprintf('account_id=%s',$this->account_id))); if ($rs && $rs->RecordCount()) $this->session_id = $rs->fields['id']; } $this->details($VAR); } public function details($VAR) { return $this->tmdetails($VAR); } /** * Show the product details to the user - used on the order form */ public function tmdetails($VAR) { global $smarty, $C_auth; if (empty($VAR['id'])) return false; # Able to view inactive items? if (! $C_auth->auth_method_by_name('invoice','add')) $active = ''; else $active = ' AND active=1'; $result = $this->sql_GetRecords(array('where'=>sprintf('id=%s%s',$VAR['id'],$active))); if (! count($result)) return false; $result = array_pop($result); # Check for group settings $groups = unserialize($result['group_avail']); $auth = false; for ($ii=0; $iiauth_group_by_id($groups[$ii])) { $auth = true; break; } } if (! $auth) return false; # Define the DB vars as a Smarty accessible block $smarty->assign('record',$result); # If trial, get the sku of the trial product: if ($result['price_type'] == '2') { $trial = $this->sql_GetRecords(array('where'=>array('id'=>$result['price_trial_prod']))); if (count($trial)) $smarty->assign('trial',array_pop($trial)); } # Get the best price for base, setup, & any attributes: $this->price_arr($result); $smarty->assign('price',$this->price); # Get any attributes & attribute pricing: $this->attr_arr($VAR['id']); $smarty->assign('attr',$this->attr); return true; } /** * Return the best price for a product, based on group details * Determine the best price based on the base rate, not based on the best setup price. * * @param array Price Group Array * @return array Best Price and Setup Price */ private function best_price($fields,$account,$recurr_schedule,$show=false) { global $C_auth; $ret = array(); @$g_ar = unserialize($fields['price_group']); if (is_array($g_ar)) { $g_ar = $g_ar[$recurr_schedule]; if (count($g_ar) > 0) { while (list($group,$vals) = each($g_ar)) { if (! $show || (isset($g_ar['show']) && $g_ar['show'])) if ($C_auth->auth_group_by_account_id($account,$group)) { if ($vals['price_base'] != '' && (! isset($ret['base']) || $vals['price_base'] < $ret['base'])) { $ret['base'] = $vals['price_base']; $ret['setup'] = $vals['price_setup']; } } } } } return $ret; } /** * Get Attribute values for product details page, sets $this->attr * * @param int $product_id The product id */ private function attr_arr($product_id) { # Set the current account if (empty($this->account_id)) $this->account_id = SESS_ACCOUNT; $db = &DB(); $result = $db->Execute(sqlSelect($db,'product_attr','*',sprintf('product_id=::%s::',$product_id),'sort_order')); if (! $result || $result->RecordCount() == 0) { $this->attr = array(); return false; } # Loop through each attribute to get the values $i = 0; while (! $result->EOF) { $this->attr[$i]['id'] = $result->fields['id']; $this->attr[$i]['type'] = $result->fields['collect_type']; $this->attr[$i]['default'] = $result->fields['collect_default']; $this->attr[$i]['name'] = $result->fields['name']; $this->attr[$i]['description'] = $result->fields['description']; $this->attr[$i]['required'] = $result->fields['required']; # Get the best base & setup price $ret = $this->best_price(unserialize($result->fields['price_group'])); $this->attr[$i]['price_base'] = $ret['base']; $this->attr[$i]['price_setup'] = $ret['setup']; # If menu, get the menu values as an array if ($result->fields['collect_type'] == '2') { $pat = "\r\n"; $tarr = false; $marr = explode($pat,$result->fields['collect_default']); for ($ii=0; $ii'','base'=>0,'setup'=>0); } else { # Populated line, determine base/setup price: if (preg_match('/==/',$marr[$ii])) { # Use custom prices $marrp = explode('==',$marr[$ii]); $tarr[] = array('name'=>@$marrp[0],'base'=>@$marrp[1],'setup'=>@$marrp[2]); } else { # Use default prices $tarr[] = array('name'=>$marr[$ii],'base'=>$ret['base'],'setup'=>$ret['setup']); } } } $this->attr[$i]['default'] = $tarr; } $result->MoveNext(); $i++; } return true; } /** HERE **/ /** * Calculate the cost for the attributes in the cart * * @param array $fields The product record fields * @param array $cart_attr The array of attributes in the cart * @param int $recurr_schedule The recurring schedule, 0-5 * @param int $account The account id * @param bool $prorate Apply prorating or not * @return array */ function price_attr($fields,$cart_attr,$recurr_schedule,$account=SESS_ACCOUNT,$prorate=true) { global $C_auth; $ret['base'] = 0; $ret['setup'] = 0; $product_id = $fields['id']; # Get the vars: if (! empty($cart_attr) && ! is_array($cart_attr)) $cart_attr = unserialize($cart_attr); if (! is_array($cart_attr)) return false; # Get the attributes for this product $db = &DB(); $result = $db->Execute(sqlSelect($db,'product_attr','*',sprintf('product_id=::%s::',$product_id),'sort_order')); if (!$result || $result->RecordCount() == 0) { $this->attr = false; return false; } # Loop through each attribute to get the values & validate the input $i = 0; while (! $result->EOF) { $calc = false; reset($cart_attr); # Loop through each attribute defined in the cart foreach ($cart_attr as $id=>$val) { $menu_def = true; # If defined in the cart: if (! empty($val) && is_numeric($id) && $id == $result->fields['id']) { # Get the best base & setup price $g_ar = unserialize($result->fields['price_group']); $curr['base'] = $result->fields['price_base']; $curr['setup'] = $result->fields['price_setup']; # If menu, get the base & setup amount from the selected item: if ($result->fields['collect_type'] == '2') { $marr = explode("\r\n",$result->fields['collect_default']); # Loop through each menu option for($ii=0;$iiattr[$i]['default'] = $tarr; } # Determine best group pricing if($menu_def) { if($curr['base'] > 0 || $curr['setup'] > 0) { if(count($g_ar) > 0) { $idx = 0; while (( (list ($group, $vals) = each ($g_ar)) && ($idx < 1) )) { // check if better pricing exist for current group if (is_numeric($group) && $C_auth->auth_group_by_account_id($account, $group)) { // calculate the base price if($vals['price_base'] != '' && $vals['price_base'] < $curr['base']) @$ret['base'] += $vals['price_base']; else @$ret['base'] += $curr['base']; // calculate the setup price if($vals['price_setup'] != '' && $vals['price_setup'] < $curr['setup']) @$ret['setup'] += $vals['price_setup']; else @$ret['setup'] += $curr['setup']; $idx++; } } } } } } } $result->MoveNext(); $i++; } # check the subscription schedule and calculate actual rate for this schedule: $arr = array(.23, 1, 3, 6, 12, 24, 36); if($fields['price_recurr_type'] == 1) $ret['base'] *= $arr[$recurr_schedule]; # check for any prorating for the selected schedule: if($fields['price_recurr_type'] == 1 && $prorate==true) $prorate = $this->prorate($recurr_schedule, $fields['price_recurr_weekday'], $fields['price_recurr_week']); # calculate the prorated recurring amount: if (@$prorate > 0 && $ret['base'] > 0) $ret['base'] *= $prorate; return array('base' => @round($ret['base'],2), 'setup' => @$ret['setup']); } /** * Get the start & end of set billing schedules * * @param int $type Type of Recur * @param int $weekday Day of Month for fixed billing * @param int $week Unused * @return array * @todo $temp is a temporary hack to stop the rounding of the dates to the begining of the BILLING_WEEKDAY */ public function recurrDates($type,$weekday,$week,$period_date=false,$temp=false) { # Make the period consistent, eg: Quarterly = Jan-Mar,Apr-Jun; HalfYearly = Jan-Jun,Jul-Dec $strict = false; $used_months = 0; # Round the time integer to a whole day. if (! $period_date) $period_date = strtotime('today'); else $period_date = strtotime(date('Y-m-d',$period_date)); switch ($type) { # Weekly case 0: $period_end = $period_date+(86400*7); return array('start'=>$period_date,'date'=>$period_date,'end'=>$period_end,'prorate'=>1); # Monthly case 1: $inc_months = 1; break; # Quarterly case 2: # @todo Make this configurable. $strict = true; $inc_months = 3; break; # Half Yearly case 3: # @todo Make this configurable. $strict = true; $inc_months = 6; break; # Yearly case 4: $inc_months = 12; break; # Biennial case 5: $inc_months = 24; break; # Triennial case 6: $inc_months = 36; break; default: return false; } if (is_null($weekday) && ! $temp) $weekday = BILLING_WEEKDAY; elseif ($temp) $weekday = date('d',$period_date); if ($strict && $type > 0 && $type < 5) $used_months = $inc_months-(($inc_months-(date('n',$period_date)%$inc_months))%$inc_months+1); $d = mktime(0,0,0,date('m',$period_date)-$used_months,$weekday,date('y',$period_date)); if ($d <= $period_date) $period_start = $d; else $period_start = mktime(0,0,0,date('m',$d)-1-$used_months,$weekday,date('y',$d)); $period_end = mktime(0,0,0,date('m',$period_start)+$inc_months,$weekday,date('y',$period_start)); $total_time = $period_end-$period_start; $remain_time = $period_end-$period_date; return array('start'=>$period_start,'date'=>$period_date,'end'=>$period_end,'prorata'=>round($remain_time/$total_time,4)); } /** * Determine Prorate Amount * * @param int $type * @param int $weekday * @param int $week * @return float */ public function prorate($type,$weekday,$week,$period_start=false) { $arr = $this->recurrDates($type,$weekday,$week,$period_start); if (! $arr) return 0; else return $arr['prorata']; } /** * Get the lowest price for one-time or recurring product fees * * @param array $fields array containing all product fields */ function price_arr($fields) { global $C_auth; if (empty($this->account_id)) $this->account_id = SESS_ACCOUNT; $type = $fields['price_type']; $g_ar = unserialize($fields["price_group"]); //echo '
'.__METHOD__;print_r($fields);die();
		if($type != "1")
		{
			# get the best base price (trial or one-time charges):
			$ret['base']  = $fields["price_base"];
			$ret['setup'] = $fields["price_setup"];

			if(is_array($g_ar) && count($g_ar) > 0)
			{
				while (list ($group, $vals) = each ($g_ar))
				{
					if (is_numeric($group) && $C_auth->auth_group_by_account_id($this->account_id, $group))
					{
						if($this->group_pricing($group))
						{
							if($vals["price_base"] != "" || $vals["price_setup"] != "" )
							{
								if(!empty($vals["price_base"]) && $vals["price_base"] < $ret['base'])
								$ret['base']= $vals["price_base"];
								if(!empty($vals["price_setup"]) && $vals["price_setup"] <  $ret['setup'])
								$ret['setup'] = $vals["price_setup"];
							}
						}
					}
				}
			}
			$this->price = $ret;
		}
		else
		{
			## Recurring charge, return best base/setup rates for all available payment schedules
			if(is_array($g_ar) && count($g_ar) > 0)
			{
				for($i=0; $i$vals)
					{
						if($g_ar[$i]["show"] == "1")
						{
							if (is_numeric($group) && $C_auth->auth_group_by_account_id($this->account_id, $group))
							{
								if($this->group_pricing($group))
								{
									if($vals["price_base"] != "" || $vals["price_setup"] != "" )
									{
										if(empty($ret[$i]['base']) || $vals["price_base"] < $ret[$i]['base']) $ret["$i"]['base'] = $vals["price_base"];
										if(empty($ret[$i]['setup']) || $vals["price_setup"] < $ret[$i]['setup']) $ret["$i"]['setup'] = $vals["price_setup"];
									}
								}
							}
						}
					}
				}
			}
		}

		$this->price = $ret;
	}

	/**
	 * Check if alternate pricing is allowed for specified group
	 *
	 * @param int $group Group ID
	 * @return bool
	 */
	private function group_pricing($group) {
		$db = &DB();
		$rs = $db->Execute(sqlSelect($db,'group','pricing',sprintf('id=%s',$group)));

		if ($rs && $rs->fields['pricing']==1)
			return true;
	}

	/**
	 * Best Price for Product
	 *
	 * @param array $fields
	 * @param array $recurr_schedule
	 * @param int $account
	 * @param bool $prorate
	 * @return array
	 */
	function price_prod($fields,$recurr_schedule,$account=SESS_ACCOUNT,$prorate=true) {
		switch ($fields['price_type']) {
			# Recurring charge, return best base/setup rates for all available payment schedules
			case 1:
				# Check for any prorating for the selected schedule:
				if ($fields['price_recurr_type'] == 1 && $prorate==true)
					$prorate = $this->prorate($recurr_schedule,$fields['price_recurr_weekday'],$fields['price_recurr_week']);

				$ret = $this->best_price($fields,$account,$recurr_schedule,true);

				if (! count($ret))
					return false;

				# Calculate the prorated recurring amount:
				if ($prorate > 0 && $ret['base'] > 0)
					$ret['base'] *= $prorate;

				break;

			# Get the best base price (trial or one-time charges)
			# @todo - need to optimise (call best_price())
			default:
				global $C_auth;

				@$g_ar = unserialize($fields['price_group']);
				if (! is_array($g_ar) || ! count($g_ar))
					return false;

				$ret['base']  = $fields['price_base'];
				$ret['setup'] = $fields['price_setup'];

				while (list($group,$vals) = each($g_ar)) {
					if (is_numeric($group) && $C_auth->auth_group_by_account_id($account,$group)) {
						if ($this->group_pricing($group)) {
							if ($vals['price_base'] != '' && $vals['price_base'] < $ret['base'])
								$ret['base']= $vals['price_base'];
							if ($vals['price_setup'] != '' && $vals['price_setup'] <  $ret['setup'])
								$ret['setup'] = $vals['price_setup'];
						}
					}
				}

		}

		return array('base'=>round($ret['base'],2),'setup'=>$ret['setup']);
	}

	/**
	 * Get the lowest (recurring) price
	 *
	 * @param array $fields
	 * @param int $account
	 * @return array Recurring Price
	 */
	function price_recurr_arr($fields, $account) {
		global $C_auth;
		$g_ar = unserialize($fields["price_group"]);
		if(count($g_ar) > 0) {
			for($i=0; $iauth_group_by_account_id($account,$group)) {
							if($vals["price_base"] != "")
								if(empty($ret[$i]['base']) || $vals["price_base"] < $ret[$i]['base']) $ret[$i]['base'] = $vals["price_base"];

							if($vals["price_setup"] != "")
								if(empty($ret[$i]['setup']) || $vals["price_setup"] < $ret[$i]['setup']) $ret[$i]['setup'] = $vals["price_setup"];
						}
					}
				}
			}
		}
		return $ret;
	}

	/**
	 * Clone Existing Product
	 */
	function cloner($VAR)
	{
		global $C_debug, $C_translate;

		$product_id = $VAR['id'];
		$sku = $VAR['product_sku'];
		$p   = AGILE_DB_PREFIX;

		if(empty($product_id) || empty($sku)) {
			$C_debug->alert( $C_translate->translate('clone_error', 'product',''));
			return false;
		}

		$db = &DB();
		$dbc= new CORE_database;

		# Get current product details
		$sql = $dbc->sql_select("product", "*", "id = $product_id", "", $db);
		$result = $db->Execute($sql);

		# Clone product
		$new_prod_id = $db->GenID(AGILE_DB_PREFIX.'product_id');
		$sql = "INSERT INTO {$p}product SET
    				id  = $new_prod_id,
    				sku = " . $db->qstr($sku);
		while(list($field,$value) = each($result->fields)) {
			if($field != 'sku' && $field != 'id' && !is_numeric($field) )
			$sql .= ",$field = ".$db->qstr($value);
		}
		$result = $db->Execute($sql);

		# Get current translation
		$sql = $dbc->sql_select("product_translate", "*", "product_id = $product_id", "", $db);
		$result = $db->Execute($sql);

		# Clone translation
		while(!$result->EOF)
		{
			$id = $db->GenID(AGILE_DB_PREFIX.'product_translate_id');
			$sql = "INSERT INTO {$p}product_translate SET
	    				id  = $id,
	    				product_id = $new_prod_id";
			while(list($field,$value) = each($result->fields)) {
				if($field != 'product_id' && $field != 'id' && !is_numeric($field)  )
				$sql .= ",$field = ".$db->qstr($value);
			}
			$db->Execute($sql);
			$result->MoveNext();
		}

		# Get current attributes
		$sql = $dbc->sql_select("product_attr", "*", "product_id = $product_id", "", $db);
		$result = $db->Execute($sql);

		# Clone attributes
		while(!$result->EOF)
		{
			$id = $db->GenID(AGILE_DB_PREFIX.'product_attr_id');
			$sql = "INSERT INTO {$p}product_attr SET
	    				id  = $id,
	    				product_id = $new_prod_id";
			while(list($field,$value) = each($result->fields)) {
				if($field != 'product_id' && $field != 'id' && !is_numeric($field) )
				$sql .= ",$field = ".$db->qstr($value);
			}
			$db->Execute($sql);
			$result->MoveNext();
		}

		$msg = $C_translate->translate('clone_success', 'product','');
		$C_debug->alert( ''. $msg .'');
		return $new_prod_id;
	}

	function add($VAR)  {
		# defaults for 'recurring' product
		if($VAR["product_price_type"] == "1")
		{
			$VAR['product_price_recurr_default'] = "1";
			$VAR['product_price_recurr_type'] = "0";
			$VAR['product_price_recurr_week'] = "1";
			$VAR['product_price_recurr_weekday'] = "1";

			# Set default recurring prices: (monthly only)
			$db     = &DB();
			$sql    = 'SELECT id FROM ' . AGILE_DB_PREFIX . 'group WHERE
                            site_id             = ' . $db->qstr(DEFAULT_SITE) . ' AND
                            pricing		        = ' . $db->qstr('1');
			$rs = $db->Execute($sql);
			while(!$rs->EOF) {
				$i = $rs->fields['id'];
				$recur_price[0][$i]['price_base']  = '';
				$recur_price[0][$i]['price_setup'] = '';
				@$recur_price[1][$i]['price_base'] = $VAR['product_price_base'];
				@$recur_price[1][$i]['price_setup'] = $VAR['product_price_setup'];
				$recur_price[2][$i]['price_base']  = '';
				$recur_price[2][$i]['price_setup'] = '';
				$recur_price[3][$i]['price_base']  = '';
				$recur_price[3][$i]['price_setup'] = '';
				$recur_price[4][$i]['price_base']  = '';
				$recur_price[4][$i]['price_setup'] = '';
				$recur_price[5][$i]['price_base']  = '';
				$recur_price[5][$i]['price_setup'] = '';
				$rs->MoveNext();
			}
			$recur_price[0]['show'] = "0";
			$recur_price[1]['show'] = "1";
			$recur_price[2]['show'] = "0";
			$recur_price[3]['show'] = "0";
			$recur_price[4]['show'] = "0";
			$recur_price[5]['show'] = "0";
			@$VAR['product_price_group'] = $recur_price;
		}

		# Defaults for product groups:
		$VAR['product_group_avail'] = array('0');

#		$this->product_construct();
		$type 		= "add";
		$this->method["$type"] = explode(",", $this->method["$type"]);
		$db 		= new CORE_database;
		$result 	= $db->add($VAR, $this, $type);

		# Create a translate record for this product:
		if($result) {
			$db     = &DB();
			$id     = $db->GenID(AGILE_DB_PREFIX . 'product_translate_id');
			$sql    = 'INSERT INTO ' . AGILE_DB_PREFIX . 'product_translate SET
                            site_id             = ' . $db->qstr(DEFAULT_SITE) . ',
                            id                  = ' . $db->qstr($id) . ',
                            product_id          = ' . $db->qstr($result) . ',
                            language_id         = ' . $db->qstr(DEFAULT_LANGUAGE) . ',
                            name                = ' . $db->qstr(@$VAR["translate_name"]) . ',
                            description_short   = ' . $db->qstr(@$VAR["translate_description_short"]) . ',
                            description_full    = ' . $db->qstr(@$VAR["translate_description_full"]);
			$db->Execute($sql);
		}
	}

	function update($VAR) {
		global $_FILES;
		$imgarr = array('jpeg','jpg','gif','bmp','tif','tiff','png');
		if(isset($_FILES['upload_file1']) && $_FILES['upload_file1']['size'] > 0)
		{
			for($i=0; $iproduct_construct();
		$type = "update";
		$this->method["$type"] = explode(",", $this->method["$type"]);
		$db = new CORE_database;
		$result = $db->update($VAR, $this, $type);

		### Copy the thumbnail
		if($result && isset($filename))
		{
			### Copy 1ST file upoad:
			copy($_FILES['upload_file1']['tmp_name'], PATH_IMAGES . "" . $filename);
		}
	}

	function delete($VAR) {
		$this->associated_DELETE =
		array(
		array( 'table' => 'product_translate', 'field' => 'product_id'),
		array( 'table' => 'product_attr', 		'field' => 'product_id'),
		array( 'table' => 'product_img', 		'field' => 'product_id')
		);
#		$this->product_construct();
		$db = new CORE_database;
		$db->mass_delete($VAR, $this, "");
	}

	public function getTranslateField($field) {
		static $CACHE = array();

		if (! $id = $this->getRecordAttr('id'))
			return null;

		if (! isset($CACHE[$id][$field])) {
			$db = &DB();
			$rs = $db->Execute(sqlSelect('product_translate',$field,array('where'=>array('product_id'=>$id))));
	
			if ($rs && $rs->RecordCount() && isset($rs->fields[$field]))
				$CACHE[$id][$field] = $rs->fields[$field];
			else
				$CACHE[$id][$field] = null;
		}

		return $CACHE[$id][$field];
	}
}
?>