BxDolPrivacy.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  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. define('BX_DOL_PG_HIDDEN', '1');
  10. define('BX_DOL_PG_MEONLY', '2');
  11. define('BX_DOL_PG_ALL', '3');
  12. define('BX_DOL_PG_MEMBERS', '4');
  13. define('BX_DOL_PG_FRIENDS', '5');
  14. define('BX_DOL_PG_DEFAULT', BX_DOL_PG_ALL);
  15. /**
  16. * Privacy settings for any content.
  17. *
  18. * Integration of the content with privacy engine allows site member
  19. * to organize the access to his content.
  20. *
  21. * In addition to regular privacy groups (Public, Friends), spaces are supported.
  22. * When some space (usually some another profile) is specified as privacy,
  23. * then another profile visibility is used to check the privacy.
  24. *
  25. * Related classes:
  26. * BxDolPrivacyQuery - database queries.
  27. *
  28. * Example of usage:
  29. * 1. Register your privacy actions in `sys_privacy_actions` database table.
  30. * 2. Add one privacy field(with INT type) in the table with your items for each action.
  31. * For example, for action 'comment', the field name should be 'allow_comment_to'.
  32. * 3. Add group choosers for necessary actions in the form, which is used to add new items.
  33. * @code
  34. * $oPrivacy = new BxDolPrivacy();
  35. * $oPrivacy->getGroupChooser($iItemOwnerId, $sModuleUri, $sModuleAction);
  36. * @endcode
  37. *
  38. * 4. Check privacy when any user tries to view an item.
  39. * @code
  40. * $oPrivacy = new BxDolPrivacy($sTable, $sFieldId, $sFieldOwnerId);
  41. * if($oPrivacy->check($sAction, $iObjectId, $iViewerId)) {
  42. * //show necessary content
  43. * }
  44. * @endcode
  45. *
  46. * @see an example of integration in BoonEx modules, for example: Posts
  47. *
  48. *
  49. * Memberships/ACL:
  50. * Doesn't depend on user's membership.
  51. *
  52. *
  53. * Alerts:
  54. * no alerts available
  55. *
  56. */
  57. class BxDolPrivacy extends BxDolFactory implements iBxDolFactoryObject
  58. {
  59. protected $_oDb;
  60. protected $_sObject;
  61. protected $_aObject;
  62. protected $_aGroupsExclude;
  63. /**
  64. * Constructor
  65. * @param $aObject array of grid options
  66. */
  67. protected function __construct($aObject)
  68. {
  69. parent::__construct();
  70. $this->_aObject = $aObject;
  71. $this->_sObject = $aObject['object'];
  72. $this->_oDb = new BxDolPrivacyQuery();
  73. $this->_oDb->init($this->_aObject);
  74. $this->_aGroupsExclude = array();
  75. }
  76. /**
  77. * Get privacy object instance by object name
  78. * @param $sObject object name
  79. * @return object instance or false on error
  80. */
  81. public static function getObjectInstance($sObject)
  82. {
  83. if(isset($GLOBALS['bxDolClasses']['BxDolPrivacy!' . $sObject]))
  84. return $GLOBALS['bxDolClasses']['BxDolPrivacy!' . $sObject];
  85. $aObject = BxDolPrivacyQuery::getPrivacyObject($sObject);
  86. if(!$aObject || !is_array($aObject))
  87. return false;
  88. $sClass = 'BxTemplPrivacy';
  89. if(!empty($aObject['override_class_name'])) {
  90. $sClass = $aObject['override_class_name'];
  91. if(!empty($aObject['override_class_file']))
  92. require_once(BX_DIRECTORY_PATH_ROOT . $aObject['override_class_file']);
  93. }
  94. $o = new $sClass($aObject);
  95. return ($GLOBALS['bxDolClasses']['BxDolPrivacy!' . $sObject] = $o);
  96. }
  97. /**
  98. * Get Select element with available groups.
  99. *
  100. * @param string $sObject privacy object name.
  101. * @param integer $iOwnerId object's owner ID.
  102. * @param array $aParams an array of custom selector's params (dynamic_groups - an array of arrays('key' => group_id, 'value' => group_title), title - the title to be used for generated field).
  103. * @return an array with Select element description.
  104. */
  105. public static function getGroupChooser($sObject, $iOwnerId = 0, $aParams = array())
  106. {
  107. $oPrivacy = BxDolPrivacy::getObjectInstance($sObject);
  108. if(empty($oPrivacy))
  109. return array();
  110. $sModule = $oPrivacy->_aObject['module'];
  111. $sAction = $oPrivacy->_aObject['action'];
  112. if($iOwnerId == 0)
  113. $iOwnerId = bx_get_logged_profile_id();
  114. $sValue = $oPrivacy->_oDb->getDefaultGroupByUser($sModule, $sAction, $iOwnerId);
  115. if(empty($sValue))
  116. $sValue = $oPrivacy->_oDb->getDefaultGroup($sModule, $sAction);
  117. $aValues = $oPrivacy->getGroups();
  118. $aValues = $oPrivacy->addDynamicGroups($aValues, $iOwnerId, $aParams);
  119. $aValues = $oPrivacy->addAclGroups($aValues, $iOwnerId, $aParams);
  120. $aValues = $oPrivacy->addSpaces($aValues, $iOwnerId, $aParams);
  121. $sName = $oPrivacy->convertActionToField($sAction);
  122. $sTitle = isset($aParams['title']) && !empty($aParams['title']) ? $aParams['title'] : '';
  123. if(empty($sTitle)) {
  124. $sTitle = $oPrivacy->_oDb->getTitle($sModule, $sAction);
  125. $sTitle = _t(!empty($sTitle) ? $sTitle : '_' . $sName);
  126. }
  127. return array(
  128. 'type' => 'select',
  129. 'name' => $sName,
  130. 'caption' => $sTitle,
  131. 'value' => $sValue,
  132. 'values' => $aValues,
  133. 'checker' => array(
  134. 'func' => 'avail',
  135. 'error' => _t('_sys_ps_ferr_incorrect_select')
  136. ),
  137. 'db' => array(
  138. 'pass' => 'Int'
  139. )
  140. );
  141. }
  142. public function addDynamicGroups($aValues, $iOwnerId, $aParams)
  143. {
  144. if (isset($aParams['dynamic_groups']) && is_array($aParams['dynamic_groups']))
  145. $aValues = array_merge($aValues, $aParams['dynamic_groups']);
  146. return $aValues;
  147. }
  148. public function addAclGroups($aValues, $iOwnerId, $aParams)
  149. {
  150. $aLevels = BxDolAcl::getInstance()->getMemberships(false, true, true, true);
  151. if(empty($aLevels) || !is_array($aLevels))
  152. return $aValues;
  153. $aValues[] = array('type' => 'group_header', 'value' => mb_strtoupper(_t('_sys_privacy_groups_acl')));
  154. foreach($aLevels as $iId => $sTitle)
  155. $aValues[] = array('key' => 'ml' . $iId, 'value' => $sTitle);
  156. $aValues[] = array('type' => 'group_end');
  157. return $aValues;
  158. }
  159. public function addSpaces($aValues, $iOwnerId, $aParams)
  160. {
  161. if (!$this->_aObject['spaces'])
  162. return $aValues;
  163. if (!($oProfile = BxDolProfile::getInstance($iOwnerId)))
  164. return $aValues;
  165. if (!($aModules = BxDolModuleQuery::getInstance()->getModules()))
  166. return $aValues;
  167. $bProfileProcessed = true; // don't allow to post to profiles for now
  168. foreach ($aModules as $aModule) {
  169. if (!$aModule['enabled'])
  170. continue;
  171. if ('all' != $this->_aObject['spaces'] && false === stripos($this->_aObject['spaces'], $aModule['name']))
  172. continue;
  173. if (!BxDolRequest::serviceExists($aModule['name'], 'act_as_profile'))
  174. continue;
  175. $bActAsProfile = BxDolService::call($aModule['name'], 'act_as_profile');
  176. if ($bActAsProfile && $bProfileProcessed)
  177. continue;
  178. if ($bActAsProfile)
  179. $bProfileProcessed = true;
  180. $a = BxDolService::call($aModule['name'], 'get_participating_profiles', array($oProfile->id()));
  181. $aSpaces = array();
  182. foreach ($a as $iProfileId) {
  183. if (!($o = BxDolProfile::getInstance($iProfileId)))
  184. continue;
  185. $aSpaces[-$iProfileId] = array('key' => -$iProfileId, 'value' => $o->getDisplayName());
  186. }
  187. if ($aSpaces) {
  188. $aItemStart = array(array('type' => 'group_header', 'value' => mb_strtoupper(BxDolService::call($aModule['name'], 'get_space_title'))));
  189. $aItemEnd = array(array('type' => 'group_end'));
  190. $aValues = array_merge($aValues, $aItemStart, array_values($aSpaces), $aItemEnd);
  191. }
  192. }
  193. return $aValues;
  194. }
  195. /**
  196. * Get database field name for action.
  197. *
  198. * @param string $sObject privacy object name.
  199. * @param string $sAction action name.
  200. * @return string with field name.
  201. */
  202. public static function getFieldName($sObject, $sAction = '')
  203. {
  204. $oPrivacy = BxDolPrivacy::getObjectInstance($sObject);
  205. if(empty($oPrivacy))
  206. return '';
  207. if(empty($sAction))
  208. $sAction = $oPrivacy->_aObject['action'];
  209. return $oPrivacy->convertActionToField($sAction);
  210. }
  211. /**
  212. * Get necessary condition array to use privacy in search classes
  213. * @param $mixedGroupId group ID or array of group IDs
  214. * @return array of conditions, for now with 'restriction' part only is returned
  215. */
  216. public function getContentByGroupAsCondition($mixedGroupId)
  217. {
  218. $aResult = array(
  219. 'restriction' => array (
  220. 'privacy_' . $this->_sObject => array(
  221. 'value' => $mixedGroupId,
  222. 'field' => $this->convertActionToField($this->_aObject['action']),
  223. 'operator' => is_array($mixedGroupId) ? 'in' : '=',
  224. 'table' => $this->_aObject['table'],
  225. ),
  226. ),
  227. );
  228. bx_alert('system', 'privacy_condition', 0, false, array(
  229. 'group_id' => $mixedGroupId,
  230. 'field' => $this->convertActionToField($this->_aObject['action']),
  231. 'object' => $this->_aObject,
  232. 'privacy_object' => $this,
  233. 'result' => &$aResult
  234. )
  235. );
  236. return $aResult;
  237. }
  238. /**
  239. * Get necessary condition array to use privacy in search classes
  240. * @param $iProfileIdOwner owner profile ID
  241. * @return array of conditions, for now with 'restriction' part only is returned
  242. */
  243. public function getContentPublicAsCondition($iProfileIdOwner = 0, $aCustomGroups = array())
  244. {
  245. $mixedPrivacyGroups = $this->getPrivacyGroupsForContentPublic($iProfileIdOwner, $aCustomGroups);
  246. if($mixedPrivacyGroups === true)
  247. return array();
  248. return $this->getContentByGroupAsCondition($mixedPrivacyGroups);
  249. }
  250. /**
  251. * Get necessary parts of SQL query to use privacy in other queries
  252. * @param $mixedGroupId group ID or array of group IDs
  253. * @return array of SQL string parts, for now 'where' part only is returned
  254. */
  255. public function getContentByGroupAsSQLPart($mixedGroupId)
  256. {
  257. $sField = $this->convertActionToField($this->_aObject['action']);
  258. return $this->_oDb->getContentByGroupAsSQLPart($sField, $mixedGroupId);
  259. }
  260. /**
  261. * Get necessary parts of SQL query to use privacy in other queries
  262. * @param $iProfileIdOwner owner profile ID
  263. * @return array of SQL string parts, for now 'where' part only is returned
  264. */
  265. public function getContentPublicAsSQLPart($iProfileIdOwner = 0, $aCustomGroups = array())
  266. {
  267. $mixedPrivacyGroups = $this->getPrivacyGroupsForContentPublic($iProfileIdOwner, $aCustomGroups);
  268. if($mixedPrivacyGroups === true)
  269. return array();
  270. return $this->getContentByGroupAsSQLPart($mixedPrivacyGroups);
  271. }
  272. /**
  273. * Check whether the viewer can make requested action.
  274. *
  275. * @param integer $iObjectId object ID the action to be performed with.
  276. * @param integer $iViewerId viewer ID.
  277. * @return boolean result of operation.
  278. */
  279. function check($iObjectId, $iViewerId = 0)
  280. {
  281. if(empty($iViewerId))
  282. $iViewerId = (int)bx_get_logged_profile_id();
  283. $aObject = $this->getObjectInfo($this->convertActionToField($this->_aObject['action']), $iObjectId);
  284. if(empty($aObject) || !is_array($aObject))
  285. return false;
  286. if($aObject['group_id'] == BX_DOL_PG_HIDDEN)
  287. return false;
  288. if(isAdmin() || $iViewerId == $aObject['owner_id'])
  289. return true;
  290. if(strncmp($aObject['group_id'], 'ml', 2) === 0) {
  291. $iLevel = (int)substr($aObject['group_id'], 2);
  292. return (bool)BxDolAcl::getInstance()->isMemberLevelInSet(array($iLevel), $iViewerId);
  293. }
  294. if($aObject['group_id'] < 0)
  295. return $this->checkSpace($aObject, $iViewerId);
  296. $aGroup = $this->_oDb->getGroupsBy(array('type' => 'id', 'id' => $aObject['group_id']));
  297. if(!empty($aGroup) && is_array($aGroup) && (int)$aGroup['active'] == 1 && !empty($aGroup['check'])) {
  298. $sCheckMethod = $this->getCheckMethod($aGroup['check']);
  299. if(method_exists($this, $sCheckMethod) && $this->$sCheckMethod($aObject['owner_id'], $iViewerId))
  300. return true;
  301. }
  302. return $this->isDynamicGroupMember($aObject['group_id'], $aObject['owner_id'], $iViewerId, $iObjectId);
  303. }
  304. public function checkSpace($aObject, $iViewerId)
  305. {
  306. $oProfile = BxDolProfile::getInstance(-$aObject['group_id']);
  307. if (!$oProfile)
  308. return false;
  309. return CHECK_ACTION_RESULT_ALLOWED === BxDolService::call($oProfile->getModule(), 'check_space_privacy', array($oProfile->getContentId()));
  310. }
  311. public function checkMeOnly($iOwnerId, $iViewerId)
  312. {
  313. return false;
  314. }
  315. public function checkPublic($iOwnerId, $iViewerId)
  316. {
  317. return true;
  318. }
  319. public function checkMembers($iOwnerId, $iViewerId)
  320. {
  321. return isMember();
  322. }
  323. public function checkFriends($iOwnerId, $iViewerId)
  324. {
  325. return BxDolConnection::getObjectInstance('sys_profiles_friends')->isConnected($iOwnerId, $iViewerId, true);
  326. }
  327. public function setTableFieldAuthor($sValue)
  328. {
  329. $this->_aObject['table_field_author'] = $sValue;
  330. $this->_oDb->init($this->_aObject);
  331. }
  332. protected function getObjectInfo($sAction, $iObjectId)
  333. {
  334. return $this->_oDb->getObjectInfo($sAction, $iObjectId);
  335. }
  336. protected function getPrivacyGroupsForContentPublic($iProfileIdOwner = 0, $aCustomGroups = array())
  337. {
  338. $aGroups = array(BX_DOL_PG_ALL);
  339. if(isLogged()) {
  340. $iProfileIdLogged = bx_get_logged_profile_id();
  341. if($iProfileIdLogged == $iProfileIdOwner)
  342. return true;
  343. $aGroups[] = BX_DOL_PG_MEMBERS;
  344. if($iProfileIdOwner && $this->checkFriends($iProfileIdOwner, $iProfileIdLogged))
  345. $aGroups[] = BX_DOL_PG_FRIENDS;
  346. }
  347. return array_merge($aGroups, $aCustomGroups);
  348. }
  349. protected function getCheckMethod($s)
  350. {
  351. if(substr($s, 0, 1) != '@')
  352. return false;
  353. return bx_gen_method_name(str_replace('@', 'check_', $s));
  354. }
  355. protected function convertActionToField($sAction)
  356. {
  357. return 'allow_' . strtolower(str_replace(' ', '-', $sAction)) . '_to';
  358. }
  359. /**
  360. * Check whethere viewer is a member of dynamic group.
  361. *
  362. * @param mixed $mixedGroupId dynamic group ID.
  363. * @param integer $iObjectOwnerId object owner ID.
  364. * @param integer $iViewerId viewer ID.
  365. * @return boolean result of operation.
  366. */
  367. protected function isDynamicGroupMember($mixedGroupId, $iObjectOwnerId, $iViewerId, $iObjectId)
  368. {
  369. return false;
  370. }
  371. /**
  372. * get privacy groups for getGroupChooser
  373. */
  374. protected function getGroups()
  375. {
  376. $aValues = array();
  377. $aGroups = $this->_oDb->getGroupsBy(array('type' => 'active'));
  378. foreach($aGroups as $aGroup) {
  379. if((int)$aGroup['active'] == 0 || in_array($aGroup['id'], $this->_aGroupsExclude))
  380. continue;
  381. $aValues[] = array('key' => $aGroup['id'], 'value' => _t($aGroup['title']));
  382. }
  383. return $aValues;
  384. }
  385. }
  386. /** @} */