1, MEMBERSHIP_ID_ACCOUNT => 1, MEMBERSHIP_ID_UNCONFIRMED => 1, MEMBERSHIP_ID_PENDING => 1, MEMBERSHIP_ID_SUSPENDED => 1, MEMBERSHIP_ID_STANDARD => 1, ); protected $_aProfileStatus2LevelMap = array ( BX_PROFILE_STATUS_SUSPENDED => MEMBERSHIP_ID_SUSPENDED, BX_PROFILE_STATUS_PENDING => MEMBERSHIP_ID_PENDING, ); protected $_aLevel2MessageMap = array ( MEMBERSHIP_ID_NON_MEMBER => '_sys_acl_action_unauthenticated', MEMBERSHIP_ID_ACCOUNT => '_sys_acl_action_account', MEMBERSHIP_ID_UNCONFIRMED => '_sys_acl_action_unconfirmed', MEMBERSHIP_ID_PENDING => '_sys_acl_action_pending', MEMBERSHIP_ID_SUSPENDED => '_sys_acl_action_suspended', ); protected function __construct() { if (isset($GLOBALS['bxDolClasses'][get_class($this)])) trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR); parent::__construct(); $this->_bIsApi = bx_is_api(); $this->oDb = BxDolAclQuery::getInstance(); } /** * Prevent cloning the instance */ public function __clone() { if (isset($GLOBALS['bxDolClasses'][get_class($this)])) trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR); } /** * Get singleton instance of the class */ public static function getInstance() { if(!isset($GLOBALS['bxDolClasses'][__CLASS__])) $GLOBALS['bxDolClasses'][__CLASS__] = new BxTemplAcl(); return $GLOBALS['bxDolClasses'][__CLASS__]; } /** * Get necessary condition array to use membership level in search classes * @param $sContentField content table field name * @param $mixedLevelId level ID or array of level IDs * @return array of conditions is returned */ public function getContentByLevelAsCondition($sContentField, $mixedLevelId) { $iLevelId = !is_array($mixedLevelId) ? $mixedLevelId : 0; if (!$iLevelId && is_array($mixedLevelId) && 1 == count($mixedLevelId)) { $a = array_values($mixedLevelId); $iLevelId = array_shift($a); } // unconfirmed if (MEMBERSHIP_ID_UNCONFIRMED == $iLevelId) { return array( 'restriction_sql' => ' AND `sys_accounts`.`email_confirmed` = 0 ', 'restriction' => array (), 'join' => array (), ); } // standard elseif (MEMBERSHIP_ID_STANDARD == $iLevelId) { return array( 'restriction_sql' => ' AND (`tlm`.DateStarts IS NULL OR `tlm`.DateStarts <= NOW()) AND (`tlm`.DateExpires IS NULL OR `tlm`.DateExpires > NOW()) AND `tlm`.`IDMember` IS NULL AND `sys_accounts`.`email_confirmed` != 0 ', 'restriction' => array ( ), 'join' => array ( 'acl_members' => array( 'type' => 'LEFT', 'table' => 'sys_acl_levels_members', 'table_alias' => 'tlm', 'mainField' => $sContentField, 'onField' => 'IDMember', 'joinFields' => array(), ), ), ); } // other levels else { return array( 'restriction_sql' => ' AND (`tlm`.DateStarts IS NULL OR `tlm`.DateStarts <= NOW()) AND (`tlm`.DateExpires IS NULL OR `tlm`.DateExpires > NOW()) AND `sys_accounts`.`email_confirmed` != 0 ', 'restriction' => array ( 'acl_members' => array( 'value' => $mixedLevelId, 'field' => 'IDLevel', 'operator' => is_array($mixedLevelId) ? 'in' : '=', 'table' => 'tlm', ), ), 'join' => array ( 'acl_members' => array( 'type' => 'INNER', 'table' => 'sys_acl_levels_members', 'table_alias' => 'tlm', 'mainField' => $sContentField, 'onField' => 'IDMember', 'joinFields' => array(), ), ), ); } } /** * Get necessary parts of SQL query to use membership levels in other queries * @param $sContentTable content table name * @param $sContentField content table field name * @param $mixedLevelId level ID or array of level IDs * @return array of SQL string parts, for now 'where' part only is returned */ public function getContentByLevelAsSQLPart($sContentTable, $sContentField, $mixedLevelId) { return $this->oDb->getContentByLevelAsSQLPart($sContentTable, $sContentField, $mixedLevelId); } /** * Check if member has one of the provided membership levels * @param $mixedPermissions - integer value (every bit is matched with some membership ID) or an array of membership IDs to check permissions for * @param $iProfileId - profile to check, if it isn't provided or is false then currently logged in profile is used. * @return true if member has privided membership levels, or false if member hasn't. */ public function isMemberLevelInSet($mixedPermissions, $iProfileId = false) { $iPermissions = 0; if(is_array($mixedPermissions)) foreach($mixedPermissions as $iPermissionId) $iPermissions += pow(2, (int)$iPermissionId - 1); else if(is_numeric($mixedPermissions)) $iPermissions = (int)$mixedPermissions; if(!$iPermissions) return false; return ($iPermissions & $this->getMemberLevelBit($iProfileId)); } /** * Get user's membership level bit for bitwise operarions */ public function getMemberLevelBit($iProfileId = 0) { if (!$iProfileId) $iProfileId = bx_get_logged_profile_id(); $aACL = $this->getMemberMembershipInfo($iProfileId); return pow(2, $aACL['id'] - 1); } /** * Checks if a given action is allowed for a given profile and updates action information if the * action is performed. * * @param int $iProfileId ID of a profile that is going to perform an action * @param int $iActionId ID of the action itself * @param boolean $bPerformAction if true, then action information is updated, i.e. action is 'performed' * @return array( * CHECK_ACTION_RESULT => CHECK_ACTION_RESULT_ constant, * CHECK_ACTION_MESSAGE => CHECK_ACTION_MESSAGE_ constant, * CHECK_ACTION_PARAMETER => additional action parameter (string) * ) * * NOTES: * * $aResult[CHECK_ACTION_MESSAGE] contains a message with detailed information about the result, * already processed by the language file * * if $aResult[CHECK_ACTION_RESULT] === CHECK_ACTION_RESULT_ALLOWED then this node contains * an empty string * * The error messages themselves are stored in the language file. Additional variables are * passed to the languages.inc.php function _t_ext() as an array and can be used there in the form of * {0}, {1}, {2} ... * * Additional variables passed to the lang. file on errors (can be used in error messages): * * For all errors: * * $arg0[CHECK_ACTION_LANG_FILE_ACTION] = name of the action * $arg0[CHECK_ACTION_LANG_FILE_MEMBERSHIP]= name of the current membership * * CHECK_ACTION_RESULT_LIMIT_REACHED: * * $arg0[CHECK_ACTION_LANG_FILE_LIMIT] = limit on number of actions allowed for the profile * $arg0[CHECK_ACTION_LANG_FILE_PERIOD] = period that the limit is set for (in hours, 0 if unlimited) * $arg0[CHECK_ACTION_LANG_FILE_PERIOD_RESTART_AT] = time when new period begins, so counter will be reset * * CHECK_ACTION_RESULT_NOT_ALLOWED_BEFORE: * * $arg0[CHECK_ACTION_LANG_FILE_BEFORE] = date/time since when the action is allowed * * CHECK_ACTION_RESULT_NOT_ALLOWED_AFTER: * * $arg0[CHECK_ACTION_LANG_FILE_AFTER] = date/time since when the action is not allowed * * $aResult[CHECK_ACTION_PARAMETER] contains an additional parameter that can be considered * when performing the action (like the number of profiles to show in search result) */ function checkAction($iProfileId, $iActionId, $bPerformAction = false) { $aResult = array(); $aLangFileParams = array(); $iProfileId = (int)$iProfileId; $iActionId = (int)$iActionId; $bPerformAction = $bPerformAction ? true : false; $aMembership = $this->getMemberMembershipInfo($iProfileId); // get current profile's membership information $aLangFileParams[CHECK_ACTION_LANG_FILE_MEMBERSHIP] = _t($aMembership['name']); $aLangFileParams[CHECK_ACTION_LANG_FILE_SITE_EMAIL] = getParam('site_email'); $aAction = $this->oDb->getAction($aMembership['id'], $iActionId); if (!$aAction) bx_trigger_error('Unknown action ID: ' . $iActionId, 2); $aResult[CHECK_ACTION_PARAMETER] = $aAction['additional_param_value']; $aLangFileParams[CHECK_ACTION_LANG_FILE_ACTION] = !empty($aAction['title']) ? _t($aAction['title']) : $aAction['name']; /* Check membership action with ability to overwrite params - begin */ $bContinue = true; /** * @hooks * @hookdef hook-membership-check_action 'membership', 'check_action' - hook to override the result of checking whether an action is allowed or not * - $unit_name - equals `membership` * - $action - equals `check_action` * - $object_id - action id * - $sender_id - performer profile id * - $extra_params - array of additional params with the following array keys: * - `action` - [string] by ref, action name, can be overridden in hook processing * - `perform` - [boolean] by ref, check only or mark action as performed after checking, can be overridden in hook processing * - `result` - [array] by ref, check action result array, can be overridden in hook processing * - `lang` - [array] by ref, array of parsable into check result message variables, can be overridden in hook processing * - `continue` - [boolean] by ref, continue or not the default check operation after the hook, can be overridden in hook processing * @hook @ref hook-membership-check_action */ bx_alert('membership', 'check_action', $iActionId, $iProfileId, array( 'action' => &$aAction, 'perform' => &$bPerformAction, 'result' => &$aResult, 'lang' => &$aLangFileParams, 'continue' => &$bContinue, 'action_ref' => &$aAction, 'perform_ref' => &$bPerformAction, 'result_ref' => &$aResult, 'lang_ref' => &$aLangFileParams, 'continue_ref' => &$bContinue )); if (!$bContinue) return $aResult; /* Check membership action with ability to overwrite params - end */ /** * Action is not allowed for the current membership */ if (is_null($aAction['id'])) { $sLangKey = CHECK_ACTION_MESSAGE_NOT_ALLOWED; if (isset($this->_aLevel2MessageMap[$aMembership['id']])) $sLangKey = $this->_aLevel2MessageMap[$aMembership['id']]; $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_NOT_ALLOWED; $aResult[CHECK_ACTION_MESSAGE] = _t_ext($sLangKey, $aLangFileParams); return $aResult; } /** * Check fixed period limitations if present (also for non-members) */ if($aAction['allowed_period_start'] && time() < $aAction['allowed_period_start']) { $aLangFileParams[CHECK_ACTION_LANG_FILE_BEFORE] = bx_time_js($aAction['allowed_period_start'], BX_FORMAT_DATE_TIME); $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_NOT_ALLOWED_BEFORE; $aResult[CHECK_ACTION_MESSAGE] = _t_ext(CHECK_ACTION_MESSAGE_NOT_ALLOWED_BEFORE, $aLangFileParams); return $aResult; } if($aAction['allowed_period_end'] && time() > $aAction['allowed_period_end']) { $aLangFileParams[CHECK_ACTION_LANG_FILE_AFTER] = bx_time_js($aAction['allowed_period_end'], BX_FORMAT_DATE_TIME); $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_NOT_ALLOWED_AFTER; $aResult[CHECK_ACTION_MESSAGE] = _t_ext(CHECK_ACTION_MESSAGE_NOT_ALLOWED_AFTER, $aLangFileParams); return $aResult; } /** * if non-member, allow action without performing further checks */ if ($aMembership['id'] == MEMBERSHIP_ID_NON_MEMBER) { $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_ALLOWED; return $aResult; } /** * Check other limitations (for members only) */ $iAllowedCnt = (int)$aAction['allowed_count']; ///< Number of allowed actions. Unlimited if not specified or 0 $iPeriodLen = (int)$aAction['allowed_period_len']; ///< Period for AllowedCount in hours. If not specified, AllowedCount is treated as total number of actions permitted. if($iAllowedCnt > 0) { $aActionTrack = $this->oDb->getActionTrack($iActionId, $iProfileId); $iActionsLeft = $bPerformAction ? $iAllowedCnt - 1 : $iAllowedCnt; $iValidSince = time(); /** * Member is requesting/performing this action for the first time, * and there is no corresponding record in sys_acl_actions_track table. */ if(!$aActionTrack) { $this->oDb->insertActionTarck($iActionId, $iProfileId, $iActionsLeft, $iValidSince); $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_ALLOWED; return $aResult; } /** * Action has been requested/performed at least once at this point and there is a corresponding record in sys_acl_actions_track table * * Action record in sys_acl_actions_track table is out of date. */ $iPeriodEnd = (int)$aActionTrack['valid_since'] + $iPeriodLen * 3600; //ValidSince is in seconds, PeriodLen is in hours if($iPeriodLen > 0 && $iPeriodEnd < time()) { $this->oDb->updateActionTrack($iActionId, $iProfileId, $iActionsLeft, $iValidSince); $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_ALLOWED; return $aResult; } $iActionsLeft = (int)$aActionTrack['actions_left']; ///< Action record is up to date /** * Action limit reached for now */ if($iActionsLeft <= 0){ $aLangFileParams[CHECK_ACTION_LANG_FILE_LIMIT] = $iAllowedCnt; $aLangFileParams[CHECK_ACTION_LANG_FILE_PERIOD] = $iPeriodLen; $aLangFileParams[CHECK_ACTION_LANG_FILE_PERIOD_RESTART_AT] = bx_time_js($iPeriodEnd); $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_LIMIT_REACHED; $aResult[CHECK_ACTION_MESSAGE] = '
' . _t_ext(CHECK_ACTION_MESSAGE_LIMIT_REACHED, $aLangFileParams) . ($iPeriodLen > 0 ? _t_ext(CHECK_ACTION_MESSAGE_MESSAGE_EVERY_PERIOD, $aLangFileParams) : '') . '.
'; return $aResult; } if($bPerformAction) { $iActionsLeft--; $this->oDb->updateActionTrack($iActionId, $iProfileId, $iActionsLeft); } } $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_ALLOWED; return $aResult; } /** * Get the action data * * @param int $iProfileId ID of a profile to get action data for * @param int $iActionId ID of the action itself * @return array with action data */ function getAction($iProfileId, $iActionId) { $aMembership = $this->getMemberMembershipInfo($iProfileId); // get current profile's membership information return $this->oDb->getAction($aMembership['id'], $iActionId); } /** * Get the number of allowed action * * @param int $iProfileId ID of a profile that is going to perform an action * @param int $iActionId ID of the action itself * @param boolean $bPerformAction if true, then action information is updated, i.e. action is 'performed' * @return int if the action is countable, or true if it's not countable */ function getActionNumberLeft($iProfileId, $iActionId) { $aMembership = $this->getMemberMembershipInfo($iProfileId); // get current profile's membership information $aAction = $this->oDb->getAction($aMembership['id'], $iActionId); $iAllowedCnt = (int)$aAction['allowed_count']; ///< Number of allowed actions. Unlimited if not specified or 0 if($iAllowedCnt > 0) { $aActionTrack = $this->oDb->getActionTrack($iActionId, $iProfileId); if(!$aActionTrack) return $iAllowedCnt; return (int)$aActionTrack['actions_left']; } return true; } /** * Get the list of existing memberships * * @param bool $bPurchasableOnly if true, fetches only purchasable memberships; 'purchasable' here means that: * - MemLevels.Purchasable = 'yes' * - MemLevels.Active = 'yes' * - there is at least one pricing option for the membership * @return array( membershipID_1 => membershipName_1, membershipID_2 => membershipName_2, ...) if no such memberships, then just array() */ function getMemberships($bPurchasableOnly = false, $bActiveOnly = false, $isTranslate = true, $bFilterOutSystemAutomaticLevels = false) { $sType = 'all_pair'; if($bPurchasableOnly) $sType = 'all_active_purchasble_pair'; else if($bActiveOnly) $sType = 'all_active_pair'; $aLevels = array(); $this->oDb->getLevels(array('type' => $sType), $aLevels, false); if ($isTranslate) foreach ($aLevels as $k => $s) $aLevels[$k] = _t($s); if ($bFilterOutSystemAutomaticLevels) { unset($aLevels[MEMBERSHIP_ID_NON_MEMBER]); unset($aLevels[MEMBERSHIP_ID_ACCOUNT]); unset($aLevels[MEMBERSHIP_ID_UNCONFIRMED]); unset($aLevels[MEMBERSHIP_ID_PENDING]); unset($aLevels[MEMBERSHIP_ID_SUSPENDED]); } return $aLevels; } function getMembershipsBy($aParams) { $aLevels = array(); $this->oDb->getLevels($aParams, $aLevels, false); return $aLevels; } /** * Get info about a given membership * * @param int $iLevelId membership to get info about * @return array( * 'id' => ID, * 'name' => name, * 'icon' => icon, * 'description' => description, * 'active' => active, * 'purchasable' => purchasable, * 'removable' => removable * 'quota_size' => quota size, * 'quota_number' => quota number, * 'quota_max_file_size' => quota max file size * ) */ function getMembershipInfo($iLevelId) { $aLevel = array(); $this->oDb->getLevels(array('type' => 'by_id', 'value' => $iLevelId), $aLevel, false); return $aLevel; } /** * Retrieves information about membership for a given profile at a given moment. * * If there are no memberships purchased/assigned to the * given profile or all of them have expired at the given point, * the profile is assumed to be a standard profile, and the function * returns information about the Standard membership. This will * also happen if a profile wasnt actually registered in the database * at that point - the function will still return info about Standard * membership, not the Non-member one. * * If there is no profile with the given $iProfileId, * the function returns information about the Non-member or Authenticated * predefined membership. * * The Standard, Authenticated and Non-member memberships have their * DateStarts and DateExpires attributes set to NULL. * * @param int $iProfileId ID of a profile to get info about * @param int $time specifies the time to use when determining membership; if not specified, the function takes the current time * @return array( * 'id' => membership id, * 'name' => membership name, * 'date_starts' => (UNIX timestamp) date/time purchased, * 'date_expires' => (UNIX timestamp) date/time expires * ) */ function getMemberMembershipInfo($iProfileId, $iTime = 0, $bClearCache = 0) { $aMembershipCurrent = $this->getMemberMembershipInfoCurrent($iProfileId, $iTime, $bClearCache); if (isset($this->_aStandardMemberships[$aMembershipCurrent['id']])) return $aMembershipCurrent; $aMembership = $aMembershipCurrent; do { $iDateStarts = $aMembership['date_starts']; $aMembership = $this->getMemberMembershipInfoCurrent($iProfileId, ((int)$iDateStarts < 1 ? 0 : $iDateStarts - 1), $bClearCache); } while($aMembership['id'] == $aMembershipCurrent['id'] && (int)$aMembership['date_starts']); $aMembership = $aMembershipCurrent; do { $iDateExpires = $aMembership['date_expires']; $aMembership = $this->getMemberMembershipInfoCurrent($iProfileId, $iDateExpires, $bClearCache); } while($aMembership['id'] == $aMembershipCurrent['id'] && (int)$aMembership['date_expires']); $aMembershipCurrent['date_starts'] = $iDateStarts; $aMembershipCurrent['date_expires'] = $iDateExpires; return $aMembershipCurrent; } /** * Set a membership for a profile * * @param int $iProfileId profile that is going to get the membership * @param int $iLevelId membership that is going to be assigned to the profile * if $iLevelId == MEMBERSHIP_ID_STANDARD then $days and $bStartsNow parameters are not used, * so Standard membership is always set immediately and `forever` * @param mixed $mixedPeriod number of Days to set membership for or an array with 'period'-'period unit' pair. If number or 'period' in pair equal 0, then the membership is set forever * @param boolean $bStartsNow if true, the membership will start immediately if false, the membership will start after the current membership expires * @return boolean true in case of success, false in case of failure */ function setMembership($iProfileId, $iLevelId, $mixedPeriod = 0, $bStartsNow = false, $sTransactionId = '') { $iProfileId = (int)$iProfileId; $iLevelId = (int)$iLevelId; $bStartsNow = $bStartsNow ? true : false; if (!$iProfileId) $iProfileId = -1; if (empty($sTransactionId)) $sTransactionId = 'NULL'; // check if profile exists if(($sProfileEmail = BxDolProfileQuery::getInstance()->getEmailById($iProfileId)) === false) return false; // check if membership exists $aLevel = array(); $this->oDb->getLevels(array('type' => 'by_id', 'value' => $iLevelId), $aLevel, false); if(empty($aLevel) || !is_array($aLevel)) return false; if($iLevelId == MEMBERSHIP_ID_NON_MEMBER) return false; $aMembershipCurrent = $this->getMemberMembershipInfo($iProfileId); $aMembershipLatest = $this->getMemberMembershipInfoLatest($iProfileId); // setting Standard membership level if ($iLevelId == MEMBERSHIP_ID_STANDARD) { if ($aMembershipCurrent['id'] == MEMBERSHIP_ID_STANDARD) return true; // delete present and future memberships $bResult = $this->oDb->deleteLevelByProfileId($iProfileId); if($bResult) { $this->oDb->cleanMemory('BxDolAclQuery::getLevelCurrent' . $iProfileId . time()); unset(self::$_aCacheData[$iProfileId . '_0']); /** * @hooks * @hookdef hook-profile-unset_membership 'profile', 'unset_membership' - hook after a default membership level was set * - $unit_name - equals `profile` * - $action - equals `unset_membership` * - $object_id - not used * - $sender_id - profile id to set level to * - $extra_params - array of additional params with the following array keys: * - `mlevel` - [int] membership level id * @hook @ref hook-profile-unset_membership */ bx_alert('profile', 'unset_membership', '', $iProfileId, [ 'mlevel'=> $iLevelId ]); } return $bResult; } if ((is_numeric($mixedPeriod) && (int)$mixedPeriod < 0) || (is_array($mixedPeriod) && (!isset($mixedPeriod['period']) || $mixedPeriod['period'] < 0))) return false; /* * Make the membership starts after the latest membership expires or starts immediately * if latest membership is lifetime membership or immediate start was requested. */ $iDateStarts = time(); if ($bStartsNow || empty($aMembershipLatest['date_expires'])) { // Delete any profile's membership level and actions traces $this->oDb->deleteLevelByProfileId($iProfileId, true); $this->oDb->clearActionsTracksForMember($iProfileId); $this->oDb->cleanMemory('BxDolAclQuery::getLevelCurrent' . $iProfileId . time()); unset(self::$_aCacheData[$iProfileId . '_0']); } else $iDateStarts = $aMembershipLatest['date_expires']; // set lifetime membership if 0 days is used. if(is_numeric($mixedPeriod)) $mixedPeriod = array('period' => (int)$mixedPeriod, 'period_unit' => MEMBERSHIP_PERIOD_UNIT_DAY); if(!$this->oDb->insertLevelByProfileId($iProfileId, $iLevelId, $iDateStarts, $mixedPeriod, $sTransactionId)) return false; $bProlong = $iLevelId == $aMembershipCurrent['id']; /** * @hooks * @hookdef hook-profile-set_membership 'profile', 'set_membership' - hook after a new membership level was set * - $unit_name - equals `profile` * - $action - equals `set_membership` * - $object_id - not used * - $sender_id - profile id to set level to * - $extra_params - array of additional params with the following array keys: * - `mlevel` - [int] membership level id * - `period` - [int] number of periods * - `period_unit` - [string] period unit (day, week, month, etc) * - `starts_now` - [boolean] if new membership starts immediately or queued * - `txn_id` - [string] related payment transaction id * @hook @ref hook-profile-set_membership */ /** * @hooks * @hookdef hook-profile-prolong_membership 'profile', 'prolong_membership' - hook after current membership level was prolonged * It's equivalent to @ref hook-profile-set_membership * @hook @ref hook-profile-prolong_membership */ bx_alert('profile', ($bProlong ? 'prolong' : 'set') . '_membership', '', $iProfileId, [ 'mlevel'=> $iLevelId, 'period' => $mixedPeriod['period'], 'period_unit' => $mixedPeriod['period_unit'], 'starts_now' => $bStartsNow, 'txn_id' => $sTransactionId ]); // audit $aDataForAudit = array(); if (!empty($aMembershipCurrent)) $aDataForAudit = array('new_membership_level' => _t($aLevel['name']), 'old_membership_level' => _t($aMembershipCurrent['name'])); BxDolProfile::getInstance($iProfileId)->doAudit('_sys_audit_action_set_membership', $aDataForAudit); $oEmailTemplates = BxDolEmailTemplates::getInstance(); // Check if there is a special email template for this membership level $sTemplateName = 't_Mem' . ($bProlong ? 'Prolonged' : 'Changed') . '_' . $aLevel['id']; if ($oEmailTemplates->getTemplate($sTemplateName) === FALSE) $sTemplateName = 't_Mem' . ($bProlong ? 'Prolonged' : 'Changed'); // Send notification $aTemplate = $oEmailTemplates->parseTemplate($sTemplateName, array('membership_level' => _t($aLevel['name'])), 0, $iProfileId); if ($aTemplate) sendMail($sProfileEmail, $aTemplate['Subject'], $aTemplate['Body']); return true; } function unsetMembership($iProfileId, $iLevelId, $sTransactionId) { return $this->oDb->deleteLevelBy(array( 'IDMember' => $iProfileId, 'IDLevel' => $iLevelId, 'TransactionID' => $sTransactionId )); } /** * get action id by module and name * @param $sAction action name * @param $sModule module name * @param $aActions array of actions from sys_acl_actions table, with default array keys (starting from 0) and text values */ function getMembershipActionId($sAction, $sModule) { $this->oDb->getActions(array('type' => 'by_names_and_module', 'value' => $sAction, 'module' => $sModule), $aActions, false); if (count($aActions) > 1) trigger_error('Duplicate action - name:' . $sAction . ', module:' . $sModule, E_USER_ERROR); $aAction = array_pop($aActions); return $aAction['id']; } function getExpirationLetter($iProfileId, $sLevelName, $iLevelExpireDays ) { $iProfileId = (int)$iProfileId; if(!$iProfileId) return false; $oProfileQuery = BxDolProfileQuery::getInstance(); $sProfileEmail = $oProfileQuery->getEmailById($iProfileId); $aPlus = array( 'membership_name' => _t($sLevelName), 'expire_days' => $iLevelExpireDays, 'page_url' => BxDolRequest::serviceExists('bx_acl', 'get_view_url') ? BxDolService::call('bx_acl', 'get_view_url') : '#' ); $aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate('t_MemExpiration', $aPlus, 0, $iProfileId); $iResult = $aTemplate && sendMail($sProfileEmail, $aTemplate['Subject'], $aTemplate['Body'], $iProfileId, $aPlus); return !empty($iResult); } /** * clear expired membership levels */ public function maintenance() { return $this->oDb->maintenance(); } protected function getMemberMembershipInfoCurrent($iProfileId, $iTime = 0, $bClearCache = 0) { $sKey = $iProfileId . '_' . $iTime; if ($bClearCache && isset(self::$_aCacheData[$sKey])) unset(self::$_aCacheData[$sKey]); elseif (array_key_exists($sKey, self::$_aCacheData) && !defined('BX_DOL_INSTALL') && !defined('BX_DOL_CRON_EXECUTE')) return self::$_aCacheData[$sKey]; $aMemLevel = false; do { // get profile status $oProfile = BxDolProfile::getInstance($iProfileId); $aProfileInfo = $oProfile ? $oProfile->getInfo() : false; $sProfileStatus = $aProfileInfo ? $aProfileInfo['status'] : false; $sProfileType = $aProfileInfo ? $aProfileInfo['type'] : false; // account profile if($sProfileType == 'system') { $aMemLevel = $this->oDb->getLevelByIdCached(MEMBERSHIP_ID_ACCOUNT); if (!$aMemLevel) trigger_error ('Standard member level is missing: MEMBERSHIP_ID_ACCOUNT', E_USER_ERROR); break; } // check if account is unconfirmed, every account's profile is unconfirmed if account is unconfirmed $oAccount = $aProfileInfo ? BxDolAccount::getInstance($aProfileInfo['account_id']) : false; if ($oAccount && !$oAccount->isConfirmed()) { $aMemLevel = $this->oDb->getLevelByIdCached(MEMBERSHIP_ID_UNCONFIRMED); if (!$aMemLevel) trigger_error ('Standard member level is missing: MEMBERSHIP_ID_UNCONFIRMED', E_USER_ERROR); break; } // profile is not active, so return standard memberships according to profile status if (false === $sProfileStatus || BX_PROFILE_STATUS_ACTIVE != $sProfileStatus) { if (!isset($this->_aProfileStatus2LevelMap[$sProfileStatus])) $iLevelId = MEMBERSHIP_ID_NON_MEMBER; // if there is no profile status - then it isn't member else $iLevelId = $this->_aProfileStatus2LevelMap[$sProfileStatus]; // get member level id which associated with every non-active status $aMemLevel = $this->oDb->getLevelByIdCached($iLevelId); if (!$aMemLevel) trigger_error ('Standard member level is missing: ' . $iLevelId, E_USER_ERROR); break; } // profile is active get memebr level from profile $aMemLevel = $this->oDb->getLevelCurrent($oProfile ? (int)$oProfile->id() : (int)$iProfileId, $iTime); // There are no purchased/assigned memberships for the profile or all of them have expired. // In this case the profile is assumed to have Standard membership. if (!$aMemLevel || is_null($aMemLevel['id'])) { $aMemLevel = $this->oDb->getLevelByIdCached(MEMBERSHIP_ID_STANDARD); if (!$aMemLevel) trigger_error ('Standard member level is missing: ' . MEMBERSHIP_ID_NON_MEMBER, E_USER_ERROR); break; } } while (0); return (self::$_aCacheData[$sKey] = $aMemLevel); } protected function getMemberMembershipInfoLatest($iProfileId, $iTime = 0, $bClearCache = 0) { $aMembershipCurrent = $this->getMemberMembershipInfoCurrent($iProfileId, $iTime, $bClearCache); if (isset($this->_aStandardMemberships[$aMembershipCurrent['id']])) return $aMembershipCurrent; $aMembership = $aMembershipCurrent; while ($aMembership['id'] != MEMBERSHIP_ID_STANDARD) { $aMembershipLast = $aMembership; if(!isset($aMembership['date_expires']) || (int)$aMembership['date_expires'] == 0) break; $aMembership = $this->getMemberMembershipInfoCurrent($iProfileId, $aMembership['date_expires'], $bClearCache); } return $aMembershipLast; } public function onProfileDelete ($iProfileId) { return $this->oDb->deleteLevelByProfileId($iProfileId, true); } } function checkAction($iProfileId, $iActionId, $bPerformAction = false) { return BxDolAcl::getInstance()->checkAction($iProfileId, $iActionId, $bPerformAction); } function checkActionModule($iProfileId, $sActionName, $sModuleName, $bPerformAction = false) { $oACL = BxDolAcl::getInstance(); $iActionId = $oACL->getMembershipActionId($sActionName, $sModuleName); if (!$iActionId) bx_trigger_error("Unknown action: '$sActionName' in module '$sModuleName'", 1); return $oACL->checkAction($iProfileId, $iActionId, $bPerformAction); } function getActionModule($iProfileId, $sActionName, $sModuleName) { $oACL = BxDolAcl::getInstance(); $iActionId = $oACL->getMembershipActionId($sActionName, $sModuleName); if (!$iActionId) bx_trigger_error("Unknown action: '$sActionName' in module '$sModuleName'", 1); return $oACL->getAction($iProfileId, $iActionId); } function getActionNumberLeftModule($iProfileId, $sActionName, $sModuleName) { $oACL = BxDolAcl::getInstance(); $iActionId = $oACL->getMembershipActionId($sActionName, $sModuleName); if (!$iActionId) bx_trigger_error("Unknown action: '$sActionName' in module '$sModuleName'", 1); return $oACL->getActionNumberLeft($iProfileId, $iActionId); } /** @} */