/**
	 * Loads a plan and returns an object of the corresponding class
	 *
	 * @param  int  $id  Id of plan
	 * @return cbpaidProduct
	 */
	public function & getObject( $id ) {
		static $_objects			=	array();

		$id							=	(int) $id;

		if ( ! isset( $_objects[$this->_tbl][$id] ) ) {
			if ( $id ) {
				$sql				=	"SELECT a.* FROM `" . $this->_tbl . "` AS a"
					.	"\n WHERE a.`" . $this->_tbl_key . "` = " . (int) $id;
				$this->_db->setQuery( $sql );
				$results			=	$this->_loadTrueObjects( $this->_tbl_key );
				if ( count( $results ) == 1 ) {
					$_objects[$this->_tbl][$id]	=	$results[$id];
				} else {
					$_objects[$this->_tbl][$id]	=	null;
					// trigger_error( 'getObject of object types ' . $this->_classPrefix . ' id ' . $id . ' failed.', E_USER_NOTICE );
					cbpaidApp::setLogErrorMSG( 5, null, sprintf( 'getObject of object types %s id %s failed.', $this->_classnamePrefix, $id ), null );
				}

			} else {
				$_objects[$this->_tbl][$id]		=	new $this->_classnamePrefix( $this->_db );
			}
		}
		return $_objects[$this->_tbl][$id];
	}
	/**
	 * Loads a subscription of a plan and returns an object of the corresponding class
	 *
	 * @param  int  $planId          Id of plan
	 * @param  int  $subscriptionId  Id of subscription within the plan $planId
	 * @return cbpaidSomething       The Something of plan $planId and subscription-id $subscriptionId
	 */
	public function & loadSomething( $planId, $subscriptionId ) {
		static $_planSubscriptions		=	array();
		$planId							=	(int) $planId;
		$subscriptionId					=	(int) $subscriptionId;

		if ( ! isset( $_planSubscriptions[$planId][$subscriptionId] ) ) {
			$plansMgr														=&	cbpaidPlansMgr::getInstance();	// can't access $this here as in static call.
			if ( $planId ) {
				$plan														=&	$plansMgr->loadPlan( $planId );
				if ( $plan ) {
					if ( $subscriptionId ) {

						$_planSubscriptions[$planId][$subscriptionId]		=	$plan->newSubscription();
						/** @noinspection PhpUndefinedMethodInspection (Due to limitation of IDE) */
						if ( $_planSubscriptions[$planId][$subscriptionId]->load( $subscriptionId ) ) {
							if ( $_planSubscriptions[$planId][$subscriptionId]->plan_id == $planId ) {

								return $_planSubscriptions[$planId][$subscriptionId];

							}
							cbpaidApp::setLogErrorMSG( 5, $plan, sprintf( 'loadSomething::planid %d of subid %d does not match planid %d.', $_planSubscriptions[$planId][$subscriptionId]->plan_id, $subscriptionId, $planId ), null );
						} else {
							cbpaidApp::setLogErrorMSG( 5, $plan, sprintf( 'loadSomething::subid %d with planid %d could not load.', $subscriptionId, $planId ), null );
						}
						unset( $_planSubscriptions[$planId][$subscriptionId] );

					} else {
						cbpaidApp::setLogErrorMSG( 5, $plan, sprintf( 'loadSomething::plan id: %d but no subscription id.', $planId ), null );
					}
				} else {
					cbpaidApp::setLogErrorMSG( 5, $plan, sprintf( 'loadSomething::plan id: %d is missing in database for subscription id: %d', $planId, $subscriptionId ), null );
				}

				$null													=	null;
				return $null;

			} else {
				trigger_error( 'loadSomething::no plan id.', E_USER_ERROR );
				exit;
			}
		}
		return $_planSubscriptions[$planId][$subscriptionId];
	}
	/**
	 * When triggered, before starting to make changes this method should be called
	 * Transition: -> E{$substate}
	 * 
	 * @param  string  $substate
	 * @return boolean                TRUE: got lock to perform scheduled task, FALSE: no scheduling needed here (and no transition made)
	 */
	public function attemptScheduledTask( $substate = '' ) {
		if ( substr( $this->_baseObject->scheduler_state, 0, 1 ) == 'E' ) {
			// It was Executing: check for iterations before resetting to 'S' (with error log).

			if ( strlen( $this->_baseObject->scheduler_state ) == 1 ) {
				// Backwards compatibility:
				$this->_baseObject->scheduler_state			.=	'1';
			}

			$iteration										=	(int) $this->_baseObject->scheduler_state[1];
			if ( ++$iteration <= $this->maxExecWaitIterations ) {
				$this->_baseObject->scheduler_state[1]		=	(string) $iteration;
				$this->_baseObject->store();
			} else {
				$this->_baseObject->scheduler_state			=	'S';
				cbpaidApp::setLogErrorMSG( 4, $this->_baseObject, CBPTXT::P("Scheduler for this basket has stayed in execution state for [NUMBER_OF_CRONS] cron cycles. Resetting execution state.", array( '[NUMBER_OF_CRONS]' => $this->maxExecWaitIterations ) ), null );
			}
		}

		// Now normal case:
		if ( $this->_baseObject->scheduler_state == 'S' ) {
			// it was scheduled:
			$this->_baseObject->scheduler_state				=	'E1' . $substate;	// Executing
			return $this->_baseObject->store();
		}
		return false;
	}
	/**
	 * Checks for renewal and upgrade possibilities for this subscription
	 * sets:
	 *         boolean       $this->_hideItsPlan
	 *         boolean       $this->_hideThisSubscription
	 *
	 * @param  int        $ui          1: frontend user, 2: backend admin
	 * @param  UserTable  $user        user id being displayed
	 * @param  float      $quantity    Quantity purchased
	 * @param  int        $now         unix time
	 * @param  int        $subsAccess  0 has only read access, 1 has user access, 2 reserved for future Super-admin access
	 * @return void
	 */
	public function checkRenewalUpgrade( $ui, $user, $quantity, $now, $subsAccess ) {
		/* see gui		//TBD later: maybe texts should go there...
				$buttonTexts		=	array(  'A' => '',
												'AA' => CBPTXT::T("Renew Now") . ': %s',					// local state
												'R' => CBPTXT::T("Pay now"),
												'U' => '',
												'C' => CBPTXT::T("Resubscribe") . ': %s',
												'X' => CBPTXT::T("Reactivate") . ': %s',
												'XX' => '',									// local state
												'ZZ' => '' );								// local state

				$buttonActions		=	array(  'A' => '',
												'AA' => "renew",							// local state
												'R' => "pay",
												'U' => '',
												'C' => "renew",
												'X' => "renew",
												'XX' => '',									// local state
												'ZZ' => '' );								// local state
		*/
		// actions allowed:
		$this->_allowedActions								=	array();
		$this->_hideItsPlan									=	true;				// in case plan missing from database

		$plan												=	$this->getPlan();
		if ( ! $plan ) {
			cbpaidApp::setLogErrorMSG( 5, $this, sprintf( 'something::checkRenewalUpgrade subid %d cannot load planid %d.', $this->id, $this->plan_id ), null );
			return;
		}

		$params												=&	cbpaidApp::settingsParams();
		$showRenewButtons									=	( $ui == 2 ) || ( $subsAccess && ( $params->get( 'showRenewButtons', '1' )		 == '1' ) );
		$showUnsubscribeButtons								=	( $ui == 2 ) || ( $subsAccess && ( $params->get( 'showUnsubscribeButtons', '0' ) == '1' ) );

		$realStatus											=	$this->realStatus( $now );

		// when we are in frontend: don't show upgraded subscriptions:
		$this->_hideThisSubscription						=	( ( $ui == 1 ) && ( $realStatus == 'U' ) ) || ( ! $this->checkIfParentSubscriptionIsValid( $now ) );
		// remaining value for this subscription (for prorate plans):
		$remainingValue										=	$this->remainingRateValue( $now );

		// upgrade possibilities for this subscription:
		// if ( $this->checkIfThisAndParentSubscriptionIsValid( $now ) ) {
		$this->_upgradePlansIdsDiscount					=	$plan->upgradePlansPossibilities( $ui, $user, $this, $remainingValue, $quantity, $now );
		// } else {
		//	$this->_upgradePlansIdsDiscount					=	array();
		// }
		switch ( $realStatus ) {
			case 'R':
				$this->_allowedActions['pay']				=	array( 'button_text' => $plan->buttonText( 'pay' ), 'warning' => '' );
				break;
			case 'A':
				if ( $showRenewButtons && $this->checkIfRenewable( $now ) ) {
					// $quantity								=	1;
					$periodPrice							=	$this->displayPeriodPrice( 'R' );			// getPriceOfNextPayment( null, $now, $quantity, 'R' );
					$separator								=	( ( $periodPrice && ( trim( str_replace( '&nbsp;', '', $periodPrice ) ) ) ) ? ': ' : '' );
					$this->_allowedActions['renew']			=	array( 'button_text' => $plan->buttonText( 'renew' ) . $separator . $periodPrice, 'warning' => '' );
				}
				break;
			case 'X':
				if ( $showRenewButtons && $this->checkIfRenewable( $now ) ) {
					// $quantity								=	1;
					$periodPrice							=	$this->displayPeriodPrice( 'R' );
					$separator								=	( ( $periodPrice && ( trim( str_replace( '&nbsp;', '', $periodPrice ) ) ) ) ? ': ' : '' );
					$this->_allowedActions['reactivate']	=	array( 'button_text' => $plan->buttonText( 'reactivate' ) . $separator . $periodPrice, 'warning' => '' );
				}
				break;
			case 'C':
				if ( $showRenewButtons && $this->checkIfRenewable( $now ) ) {
					// $quantity								=	1;
					$periodPrice							=	$this->displayPeriodPrice( 'N' );
					$separator								=	( ( $periodPrice && ( trim( str_replace( '&nbsp;', '', $periodPrice ) ) ) ) ? ': ' : '' );
					$this->_allowedActions['resubscribe']	=	array( 'button_text' => $plan->buttonText( 'resubscribe' ) . $separator . $periodPrice, 'warning' => '' );
				}
				break;
			case 'I':
			case 'U':
			default:
				break;
		}
		if ( $showUnsubscribeButtons && $this->checkIfValid( $now ) ) {
			$this->_allowedActions['unsubscribe']			=	array( 'button_text' => $plan->buttonText( 'unsubscribe' ), 'warning' => '' );
		}
		if ( $ui == 2 ) {
			$this->_allowedActions['delete']				=	array( 'button_text' => $plan->buttonText( 'delete' ), 'warning' => CBPTXT::T("This will permanently delete this subscription from the database when you save the user profile. Are you really sure ?") );
		}
		// don't propose plans for Active, Registered and Cancelled subscriptions:
		// $this->_hideItsPlan								=	( in_array( $this->status, array( 'A', 'R', 'C', 'X' ) ) );		// (  count ( $sub->_allowedActions ) == 0 );
		$this->_hideItsPlan									=	( $plan->get( 'multiple' ) == 0 ) && ( in_array( $realStatus, array( 'A', 'R', 'U' ) ) || $this->checkIfRenewable( $now ) );		// (  count ( $sub->_allowedActions ) == 0 );
	}