BxDolAcl.php 33 KB

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