BxDolAcl.php 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740
  1. <?php defined('BX_DOL') or die('hack attempt');
  2. /**
  3. * Copyright (c) UNA, Inc - https://una.io
  4. * MIT License - https://opensource.org/licenses/MIT
  5. *
  6. * @defgroup UnaCore UNA Core
  7. * @{
  8. */
  9. /**
  10. * Message constants passed to _t_ext() function by checkAction()
  11. *
  12. * NOTE: checkAction() returns language dependent messages
  13. */
  14. define('CHECK_ACTION_MESSAGE_NOT_ALLOWED', "_sys_acl_action_not_allowed");
  15. define('CHECK_ACTION_MESSAGE_LIMIT_REACHED', "_sys_acl_action_limit_reached");
  16. define('CHECK_ACTION_MESSAGE_MESSAGE_EVERY_PERIOD', "_sys_acl_action_every_period");
  17. define('CHECK_ACTION_MESSAGE_NOT_ALLOWED_BEFORE', "_sys_acl_action_not_allowed_before");
  18. define('CHECK_ACTION_MESSAGE_NOT_ALLOWED_AFTER', "_sys_acl_action_not_allowed_after");
  19. define('CHECK_ACTION_MESSAGE_UNAUTHENTICATED', "_sys_acl_action_unauthenticated");
  20. define('CHECK_ACTION_MESSAGE_UNCONFIRMED', "_sys_acl_action_unconfirmed");
  21. define('CHECK_ACTION_MESSAGE_PENDING', "_sys_acl_action_pending");
  22. define('CHECK_ACTION_MESSAGE_SUSPENDED', "_sys_acl_action_suspended");
  23. /**
  24. * Nodes of $args array that are passed to _t_ext() function by checkAction()
  25. */
  26. define('CHECK_ACTION_LANG_FILE_ACTION', 1);
  27. define('CHECK_ACTION_LANG_FILE_MEMBERSHIP', 2);
  28. define('CHECK_ACTION_LANG_FILE_LIMIT', 3);
  29. define('CHECK_ACTION_LANG_FILE_PERIOD', 4);
  30. define('CHECK_ACTION_LANG_FILE_AFTER', 5);
  31. define('CHECK_ACTION_LANG_FILE_BEFORE', 6);
  32. define('CHECK_ACTION_LANG_FILE_SITE_EMAIL', 7);
  33. /**
  34. * Standard membership ID's
  35. */
  36. define('MEMBERSHIP_ID_NON_MEMBER', 1);
  37. define('MEMBERSHIP_ID_ACCOUNT', 2);
  38. define('MEMBERSHIP_ID_STANDARD', 3);
  39. define('MEMBERSHIP_ID_UNCONFIRMED', 4);
  40. define('MEMBERSHIP_ID_PENDING', 5);
  41. define('MEMBERSHIP_ID_SUSPENDED', 6);
  42. define('MEMBERSHIP_ID_MODERATOR', 7);
  43. define('MEMBERSHIP_ID_ADMINISTRATOR', 8);
  44. /**
  45. * Indices for checkAction() result array
  46. */
  47. define('CHECK_ACTION_RESULT', 0);
  48. define('CHECK_ACTION_MESSAGE', 1);
  49. define('CHECK_ACTION_PARAMETER', 3);
  50. /**
  51. * CHECK_ACTION_RESULT node values
  52. */
  53. define('CHECK_ACTION_RESULT_ALLOWED', 0);
  54. define('CHECK_ACTION_RESULT_NOT_ALLOWED', 1);
  55. define('CHECK_ACTION_RESULT_NOT_ACTIVE', 2);
  56. define('CHECK_ACTION_RESULT_LIMIT_REACHED', 3);
  57. define('CHECK_ACTION_RESULT_NOT_ALLOWED_BEFORE', 4);
  58. define('CHECK_ACTION_RESULT_NOT_ALLOWED_AFTER', 5);
  59. /**
  60. * Standard period units
  61. */
  62. define('MEMBERSHIP_PERIOD_UNIT_DAY', 'day');
  63. define('MEMBERSHIP_PERIOD_UNIT_WEEK', 'week');
  64. define('MEMBERSHIP_PERIOD_UNIT_MONTH', 'month');
  65. define('MEMBERSHIP_PERIOD_UNIT_YEAR', 'year');
  66. class BxDolAcl extends BxDolFactory implements iBxDolSingleton
  67. {
  68. protected $oDb;
  69. protected $_aStandardMemberships = array(
  70. MEMBERSHIP_ID_NON_MEMBER => 1,
  71. MEMBERSHIP_ID_ACCOUNT => 1,
  72. MEMBERSHIP_ID_UNCONFIRMED => 1,
  73. MEMBERSHIP_ID_PENDING => 1,
  74. MEMBERSHIP_ID_SUSPENDED => 1,
  75. MEMBERSHIP_ID_STANDARD => 1,
  76. );
  77. protected $_aProfileStatus2LevelMap = array (
  78. BX_PROFILE_STATUS_SUSPENDED => MEMBERSHIP_ID_SUSPENDED,
  79. BX_PROFILE_STATUS_PENDING => MEMBERSHIP_ID_PENDING,
  80. );
  81. protected $_aLevel2MessageMap = array (
  82. MEMBERSHIP_ID_NON_MEMBER => '_sys_acl_action_unauthenticated',
  83. MEMBERSHIP_ID_ACCOUNT => '_sys_acl_action_account',
  84. MEMBERSHIP_ID_UNCONFIRMED => '_sys_acl_action_unconfirmed',
  85. MEMBERSHIP_ID_PENDING => '_sys_acl_action_pending',
  86. MEMBERSHIP_ID_SUSPENDED => '_sys_acl_action_suspended',
  87. );
  88. protected function __construct()
  89. {
  90. if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
  91. trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
  92. parent::__construct();
  93. $this->oDb = BxDolAclQuery::getInstance();
  94. }
  95. /**
  96. * Prevent cloning the instance
  97. */
  98. public function __clone()
  99. {
  100. if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
  101. trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
  102. }
  103. /**
  104. * Get singleton instance of the class
  105. */
  106. public static function getInstance()
  107. {
  108. if(!isset($GLOBALS['bxDolClasses'][__CLASS__]))
  109. $GLOBALS['bxDolClasses'][__CLASS__] = new BxTemplAcl();
  110. return $GLOBALS['bxDolClasses'][__CLASS__];
  111. }
  112. /**
  113. * Get necessary condition array to use membership level in search classes
  114. * @param $sContentField content table field name
  115. * @param $mixedLevelId level ID or array of level IDs
  116. * @return array of conditions is returned
  117. */
  118. public function getContentByLevelAsCondition($sContentField, $mixedLevelId)
  119. {
  120. return array(
  121. 'restriction_sql' => ' AND (`tlm`.DateStarts IS NULL OR `tlm`.DateStarts <= NOW()) AND (`tlm`.DateExpires IS NULL OR `tlm`.DateExpires > NOW())',
  122. 'restriction' => array (
  123. 'acl_members' => array(
  124. 'value' => $mixedLevelId,
  125. 'field' => 'IDLevel',
  126. 'operator' => is_array($mixedLevelId) ? 'in' : '=',
  127. 'table' => 'tlm',
  128. ),
  129. ),
  130. 'join' => array (
  131. 'acl_members' => array(
  132. 'type' => 'INNER',
  133. 'table' => 'sys_acl_levels_members',
  134. 'table_alias' => 'tlm',
  135. 'mainField' => $sContentField,
  136. 'onField' => 'IDMember',
  137. 'joinFields' => array(),
  138. ),
  139. 'acl_levels' => array(
  140. 'type' => 'INNER',
  141. 'table' => 'sys_acl_levels',
  142. 'table_alias' => 'tl',
  143. 'mainTable' => 'tlm',
  144. 'mainField' => 'IDLevel',
  145. 'onField' => 'ID',
  146. 'joinFields' => array(),
  147. ),
  148. ),
  149. );
  150. }
  151. /**
  152. * Get necessary parts of SQL query to use membership levels in other queries
  153. * @param $sContentTable content table name
  154. * @param $sContentField content table field name
  155. * @param $mixedLevelId level ID or array of level IDs
  156. * @return array of SQL string parts, for now 'where' part only is returned
  157. */
  158. public function getContentByLevelAsSQLPart($sContentTable, $sContentField, $mixedLevelId)
  159. {
  160. return $this->oDb->getContentByLevelAsSQLPart($sContentTable, $sContentField, $mixedLevelId);
  161. }
  162. /**
  163. * Check if member has one of the provided membership levels
  164. * @param $mixedPermissions - integer value (every bit is matched with some membership ID) or an array of membership IDs to check permissions for
  165. * @param $iProfileId - profile to check, if it isn't provided or is false then currently logged in profile is used.
  166. * @return true if member has privided membership levels, or false if member hasn't.
  167. */
  168. public function isMemberLevelInSet($mixedPermissions, $iProfileId = false)
  169. {
  170. $iPermissions = 0;
  171. if(is_array($mixedPermissions))
  172. foreach($mixedPermissions as $iPermissionId)
  173. $iPermissions += pow(2, $iPermissionId - 1);
  174. else if(is_numeric($mixedPermissions))
  175. $iPermissions = (int)$mixedPermissions;
  176. if(!$iPermissions)
  177. return false;
  178. if(false === $iProfileId) {
  179. $oProfile = BxDolProfile::getInstance();
  180. $iProfileId = $oProfile ? $oProfile->id() : 0;
  181. }
  182. $aACL = $this->getMemberMembershipInfo($iProfileId);
  183. return ($iPermissions & pow(2, $aACL['id'] - 1));
  184. }
  185. /**
  186. * Checks if a given action is allowed for a given profile and updates action information if the
  187. * action is performed.
  188. *
  189. * @param int $iProfileId ID of a profile that is going to perform an action
  190. * @param int $iActionId ID of the action itself
  191. * @param boolean $bPerformAction if true, then action information is updated, i.e. action is 'performed'
  192. * @return array(
  193. * CHECK_ACTION_RESULT => CHECK_ACTION_RESULT_ constant,
  194. * CHECK_ACTION_MESSAGE => CHECK_ACTION_MESSAGE_ constant,
  195. * CHECK_ACTION_PARAMETER => additional action parameter (string)
  196. * )
  197. *
  198. * NOTES:
  199. *
  200. * $aResult[CHECK_ACTION_MESSAGE] contains a message with detailed information about the result,
  201. * already processed by the language file
  202. *
  203. * if $aResult[CHECK_ACTION_RESULT] === CHECK_ACTION_RESULT_ALLOWED then this node contains
  204. * an empty string
  205. *
  206. * The error messages themselves are stored in the language file. Additional variables are
  207. * passed to the languages.inc.php function _t_ext() as an array and can be used there in the form of
  208. * {0}, {1}, {2} ...
  209. *
  210. * Additional variables passed to the lang. file on errors (can be used in error messages):
  211. *
  212. * For all errors:
  213. *
  214. * $arg0[CHECK_ACTION_LANG_FILE_ACTION] = name of the action
  215. * $arg0[CHECK_ACTION_LANG_FILE_MEMBERSHIP]= name of the current membership
  216. *
  217. * CHECK_ACTION_RESULT_LIMIT_REACHED:
  218. *
  219. * $arg0[CHECK_ACTION_LANG_FILE_LIMIT] = limit on number of actions allowed for the profile
  220. * $arg0[CHECK_ACTION_LANG_FILE_PERIOD] = period that the limit is set for (in hours, 0 if unlimited)
  221. *
  222. * CHECK_ACTION_RESULT_NOT_ALLOWED_BEFORE:
  223. *
  224. * $arg0[CHECK_ACTION_LANG_FILE_BEFORE] = date/time since when the action is allowed
  225. *
  226. * CHECK_ACTION_RESULT_NOT_ALLOWED_AFTER:
  227. *
  228. * $arg0[CHECK_ACTION_LANG_FILE_AFTER] = date/time since when the action is not allowed
  229. *
  230. * $aResult[CHECK_ACTION_PARAMETER] contains an additional parameter that can be considered
  231. * when performing the action (like the number of profiles to show in search result)
  232. */
  233. function checkAction($iProfileId, $iActionId, $bPerformAction = false)
  234. {
  235. $aResult = array();
  236. $aLangFileParams = array();
  237. $iProfileId = (int)$iProfileId;
  238. $iActionId = (int)$iActionId;
  239. $bPerformAction = $bPerformAction ? true : false;
  240. $aMembership = $this->getMemberMembershipInfo($iProfileId); // get current profile's membership information
  241. $aLangFileParams[CHECK_ACTION_LANG_FILE_MEMBERSHIP] = _t($aMembership['name']);
  242. $aLangFileParams[CHECK_ACTION_LANG_FILE_SITE_EMAIL] = getParam('site_email');
  243. $aAction = $this->oDb->getAction($aMembership['id'], $iActionId);
  244. if (!$aAction)
  245. bx_trigger_error('Unknown action ID: ' . $iActionId, 2);
  246. $aResult[CHECK_ACTION_PARAMETER] = $aAction['additional_param_value'];
  247. $aLangFileParams[CHECK_ACTION_LANG_FILE_ACTION] = !empty($aAction['title']) ? _t($aAction['title']) : $aAction['name'];
  248. /**
  249. * Action is not allowed for the current membership
  250. */
  251. if (is_null($aAction['id'])) {
  252. $sLangKey = CHECK_ACTION_MESSAGE_NOT_ALLOWED;
  253. if (isset($this->_aLevel2MessageMap[$aMembership['id']]))
  254. $sLangKey = $this->_aLevel2MessageMap[$aMembership['id']];
  255. $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_NOT_ALLOWED;
  256. $aResult[CHECK_ACTION_MESSAGE] = _t_ext($sLangKey, $aLangFileParams);
  257. return $aResult;
  258. }
  259. /**
  260. * Check fixed period limitations if present (also for non-members)
  261. */
  262. if($aAction['allowed_period_start'] && time() < $aAction['allowed_period_start']) {
  263. $aLangFileParams[CHECK_ACTION_LANG_FILE_BEFORE] = bx_time_js($aAction['allowed_period_start'], BX_FORMAT_DATE_TIME);
  264. $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_NOT_ALLOWED_BEFORE;
  265. $aResult[CHECK_ACTION_MESSAGE] = _t_ext(CHECK_ACTION_MESSAGE_NOT_ALLOWED_BEFORE, $aLangFileParams);
  266. return $aResult;
  267. }
  268. if($aAction['allowed_period_end'] && time() > $aAction['allowed_period_end']) {
  269. $aLangFileParams[CHECK_ACTION_LANG_FILE_AFTER] = bx_time_js($aAction['allowed_period_end'], BX_FORMAT_DATE_TIME);
  270. $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_NOT_ALLOWED_AFTER;
  271. $aResult[CHECK_ACTION_MESSAGE] = _t_ext(CHECK_ACTION_MESSAGE_NOT_ALLOWED_AFTER, $aLangFileParams);
  272. return $aResult;
  273. }
  274. /**
  275. * if non-member, allow action without performing further checks
  276. */
  277. if ($aMembership['id'] == MEMBERSHIP_ID_NON_MEMBER) {
  278. $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_ALLOWED;
  279. return $aResult;
  280. }
  281. /**
  282. * Check other limitations (for members only)
  283. */
  284. $iAllowedCnt = (int)$aAction['allowed_count']; ///< Number of allowed actions. Unlimited if not specified or 0
  285. $iPeriodLen = (int)$aAction['allowed_period_len']; ///< Period for AllowedCount in hours. If not specified, AllowedCount is treated as total number of actions permitted.
  286. if($iAllowedCnt > 0) {
  287. $aActionTrack = $this->oDb->getActionTrack($iActionId, $iProfileId);
  288. $iActionsLeft = $bPerformAction ? $iAllowedCnt - 1 : $iAllowedCnt;
  289. $iValidSince = time();
  290. /**
  291. * Member is requesting/performing this action for the first time,
  292. * and there is no corresponding record in sys_acl_actions_track table.
  293. */
  294. if(!$aActionTrack) {
  295. $this->oDb->insertActionTarck($iActionId, $iProfileId, $iActionsLeft, $iValidSince);
  296. $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_ALLOWED;
  297. return $aResult;
  298. }
  299. /**
  300. * Action has been requested/performed at least once at this point and there is a corresponding record in sys_acl_actions_track table
  301. *
  302. * Action record in sys_acl_actions_track table is out of date.
  303. */
  304. $iPeriodEnd = (int)$aActionTrack['valid_since'] + $iPeriodLen * 3600; //ValidSince is in seconds, PeriodLen is in hours
  305. if($iPeriodLen > 0 && $iPeriodEnd < time()) {
  306. $this->oDb->updateActionTrack($iActionId, $iProfileId, $iActionsLeft, $iValidSince);
  307. $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_ALLOWED;
  308. return $aResult;
  309. }
  310. $iActionsLeft = (int)$aActionTrack['actions_left']; ///< Action record is up to date
  311. /**
  312. * Action limit reached for now
  313. */
  314. if($iActionsLeft <= 0){
  315. $aLangFileParams[CHECK_ACTION_LANG_FILE_LIMIT] = $iAllowedCnt;
  316. $aLangFileParams[CHECK_ACTION_LANG_FILE_PERIOD] = $iPeriodLen;
  317. $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_LIMIT_REACHED;
  318. $aResult[CHECK_ACTION_MESSAGE] = '<div class="bx-acl-err-msg">' . _t_ext(CHECK_ACTION_MESSAGE_LIMIT_REACHED, $aLangFileParams) . ($iPeriodLen > 0 ? _t_ext(CHECK_ACTION_MESSAGE_MESSAGE_EVERY_PERIOD, $aLangFileParams) : '') . '.</div>';
  319. return $aResult;
  320. }
  321. if($bPerformAction) {
  322. $iActionsLeft--;
  323. $this->oDb->updateActionTrack($iActionId, $iProfileId, $iActionsLeft);
  324. }
  325. }
  326. $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_ALLOWED;
  327. return $aResult;
  328. }
  329. /**
  330. * Get the list of existing memberships
  331. *
  332. * @param bool $bPurchasableOnly if true, fetches only purchasable memberships; 'purchasable' here means that:
  333. * - MemLevels.Purchasable = 'yes'
  334. * - MemLevels.Active = 'yes'
  335. * - there is at least one pricing option for the membership
  336. * @return array( membershipID_1 => membershipName_1, membershipID_2 => membershipName_2, ...) if no such memberships, then just array()
  337. */
  338. function getMemberships($bPurchasableOnly = false, $bActiveOnly = false, $isTranslate = true, $bFilterOutSystemAutomaticLevels = false)
  339. {
  340. $sType = 'all_pair';
  341. if($bPurchasableOnly)
  342. $sType = 'all_active_purchasble_pair';
  343. else if($bActiveOnly)
  344. $sType = 'all_active_pair';
  345. $aLevels = array();
  346. $this->oDb->getLevels(array('type' => $sType), $aLevels, false);
  347. if ($isTranslate)
  348. foreach ($aLevels as $k => $s)
  349. $aLevels[$k] = _t($s);
  350. if ($bFilterOutSystemAutomaticLevels) {
  351. unset($aLevels[MEMBERSHIP_ID_NON_MEMBER]);
  352. unset($aLevels[MEMBERSHIP_ID_ACCOUNT]);
  353. unset($aLevels[MEMBERSHIP_ID_UNCONFIRMED]);
  354. unset($aLevels[MEMBERSHIP_ID_PENDING]);
  355. unset($aLevels[MEMBERSHIP_ID_SUSPENDED]);
  356. }
  357. return $aLevels;
  358. }
  359. function getMembershipsBy($aParams)
  360. {
  361. $aLevels = array();
  362. $this->oDb->getLevels($aParams, $aLevels, false);
  363. return $aLevels;
  364. }
  365. /**
  366. * Get info about a given membership
  367. *
  368. * @param int $iLevelId membership to get info about
  369. * @return array(
  370. * 'id' => ID,
  371. * 'name' => name,
  372. * 'icon' => icon,
  373. * 'description' => description,
  374. * 'active' => active,
  375. * 'purchasable' => purchasable,
  376. * 'removable' => removable
  377. * 'quota_size' => quota size,
  378. * 'quota_number' => quota number,
  379. * 'quota_max_file_size' => quota max file size
  380. * )
  381. */
  382. function getMembershipInfo($iLevelId)
  383. {
  384. $aLevel = array();
  385. $this->oDb->getLevels(array('type' => 'by_id', 'value' => $iLevelId), $aLevel, false);
  386. return $aLevel;
  387. }
  388. /**
  389. * Retrieves information about membership for a given profile at a given moment.
  390. *
  391. * If there are no memberships purchased/assigned to the
  392. * given profile or all of them have expired at the given point,
  393. * the profile is assumed to be a standard profile, and the function
  394. * returns information about the Standard membership. This will
  395. * also happen if a profile wasnt actually registered in the database
  396. * at that point - the function will still return info about Standard
  397. * membership, not the Non-member one.
  398. *
  399. * If there is no profile with the given $iProfileId,
  400. * the function returns information about the Non-member or Authenticated
  401. * predefined membership.
  402. *
  403. * The Standard, Authenticated and Non-member memberships have their
  404. * DateStarts and DateExpires attributes set to NULL.
  405. *
  406. * @param int $iProfileId ID of a profile to get info about
  407. * @param int $time specifies the time to use when determining membership; if not specified, the function takes the current time
  408. * @return array(
  409. * 'id' => membership id,
  410. * 'name' => membership name,
  411. * 'date_starts' => (UNIX timestamp) date/time purchased,
  412. * 'date_expires' => (UNIX timestamp) date/time expires
  413. * )
  414. */
  415. function getMemberMembershipInfo($iProfileId, $iTime = 0)
  416. {
  417. $aMembershipCurrent = $this->getMemberMembershipInfoCurrent($iProfileId, $iTime);
  418. if (isset($this->_aStandardMemberships[$aMembershipCurrent['id']]))
  419. return $aMembershipCurrent;
  420. $aMembership = $aMembershipCurrent;
  421. do {
  422. $iDateStarts = $aMembership['date_starts'];
  423. $aMembership = $this->getMemberMembershipInfoCurrent($iProfileId, ((int)$iDateStarts < 1 ? 0 : $iDateStarts - 1));
  424. }
  425. while($aMembership['id'] == $aMembershipCurrent['id'] && (int)$aMembership['date_starts']);
  426. $aMembership = $aMembershipCurrent;
  427. do {
  428. $iDateExpires = $aMembership['date_expires'];
  429. $aMembership = $this->getMemberMembershipInfoCurrent($iProfileId, $iDateExpires);
  430. } while($aMembership['id'] == $aMembershipCurrent['id'] && (int)$aMembership['date_expires']);
  431. $aMembershipCurrent['date_starts'] = $iDateStarts;
  432. $aMembershipCurrent['date_expires'] = $iDateExpires;
  433. return $aMembershipCurrent;
  434. }
  435. /**
  436. * Set a membership for a profile
  437. *
  438. * @param int $iProfileId profile that is going to get the membership
  439. * @param int $iLevelId membership that is going to be assigned to the profile
  440. * if $iLevelId == MEMBERSHIP_ID_STANDARD then $days and $bStartsNow parameters are not used,
  441. * so Standard membership is always set immediately and `forever`
  442. * @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
  443. * @param boolean $bStartsNow if true, the membership will start immediately if false, the membership will start after the current membership expires
  444. * @return boolean true in case of success, false in case of failure
  445. */
  446. function setMembership($iProfileId, $iLevelId, $mixedPeriod = 0, $bStartsNow = false, $sTransactionId = '')
  447. {
  448. $iProfileId = (int)$iProfileId;
  449. $iLevelId = (int)$iLevelId;
  450. $bStartsNow = $bStartsNow ? true : false;
  451. if (!$iProfileId)
  452. $iProfileId = -1;
  453. if (empty($sTransactionId))
  454. $sTransactionId = 'NULL';
  455. // check if profile exists
  456. if(($sProfileEmail = BxDolProfileQuery::getInstance()->getEmailById($iProfileId)) === false)
  457. return false;
  458. // check if membership exists
  459. $aLevel = array();
  460. $this->oDb->getLevels(array('type' => 'by_id', 'value' => $iLevelId), $aLevel, false);
  461. if(empty($aLevel) || !is_array($aLevel))
  462. return false;
  463. if($iLevelId == MEMBERSHIP_ID_NON_MEMBER)
  464. return false;
  465. $aMembershipCurrent = $this->getMemberMembershipInfo($iProfileId);
  466. $aMembershipLatest = $this->getMemberMembershipInfoLatest($iProfileId);
  467. // setting Standard membership level
  468. if ($iLevelId == MEMBERSHIP_ID_STANDARD) {
  469. if ($aMembershipCurrent['id'] == MEMBERSHIP_ID_STANDARD)
  470. return true;
  471. // delete present and future memberships
  472. $bResult = $this->oDb->deleteLevelByProfileId($iProfileId);
  473. if($bResult)
  474. $this->oDb->cleanMemory('BxDolAclQuery::getLevelCurrent' . $iProfileId . time());
  475. return $bResult;
  476. }
  477. if ((is_numeric($mixedPeriod) && (int)$mixedPeriod < 0) || (is_array($mixedPeriod) && (!isset($mixedPeriod['period']) || $mixedPeriod['period'] < 0)))
  478. return false;
  479. $iDateStarts = time();
  480. if (!$bStartsNow) {
  481. // Make the membership starts after the latest membership expires or return false if latest membership isn't Standard and is lifetime membership.
  482. if(!empty($aMembershipLatest['date_expires']))
  483. $iDateStarts = $aMembershipLatest['date_expires'];
  484. else if(empty($aMembershipLatest['date_expires']) && $aMembershipLatest['id'] != MEMBERSHIP_ID_STANDARD)
  485. return false;
  486. }
  487. else {
  488. // Delete any profile's membership level and actions traces
  489. $this->oDb->deleteLevelByProfileId($iProfileId, true);
  490. $this->oDb->clearActionsTracksForMember($iProfileId);
  491. $this->oDb->cleanMemory('BxDolAclQuery::getLevelCurrent' . $iProfileId . time());
  492. }
  493. // set lifetime membership if 0 days is used.
  494. if(is_numeric($mixedPeriod))
  495. $mixedPeriod = array('period' => (int)$mixedPeriod, 'period_unit' => MEMBERSHIP_PERIOD_UNIT_DAY);
  496. if(!$this->oDb->insertLevelByProfileId($iProfileId, $iLevelId, $iDateStarts, $mixedPeriod, $sTransactionId))
  497. return false;
  498. // raise membership alert
  499. $oZ = new BxDolAlerts('profile', 'set_membership', '', $iProfileId, array('mlevel'=> $iLevelId, 'period' => $mixedPeriod['period'], 'period_unit' => $mixedPeriod['period_unit'], 'starts_now' => $bStartsNow, 'txn_id' => $sTransactionId));
  500. $oZ->alert();
  501. // Send notification
  502. $aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate('t_MemChanged', array('membership_level' => _t($aLevel['name'])), 0, $iProfileId);
  503. if ($aTemplate)
  504. sendMail($sProfileEmail, $aTemplate['Subject'], $aTemplate['Body']);
  505. return true;
  506. }
  507. function unsetMembership($iProfileId, $iLevelId, $sTransactionId)
  508. {
  509. return $this->oDb->deleteLevelBy(array(
  510. 'IDMember' => $iProfileId,
  511. 'IDLevel' => $iLevelId,
  512. 'TransactionID' => $sTransactionId
  513. ));
  514. }
  515. /**
  516. * get action id by module and name
  517. * @param $sAction action name
  518. * @param $sModule module name
  519. * @param $aActions array of actions from sys_acl_actions table, with default array keys (starting from 0) and text values
  520. */
  521. function getMembershipActionId($sAction, $sModule)
  522. {
  523. $this->oDb->getActions(array('type' => 'by_names_and_module', 'value' => $sAction, 'module' => $sModule), $aActions, false);
  524. if (count($aActions) > 1)
  525. trigger_error('Duplicate action - name:' . $sAction . ', module:' . $sModule, E_USER_ERROR);
  526. $aAction = array_pop($aActions);
  527. return $aAction['id'];
  528. }
  529. function getExpirationLetter($iProfileId, $sLevelName, $iLevelExpireDays )
  530. {
  531. $iProfileId = (int)$iProfileId;
  532. if(!$iProfileId)
  533. return false;
  534. $oProfileQuery = BxDolProfileQuery::getInstance();
  535. $sProfileEmail = $oProfileQuery->getEmailById($iProfileId);
  536. $aPlus = array(
  537. 'membership_name' => $sLevelName,
  538. 'expire_days' => $iLevelExpireDays,
  539. 'page_url' => BxDolRequest::serviceExists('bx_acl', 'get_view_url') ? BxDolService::call('bx_acl', 'get_view_url') : '#'
  540. );
  541. $aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate('t_MemExpiration', $aPlus, 0, $iProfileId);
  542. $iResult = $aTemplate && sendMail($sProfileEmail, $aTemplate['Subject'], $aTemplate['Body'], $iProfileId, $aPlus);
  543. return !empty($iResult);
  544. }
  545. /**
  546. * clear expired membership levels
  547. */
  548. public function maintenance()
  549. {
  550. return $this->oDb->maintenance();
  551. }
  552. protected function getMemberMembershipInfoCurrent($iProfileId, $iTime = 0)
  553. {
  554. $aMemLevel = false;
  555. // get profile status
  556. $oProfile = BxDolProfile::getInstance($iProfileId);
  557. $aProfileInfo = $oProfile ? $oProfile->getInfo() : false;
  558. $sProfileStatus = $aProfileInfo ? $aProfileInfo['status'] : false;
  559. $sProfileType = $aProfileInfo ? $aProfileInfo['type'] : false;
  560. // account profile
  561. if($sProfileType == 'system') {
  562. $aMemLevel = $this->oDb->getLevelByIdCached(MEMBERSHIP_ID_ACCOUNT);
  563. if (!$aMemLevel)
  564. trigger_error ('Standard member level is missing: MEMBERSHIP_ID_ACCOUNT', E_USER_ERROR);
  565. return $aMemLevel;
  566. }
  567. // check if account is unconfirmed, every account's profile is unconfirmed if account is unconfirmed
  568. $oAccount = $aProfileInfo ? BxDolAccount::getInstance($aProfileInfo['account_id']) : false;
  569. if ($oAccount && !$oAccount->isConfirmed()) {
  570. $aMemLevel = $this->oDb->getLevelByIdCached(MEMBERSHIP_ID_UNCONFIRMED);
  571. if (!$aMemLevel)
  572. trigger_error ('Standard member level is missing: MEMBERSHIP_ID_UNCONFIRMED', E_USER_ERROR);
  573. return $aMemLevel;
  574. }
  575. // profile is not active, so return standard memberships according to profile status
  576. if (false === $sProfileStatus || BX_PROFILE_STATUS_ACTIVE != $sProfileStatus) {
  577. if (!isset($this->_aProfileStatus2LevelMap[$sProfileStatus]))
  578. $iLevelId = MEMBERSHIP_ID_NON_MEMBER; // if there is no profile status - then it isn't member
  579. else
  580. $iLevelId = $this->_aProfileStatus2LevelMap[$sProfileStatus]; // get member level id which associated with every non-active status
  581. $aMemLevel = $this->oDb->getLevelByIdCached($iLevelId);
  582. if (!$aMemLevel)
  583. trigger_error ('Standard member level is missing: ' . $iLevelId, E_USER_ERROR);
  584. return $aMemLevel;
  585. }
  586. // profile is active get memebr level from profile
  587. $aMemLevel = $this->oDb->getLevelCurrent((int)$iProfileId, $iTime);
  588. // There are no purchased/assigned memberships for the profile or all of them have expired.
  589. // In this case the profile is assumed to have Standard membership.
  590. if (!$aMemLevel || is_null($aMemLevel['id'])) {
  591. $aMemLevel = $this->oDb->getLevelByIdCached(MEMBERSHIP_ID_STANDARD);
  592. if (!$aMemLevel)
  593. trigger_error ('Standard member level is missing: ' . MEMBERSHIP_ID_NON_MEMBER, E_USER_ERROR);
  594. }
  595. return $aMemLevel;
  596. }
  597. protected function getMemberMembershipInfoLatest($iProfileId, $iTime = 0)
  598. {
  599. $aMembershipCurrent = $this->getMemberMembershipInfoCurrent($iProfileId, $iTime);
  600. if (isset($this->_aStandardMemberships[$aMembershipCurrent['id']]))
  601. return $aMembershipCurrent;
  602. $aMembership = $aMembershipCurrent;
  603. while ($aMembership['id'] != MEMBERSHIP_ID_STANDARD) {
  604. $aMembershipLast = $aMembership;
  605. if(!isset($aMembership['date_expires']) || (int)$aMembership['date_expires'] == 0)
  606. break;
  607. $aMembership = $this->getMemberMembershipInfoCurrent($iProfileId, $aMembership['date_expires']);
  608. }
  609. return $aMembershipLast;
  610. }
  611. public function onProfileDelete ($iProfileId)
  612. {
  613. return $this->oDb->deleteLevelByProfileId($iProfileId, true);
  614. }
  615. }
  616. function checkAction($iProfileId, $iActionId, $bPerformAction = false)
  617. {
  618. return BxDolAcl::getInstance()->checkAction($iProfileId, $iActionId, $bPerformAction);
  619. }
  620. function checkActionModule($iProfileId, $sActionName, $sModuleName, $bPerformAction = false)
  621. {
  622. $oACL = BxDolAcl::getInstance();
  623. $iActionId = $oACL->getMembershipActionId($sActionName, $sModuleName);
  624. return $oACL->checkAction($iProfileId, $iActionId, $bPerformAction);
  625. }
  626. /** @} */