flag_flag.inc 49 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610
  1. <?php
  2. /**
  3. * @file
  4. * Contains the flag_flag class.
  5. * Flag type classes use an object oriented style inspired by that
  6. * of Views 2.
  7. */
  8. /**
  9. * This abstract class represents a flag, or, in Views 2 terminology,
  10. * "a handler".
  11. *
  12. * This is the base class for all flag implementations. Notable derived
  13. * classes are flag_node and flag_comment.
  14. *
  15. * There are several ways to obtain a flag handler, operating at different
  16. * levels.
  17. *
  18. * To load an existing flag that's defined in database or code, use one of:
  19. * - flag_get_flag(), the main flag API function.
  20. * - flag_load(), the loader for hook_menu().
  21. * - flag_get_flags(), the main API function for loading all flags. This calls
  22. * flag_get_default_flags() to get flags in code.
  23. *
  24. * The above all use factory methods to instantiate the object for the flag and
  25. * load in its settings from configuration. The factory methods are:
  26. * - flag_flag::factory_by_row(), creates a flag handler from a database row.
  27. * This is used by all the API functions above.
  28. * - flag_flag::factory_by_array(), creates a flag handler from a configuration
  29. * array. This is used by flag_get_default_flags() and the flag import form.
  30. * - flag_flag::factory_by_entity_type(), creates an empty flag handler for the
  31. * given entity type. This is used when a new or dummy flag handler is
  32. * required and there is no configuration yet.
  33. *
  34. * The factory methods in turn all call the low-level function
  35. * flag_create_handler(), which obtains the correct handler for the flag, or if
  36. * that can't be found, the special handler flag_broken. Finally, this calls
  37. * $flag->construct() on the new handler object.
  38. */
  39. class flag_flag {
  40. /**
  41. * The database ID.
  42. *
  43. * NULL for flags that haven't been saved to the database yet.
  44. *
  45. * @var integer
  46. */
  47. var $fid = NULL;
  48. /**
  49. * The entity type this flag works with.
  50. *
  51. * @var string
  52. */
  53. var $entity_type = NULL;
  54. /**
  55. * The flag's "machine readable" name.
  56. *
  57. * @var string
  58. */
  59. var $name = '';
  60. /**
  61. * The human-readable title for this flag.
  62. *
  63. * @var string
  64. */
  65. var $title = '';
  66. /**
  67. * Whether this flag state should act as a single toggle to all users.
  68. *
  69. * @var bool
  70. */
  71. var $global = FALSE;
  72. /**
  73. * The sub-types, AKA bundles, this flag applies to.
  74. *
  75. * This may be an empty array to indicate all types apply.
  76. *
  77. * @var array
  78. */
  79. var $types = array();
  80. /**
  81. * The roles array. This can be populated by fetch_roles() when needed.
  82. */
  83. var $roles = array(
  84. 'flag' => array(),
  85. 'unflag' => array(),
  86. );
  87. /**
  88. * An associative array containing textual errors that may be created during
  89. * validation.
  90. *
  91. * The array keys should reflect the type of error being set. At this time,
  92. * the only "special" behavior related to the array keys is that
  93. * drupal_access_denied() is called when the key is 'access-denied' and
  94. * javascript is disabled.
  95. *
  96. * @var array
  97. */
  98. public $errors = array();
  99. /**
  100. * Creates a flag from a database row. Returns it.
  101. *
  102. * This is static method.
  103. *
  104. * The reason this isn't a non-static instance method --like Views's init()--
  105. * is because the class to instantiate changes according to the 'entity_type'
  106. * database column. This design pattern is known as the "Single Table
  107. * Inheritance".
  108. */
  109. static function factory_by_row($row) {
  110. $flag = flag_create_handler($row->entity_type);
  111. // Lump all data unto the object...
  112. foreach ($row as $field => $value) {
  113. $flag->$field = $value;
  114. }
  115. // ...but skip the following two.
  116. unset($flag->options, $flag->type);
  117. // Populate the options with the defaults.
  118. $options = (array) unserialize($row->options);
  119. $options += $flag->options();
  120. // Make the unserialized options accessible as normal properties.
  121. foreach ($options as $option => $value) {
  122. $flag->$option = $value;
  123. }
  124. if (!empty($row->type)) {
  125. // The loop loading from the database should further populate this
  126. // property.
  127. $flag->types[] = $row->type;
  128. }
  129. return $flag;
  130. }
  131. /**
  132. * Create a complete flag (except an FID) from an array definition.
  133. */
  134. static function factory_by_array($config) {
  135. // Allow for flags with a missing entity type.
  136. $config += array(
  137. 'entity_type' => FALSE,
  138. );
  139. $flag = flag_create_handler($config['entity_type']);
  140. foreach ($config as $option => $value) {
  141. $flag->$option = $value;
  142. }
  143. if (isset($config['locked']) && is_array($config['locked'])) {
  144. $flag->locked = drupal_map_assoc($config['locked']);
  145. }
  146. return $flag;
  147. }
  148. /**
  149. * Another factory method. Returns a new, "empty" flag; e.g., one suitable for
  150. * the "Add new flag" page.
  151. */
  152. static function factory_by_entity_type($entity_type) {
  153. return flag_create_handler($entity_type);
  154. }
  155. /**
  156. * Declares the options this flag supports, and their default values.
  157. *
  158. * Derived classes should want to override this.
  159. */
  160. function options() {
  161. $options = array(
  162. // The text for the "flag this" link for this flag.
  163. 'flag_short' => '',
  164. // The description of the "flag this" link.
  165. 'flag_long' => '',
  166. // Message displayed after flagging an entity.
  167. 'flag_message' => '',
  168. // Likewise but for unflagged.
  169. 'unflag_short' => '',
  170. 'unflag_long' => '',
  171. 'unflag_message' => '',
  172. 'unflag_denied_text' => '',
  173. // The link type used by the flag, as defined in
  174. // hook_flag_link_type_info().
  175. 'link_type' => 'toggle',
  176. 'weight' => 0,
  177. );
  178. // Merge in options from the current link type.
  179. $link_type = $this->get_link_type();
  180. $options = array_merge($options, $link_type['options']);
  181. // Allow other modules to change the flag options.
  182. drupal_alter('flag_options', $options, $this);
  183. return $options;
  184. }
  185. /**
  186. * Provides a form for setting options.
  187. *
  188. * Derived classes should want to override this.
  189. */
  190. function options_form(&$form) {
  191. }
  192. /**
  193. * Default constructor. Loads the default options.
  194. */
  195. function construct() {
  196. $options = $this->options();
  197. foreach ($options as $option => $value) {
  198. $this->$option = $value;
  199. }
  200. }
  201. /**
  202. * Load this flag's role data from permissions.
  203. *
  204. * Loads an array of roles into the flag, where each key is an action ('flag'
  205. * and 'unflag'), and each value is a flat array of role ids which may perform
  206. * that action.
  207. *
  208. * This should only be used when a complete overview of a flag's permissions
  209. * is needed. Use $flag->access or $flag->user_access() instead.
  210. */
  211. function fetch_roles() {
  212. $actions = array('flag', 'unflag');
  213. foreach ($actions as $action) {
  214. // Build the permission string.
  215. $permission = "$action $this->name";
  216. // We want a flat array of rids rather than $rid => $role_name.
  217. $this->roles[$action] = array_keys(user_roles(FALSE, $permission));
  218. }
  219. }
  220. /**
  221. * Update the flag with settings entered in a form.
  222. */
  223. function form_input($form_values) {
  224. // Load the form fields indiscriminately unto the flag (we don't care about
  225. // stray FormAPI fields because we aren't touching unknown properties
  226. // anyway).
  227. foreach ($form_values as $field => $value) {
  228. $this->$field = $value;
  229. }
  230. $this->types = array_values(array_filter($this->types));
  231. // Clear internal titles cache:
  232. $this->get_title(NULL, TRUE);
  233. }
  234. /**
  235. * Validates this flag's options.
  236. *
  237. * @return
  238. * A list of errors encountered while validating this flag's options.
  239. */
  240. function validate() {
  241. // TODO: It might be nice if this used automatic method discovery rather
  242. // than hard-coding the list of validate functions.
  243. return array_merge_recursive(
  244. $this->validate_name(),
  245. $this->validate_access()
  246. );
  247. }
  248. /**
  249. * Validates that the current flag's name is valid.
  250. *
  251. * @return
  252. * A list of errors encountered while validating this flag's name.
  253. */
  254. function validate_name() {
  255. $errors = array();
  256. // Ensure a safe machine name.
  257. if (!preg_match('/^[a-z_][a-z0-9_]*$/', $this->name)) {
  258. $errors['name'][] = array(
  259. 'error' => 'flag_name_characters',
  260. 'message' => t('The flag name may only contain lowercase letters, underscores, and numbers.'),
  261. );
  262. }
  263. // Ensure the machine name is unique.
  264. $flag = flag_get_flag($this->name);
  265. if (!empty($flag) && (!isset($this->fid) || $flag->fid != $this->fid)) {
  266. $errors['name'][] = array(
  267. 'error' => 'flag_name_unique',
  268. 'message' => t('Flag names must be unique. This flag name is already in use.'),
  269. );
  270. }
  271. return $errors;
  272. }
  273. /**
  274. * Validates that the current flag's access settings are valid.
  275. */
  276. function validate_access() {
  277. $errors = array();
  278. // Require an unflag access denied message a role is not allowed to unflag.
  279. if (empty($this->unflag_denied_text)) {
  280. foreach ($this->roles['flag'] as $key => $rid) {
  281. if ($rid && empty($this->roles['unflag'][$key])) {
  282. $errors['unflag_denied_text'][] = array(
  283. 'error' => 'flag_denied_text_required',
  284. 'message' => t('The "Unflag not allowed text" is required if any user roles are not allowed to unflag.'),
  285. );
  286. break;
  287. }
  288. }
  289. }
  290. // Do not allow unflag access without flag access.
  291. foreach ($this->roles['unflag'] as $key => $rid) {
  292. if ($rid && empty($this->roles['flag'][$key])) {
  293. $errors['roles'][] = array(
  294. 'error' => 'flag_roles_unflag',
  295. 'message' => t('Any user role that has the ability to unflag must also have the ability to flag.'),
  296. );
  297. break;
  298. }
  299. }
  300. return $errors;
  301. }
  302. /**
  303. * Fetches, possibly from some cache, an entity this flag works with.
  304. */
  305. function fetch_entity($entity_id, $object_to_remember = NULL) {
  306. static $cache = array();
  307. if (isset($object_to_remember)) {
  308. $cache[$entity_id] = $object_to_remember;
  309. }
  310. if (!array_key_exists($entity_id, $cache)) {
  311. $entity = $this->_load_entity($entity_id);
  312. $cache[$entity_id] = $entity ? $entity : NULL;
  313. }
  314. return $cache[$entity_id];
  315. }
  316. /**
  317. * Loads an entity this flag works with.
  318. * Derived classes must implement this.
  319. *
  320. * @abstract
  321. * @private
  322. * @static
  323. */
  324. function _load_entity($entity_id) {
  325. return NULL;
  326. }
  327. /**
  328. * Store an object in the flag handler's cache.
  329. *
  330. * This is needed because otherwise fetch_object() loads the object from the
  331. * database (by calling _load_entity()), whereas sometimes we want to fetch
  332. * an object that hasn't yet been saved to the database. Subsequent calls to
  333. * fetch_entity() return the remembered object.
  334. *
  335. * @param $entity_id
  336. * The ID of the object to cache.
  337. * @param $object
  338. * The object to cache.
  339. */
  340. function remember_entity($entity_id, $object) {
  341. $this->fetch_entity($entity_id, $object);
  342. }
  343. /**
  344. * @defgroup access Access control
  345. * @{
  346. */
  347. /**
  348. * Returns TRUE if the flag applies to the given entity.
  349. *
  350. * Derived classes must implement this.
  351. *
  352. * @abstract
  353. */
  354. function applies_to_entity($entity) {
  355. return FALSE;
  356. }
  357. /**
  358. * Returns TRUE if the flag applies to the entity with the given ID.
  359. *
  360. * This is a convenience method that simply loads the object and calls
  361. * applies_to_entity(). If you already have the object, don't call
  362. * this function: call applies_to_entity() directly.
  363. */
  364. function applies_to_entity_id($entity_id) {
  365. return $this->applies_to_entity($this->fetch_entity($entity_id));
  366. }
  367. /**
  368. * Provides permissions for this flag.
  369. *
  370. * @return
  371. * An array of permissions for hook_permission().
  372. */
  373. function get_permissions() {
  374. return array(
  375. "flag $this->name" => array(
  376. 'title' => t('Flag %flag_title', array(
  377. '%flag_title' => $this->title,
  378. )),
  379. ),
  380. "unflag $this->name" => array(
  381. 'title' => t('Unflag %flag_title', array(
  382. '%flag_title' => $this->title,
  383. )),
  384. ),
  385. );
  386. }
  387. /**
  388. * Determines whether the user has the permission to use this flag.
  389. *
  390. * @param $action
  391. * (optional) The action to test, either "flag" or "unflag". If none given,
  392. * "flag" will be tested, which is the minimum permission to use a flag.
  393. * @param $account
  394. * (optional) The user object. If none given, the current user will be used.
  395. *
  396. * @return
  397. * Boolean TRUE if the user is allowed to flag/unflag. FALSE otherwise.
  398. *
  399. * @see flag_permission()
  400. */
  401. function user_access($action = 'flag', $account = NULL) {
  402. if (!isset($account)) {
  403. $account = $GLOBALS['user'];
  404. }
  405. // Anonymous user can't use this system unless Session API is installed.
  406. if ($account->uid == 0 && !module_exists('session_api')) {
  407. return FALSE;
  408. }
  409. $permission_string = "$action $this->name";
  410. return user_access($permission_string, $account);
  411. }
  412. /**
  413. * Determines whether the user may flag, or unflag, the given entity.
  414. *
  415. * This method typically should not be overridden by child classes. Instead
  416. * they should implement type_access(), which is called by this method.
  417. *
  418. * @param $entity_id
  419. * The entity ID to flag/unflag.
  420. * @param $action
  421. * The action to test. Either 'flag' or 'unflag'. Leave NULL to determine
  422. * by flag status.
  423. * @param $account
  424. * The user on whose behalf to test the flagging action. Leave NULL for the
  425. * current user.
  426. *
  427. * @return
  428. * Boolean TRUE if the user is allowed to flag/unflag the given entity.
  429. * FALSE otherwise.
  430. */
  431. function access($entity_id, $action = NULL, $account = NULL) {
  432. if (!isset($account)) {
  433. $account = $GLOBALS['user'];
  434. }
  435. if (isset($entity_id) && !$this->applies_to_entity_id($entity_id)) {
  436. // Flag does not apply to this entity.
  437. return FALSE;
  438. }
  439. if (!isset($action)) {
  440. $uid = $account->uid;
  441. $sid = flag_get_sid($uid);
  442. $action = $this->is_flagged($entity_id, $uid, $sid) ? 'unflag' : 'flag';
  443. }
  444. // Base initial access on the user's basic permission to use this flag.
  445. $access = $this->user_access($action, $account);
  446. // Check for additional access rules provided by sub-classes.
  447. $child_access = $this->type_access($entity_id, $action, $account);
  448. if (isset($child_access)) {
  449. $access = $child_access;
  450. }
  451. // Allow modules to disallow (or allow) access to flagging.
  452. // We grant access to the flag if both of the following conditions are met:
  453. // - No modules say to deny access.
  454. // - At least one module says to grant access.
  455. // If no module specified either allow or deny, we fall back to the
  456. // default access check above.
  457. $module_access = module_invoke_all('flag_access', $this, $entity_id, $action, $account);
  458. if (in_array(FALSE, $module_access, TRUE)) {
  459. $access = FALSE;
  460. }
  461. elseif (in_array(TRUE, $module_access, TRUE)) {
  462. // WARNING: This allows modules to bypass the default access check!
  463. $access = TRUE;
  464. }
  465. return $access;
  466. }
  467. /**
  468. * Determine access to multiple objects.
  469. *
  470. * Similar to user_access() but works on multiple IDs at once. Called in the
  471. * pre_render() stage of the 'Flag links' field within Views to find out where
  472. * that link applies. The reason we do a separate DB query, and not lump this
  473. * test in the Views query, is to make 'many to one' tests possible without
  474. * interfering with the rows, and also to reduce the complexity of the code.
  475. *
  476. * This method typically should not be overridden by child classes. Instead
  477. * they should implement type_access_multiple(), which is called by this
  478. * method.
  479. *
  480. * @param $entity_ids
  481. * The array of entity IDs to check. The keys are the entity IDs, the
  482. * values are the actions to test: either 'flag' or 'unflag'.
  483. * @param $account
  484. * (optional) The account for which the actions will be compared against.
  485. * If left empty, the current user will be used.
  486. *
  487. * @return
  488. * An array whose keys are the object IDs and values are booleans indicating
  489. * access.
  490. *
  491. * @see hook_flag_access_multiple()
  492. */
  493. function access_multiple($entity_ids, $account = NULL) {
  494. $account = isset($account) ? $account : $GLOBALS['user'];
  495. $access = array();
  496. // First check basic user access for this action.
  497. foreach ($entity_ids as $entity_id => $action) {
  498. $access[$entity_id] = $this->user_access($entity_ids[$entity_id], $account);
  499. }
  500. // Check for additional access rules provided by sub-classes.
  501. $child_access = $this->type_access_multiple($entity_ids, $account);
  502. if (isset($child_access)) {
  503. foreach ($child_access as $entity_id => $entity_access) {
  504. if (isset($entity_access)) {
  505. $access[$entity_id] = $entity_access;
  506. }
  507. }
  508. }
  509. // Merge in module-defined access.
  510. foreach (module_implements('flag_access_multiple') as $module) {
  511. $module_access = module_invoke($module, 'flag_access_multiple', $this, $entity_ids, $account);
  512. foreach ($module_access as $entity_id => $entity_access) {
  513. if (isset($entity_access)) {
  514. $access[$entity_id] = $entity_access;
  515. }
  516. }
  517. }
  518. return $access;
  519. }
  520. /**
  521. * Implements access() implemented by each child class.
  522. *
  523. * @abstract
  524. *
  525. * @return
  526. * FALSE if access should be denied, or NULL if there is no restriction to
  527. * be made. This should NOT return TRUE.
  528. */
  529. function type_access($entity_id, $action, $account) {
  530. return NULL;
  531. }
  532. /**
  533. * Implements access_multiple() implemented by each child class.
  534. *
  535. * @abstract
  536. *
  537. * @return
  538. * An array keyed by entity ids, whose values represent the access to the
  539. * corresponding entity. The access value may be FALSE if access should be
  540. * denied, or NULL (or not set) if there is no restriction to be made. It
  541. * should NOT be TRUE.
  542. */
  543. function type_access_multiple($entity_ids, $account) {
  544. return array();
  545. }
  546. /**
  547. * @} End of "defgroup access".
  548. */
  549. /**
  550. * Given an entity, returns its ID.
  551. * Derived classes must implement this.
  552. *
  553. * @abstract
  554. */
  555. function get_entity_id($entity) {
  556. return NULL;
  557. }
  558. /**
  559. * Utility function: Checks whether a flag applies to a certain type, and
  560. * possibly subtype, of entity.
  561. *
  562. * @param $entity_type
  563. * The type of entity being checked, such as "node".
  564. * @param $content_subtype
  565. * The subtype being checked. For entities this will be the bundle name (the
  566. * node type in the case of nodes).
  567. *
  568. * @return
  569. * TRUE if the flag is enabled for this type and subtype.
  570. */
  571. function access_entity_enabled($entity_type, $content_subtype = NULL) {
  572. $entity_type_matches = ($this->entity_type == $entity_type);
  573. $sub_type_matches = FALSE;
  574. if (!isset($content_subtype) || !count($this->types)) {
  575. // Subtype automatically matches if we're not asked about it,
  576. // or if the flag applies to all subtypes.
  577. $sub_type_matches = TRUE;
  578. }
  579. else {
  580. $sub_type_matches = in_array($content_subtype, $this->types);
  581. }
  582. return $entity_type_matches && $sub_type_matches;
  583. }
  584. /**
  585. * Determine whether the flag should show a flag link in entity links.
  586. *
  587. * Derived classes are likely to implement this.
  588. *
  589. * @param $view_mode
  590. * The view mode of the entity being displayed.
  591. *
  592. * @return
  593. * A boolean indicating whether the flag link is to be shown in entity
  594. * links.
  595. */
  596. function shows_in_entity_links($view_mode) {
  597. return FALSE;
  598. }
  599. /**
  600. * Returns TRUE if this flag requires anonymous user cookies.
  601. */
  602. function uses_anonymous_cookies() {
  603. global $user;
  604. return $user->uid == 0 && variable_get('cache', 0);
  605. }
  606. /**
  607. * Flags, or unflags, an item.
  608. *
  609. * @param $action
  610. * Either 'flag' or 'unflag'.
  611. * @param $entity_id
  612. * The ID of the item to flag or unflag.
  613. * @param $account
  614. * The user on whose behalf to flag. Leave empty for the current user.
  615. * @param $skip_permission_check
  616. * Flag the item even if the $account user don't have permission to do so.
  617. * @param $flagging
  618. * (optional) This method works in tandem with Drupal's Field subsystem.
  619. * Pass in a Flagging entity if you want operate on it as well. This may be
  620. * used either of the following cases:
  621. * - to save field data on a new Flagging entity at the same time as
  622. * flagging an entity. In this case, using Entity API's entity_create()
  623. * is recommended, although the Flagging entity may also be created
  624. * directly as a new stdClass object.
  625. * - to update field data an existing flagging. The $action parameter should
  626. * be set to 'flag'. The Flagging entity will need to be loaded first with
  627. * flagging_load().
  628. * As with Drupal core API functions for saving entities, no validation of
  629. * Field API fields is performed here. It is the responsibility of the caller
  630. * to take care of Field API validation, using either
  631. * field_attach_form_validate() or field_attach_validate().
  632. *
  633. * @return
  634. * FALSE if some error occured (e.g., user has no permission, flag isn't
  635. * applicable to the item, etc.), TRUE otherwise.
  636. */
  637. function flag($action, $entity_id, $account = NULL, $skip_permission_check = FALSE, $flagging = NULL) {
  638. // Get the user.
  639. if (!isset($account)) {
  640. $account = $GLOBALS['user'];
  641. }
  642. // Check access and applicability.
  643. if (!$skip_permission_check) {
  644. if (!$this->access($entity_id, $action, $account)) {
  645. $this->errors['access-denied'] = t('You are not allowed to flag, or unflag, this content.');
  646. // User has no permission to flag/unflag this object.
  647. return FALSE;
  648. }
  649. }
  650. else {
  651. // We are skipping permission checks. However, at a minimum we must make
  652. // sure the flag applies to this entity type:
  653. if (!$this->applies_to_entity_id($entity_id)) {
  654. $this->errors['entity-type'] = t('This flag does not apply to this entity type.');
  655. return FALSE;
  656. }
  657. }
  658. if (($this->errors = module_invoke_all('flag_validate', $action, $this, $entity_id, $account, $skip_permission_check, $flagging))) {
  659. return FALSE;
  660. }
  661. // Find out which user id to use.
  662. $uid = $this->global ? 0 : $account->uid;
  663. // Find out which session id to use.
  664. if ($this->global) {
  665. $sid = 0;
  666. }
  667. else {
  668. $sid = flag_get_sid($uid, TRUE);
  669. // Anonymous users must always have a session id.
  670. if ($sid == 0 && $account->uid == 0) {
  671. $this->errors['session'] = t('Internal error: You are anonymous but you have no session ID.');
  672. return FALSE;
  673. }
  674. }
  675. // @todo: Discuss: Core wraps everything in a try { }, should we?
  676. $existing_flagging_id = $this->_is_flagged($entity_id, $uid, $sid);
  677. $flagged = (bool) $existing_flagging_id;
  678. // Ensure we have a Flagging entity and it is correctly formed.
  679. if (isset($flagging)) {
  680. // We were given a Flagging entity.
  681. // Ensure that it has the uid and sid that we were also given.
  682. $flagging->uid = $uid;
  683. $flagging->sid = $sid;
  684. // This is an ugly hack to preserve previous behaviour.
  685. $flagging->given_as_parameter = TRUE;
  686. }
  687. else {
  688. // We were not given a Flagging entity.
  689. if ($flagged) {
  690. // Load the existing Flagging entity.
  691. $flagging = flagging_load($existing_flagging_id);
  692. }
  693. else {
  694. // Construct a new Flagging entity to flag with.
  695. $flagging = $this->new_flagging($entity_id, $uid, $sid);
  696. }
  697. }
  698. // Perform the flagging or unflagging of this flag.
  699. if ($action == 'unflag') {
  700. if ($flagged) {
  701. $this->flagging_delete($flagging, $entity_id, $account);
  702. }
  703. // We do nothing in the case of an attempt to unflag something that isn't
  704. // actually flagged.
  705. }
  706. elseif ($action == 'flag') {
  707. if (!$flagged) {
  708. $this->flagging_insert($flagging, $entity_id, $account);
  709. }
  710. else {
  711. $this->flagging_update($flagging, $entity_id, $account);
  712. }
  713. }
  714. return TRUE;
  715. }
  716. /**
  717. * Create a new Flagging to flag an entity.
  718. *
  719. * @param $flagging
  720. * The flagging entity that is to be saved.
  721. * @param $entity_id
  722. * The entity ID of entity being flagged.
  723. * @param $account
  724. * The account performing the flagging.
  725. */
  726. private function flagging_insert($flagging, $entity_id, $account) {
  727. if ($this->uses_anonymous_cookies()) {
  728. $this->_flag_anonymous($entity_id);
  729. }
  730. // Invoke presave hooks.
  731. field_attach_presave('flagging', $flagging);
  732. // Invoke hook_entity_presave().
  733. module_invoke_all('entity_presave', $flagging, 'flagging');
  734. // Set the timestamp.
  735. $flagging->timestamp = REQUEST_TIME;
  736. // Save the flagging entity.
  737. drupal_write_record('flagging', $flagging);
  738. // Clear various caches; we don't want code running after us to report
  739. // wrong counts or false flaggings.
  740. drupal_static_reset('flag_get_user_flags');
  741. drupal_static_reset('flag_get_entity_flags');
  742. // Despite being named in the same pattern as the count API functions, these
  743. // query the {flagging} table, so are reset here.
  744. drupal_static_reset('flag_get_entity_flag_counts');
  745. drupal_static_reset('flag_get_user_flag_counts');
  746. $this->_increase_count($entity_id);
  747. // We're writing out a flagging entity even when we aren't passed one
  748. // (e.g., when flagging via JavaScript toggle links); in this case
  749. // Field API will assign the fields their default values.
  750. // Invoke insert hooks.
  751. field_attach_insert('flagging', $flagging);
  752. // Invoke hook_entity_insert().
  753. module_invoke_all('entity_insert', $flagging, 'flagging');
  754. module_invoke_all('flag_flag', $this, $entity_id, $account, $flagging);
  755. // Invoke Rules event.
  756. if (module_exists('rules')) {
  757. $this->invoke_rules_event('flag', $flagging, $entity_id, $account);
  758. }
  759. }
  760. /**
  761. * Update a Flagging.
  762. *
  763. * @param $flagging
  764. * The flagging entity that is being updated.
  765. * @param $entity_id
  766. * The entity ID of entity the flagging is on.
  767. * @param $account
  768. * The account performing the action.
  769. */
  770. private function flagging_update($flagging, $entity_id, $account) {
  771. // Invoke presave hooks.
  772. // This is technically still a presave, even though the {flagging} table
  773. // itself is not changed.
  774. field_attach_presave('flagging', $flagging);
  775. // Invoke hook_entity_presave().
  776. module_invoke_all('entity_presave', $flagging, 'flagging');
  777. // This check exists solely to preserve previous behaviour with re-flagging.
  778. // TODO: consider removing it.
  779. if (!empty($flagging->given_as_parameter)) {
  780. field_attach_update('flagging', $flagging);
  781. // Update the cache.
  782. entity_get_controller('flagging')->resetCache();
  783. // Invoke hook_entity_update().
  784. // Since there are no fields on the {flagging} table that can be
  785. // meaningfully changed, we don't perform an update on it. However, this
  786. // technically still counts as updating the flagging entity, since we update
  787. // its fields.
  788. module_invoke_all('entity_update', $flagging, 'flagging');
  789. }
  790. }
  791. /**
  792. * Unflag an entity by deleting a Flagging.
  793. *
  794. * @param $flagging
  795. * The flagging entity that is to be removed.
  796. * @param $entity_id
  797. * The entity ID of entity being unflagged.
  798. * @param $account
  799. * The account performing the unflagging.
  800. */
  801. private function flagging_delete($flagging, $entity_id, $account) {
  802. if ($this->uses_anonymous_cookies()) {
  803. $this->_unflag_anonymous($entity_id);
  804. }
  805. $transaction = db_transaction();
  806. try {
  807. // Note the order: We decrease the count first so hooks have accurate
  808. // data, then invoke hooks, then delete the flagging entity.
  809. $this->_decrease_count($entity_id);
  810. module_invoke_all('flag_unflag', $this, $entity_id, $account, $flagging);
  811. // Invoke Rules event.
  812. if (module_exists('rules')) {
  813. $this->invoke_rules_event('unflag', $flagging, $entity_id, $account);
  814. }
  815. // Invoke hook_entity_delete().
  816. module_invoke_all('entity_delete', $flagging, 'flagging');
  817. // Delete field data.
  818. field_attach_delete('flagging', $flagging);
  819. // Delete the flagging entity.
  820. db_delete('flagging')->condition('flagging_id', $flagging->flagging_id)->execute();
  821. // Remove from the cache.
  822. entity_get_controller('flagging')->resetCache();
  823. // Clear various caches; we don't want code running after us to report
  824. // wrong counts or false flaggings.
  825. drupal_static_reset('flag_get_user_flags');
  826. drupal_static_reset('flag_get_entity_flags');
  827. // Despite being named in the same pattern as the count API functions, these
  828. // query the {flagging} table, so are reset here.
  829. drupal_static_reset('flag_get_entity_flag_counts');
  830. drupal_static_reset('flag_get_user_flag_counts');
  831. }
  832. catch (Exception $e) {
  833. $transaction->rollback();
  834. watchdog_exception('flag', $e);
  835. throw $e;
  836. }
  837. }
  838. /**
  839. * Invoke a Rules event in reaction to a flagging or unflagging.
  840. *
  841. * @param $action
  842. * Either 'flag' or 'unflag'.
  843. * @param $flagging
  844. * The flagging entity that is either newly created or about to be deleted.
  845. * @param $entity_id
  846. * The entity ID of entity being flagged or unflagged.
  847. * @param $account
  848. * The account performing the action.
  849. */
  850. protected function invoke_rules_event($action, $flagging, $entity_id, $account) {
  851. // We only support flags on entities: do nothing in this class.
  852. // See flag_entity::invoke_rules_event().
  853. return;
  854. }
  855. /**
  856. * Construct a new, empty flagging entity object.
  857. *
  858. * @param mixed $entity_id
  859. * The unique identifier of the object being flagged.
  860. * @param int $uid
  861. * (optional) The user id of the user doing the flagging.
  862. * @param mixed $sid
  863. * (optional) The user SID (provided by Session API) who is doing the
  864. * flagging. The SID is 0 for logged in users.
  865. *
  866. * @return stdClass
  867. * The returned object has at least the 'flag_name' property set, which
  868. * enables Field API to figure out the bundle, but it's your responsibility
  869. * to eventually populate 'entity_id' and 'flagging_id'.
  870. */
  871. function new_flagging($entity_id = NULL, $uid = NULL, $sid = NULL) {
  872. return (object) array(
  873. 'flagging_id' => NULL,
  874. 'flag_name' => $this->name,
  875. 'fid' => $this->fid,
  876. 'entity_type' => $this->entity_type,
  877. 'entity_id' => $entity_id,
  878. 'uid' => $uid,
  879. 'sid' => $sid,
  880. // The timestamp is not set until this is saved.
  881. );
  882. }
  883. /**
  884. * Determines if a certain user has flagged this object.
  885. *
  886. * Thanks to using a cache, inquiring several different flags about the same
  887. * item results in only one SQL query.
  888. *
  889. * @param $uid
  890. * (optional) The user ID whose flags we're checking. If none given, the
  891. * current user will be used.
  892. *
  893. * @return
  894. * TRUE if the object is flagged, FALSE otherwise.
  895. */
  896. function is_flagged($entity_id, $uid = NULL, $sid = NULL) {
  897. return (bool) $this->get_flagging_record($entity_id, $uid, $sid);
  898. }
  899. /**
  900. * Returns the flagging record.
  901. *
  902. * This method returns the "flagging record": the {flagging} record that
  903. * exists for each flagged item (for a certain user). If the item isn't
  904. * flagged, returns NULL. This method could be useful, for example, when you
  905. * want to find out the 'flagging_id' or 'timestamp' values.
  906. *
  907. * Thanks to using a cache, inquiring several different flags about the same
  908. * item results in only one SQL query.
  909. *
  910. * Parameters are the same as is_flagged()'s.
  911. */
  912. function get_flagging_record($entity_id, $uid = NULL, $sid = NULL) {
  913. $uid = $this->global ? 0 : (!isset($uid) ? $GLOBALS['user']->uid : $uid);
  914. $sid = $this->global ? 0 : (!isset($sid) ? flag_get_sid($uid) : $sid);
  915. // flag_get_user_flags() does caching.
  916. $user_flags = flag_get_user_flags($this->entity_type, $entity_id, $uid, $sid);
  917. return isset($user_flags[$this->name]) ? $user_flags[$this->name] : NULL;
  918. }
  919. /**
  920. * Similar to is_flagged() excepts it returns the flagging entity.
  921. */
  922. function get_flagging($entity_id, $uid = NULL, $sid = NULL) {
  923. if (($record = $this->get_flagging_record($entity_id, $uid, $sid))) {
  924. return flagging_load($record->flagging_id);
  925. }
  926. }
  927. /**
  928. * Determines if a certain user has flagged this object.
  929. *
  930. * You probably shouldn't call this raw private method: call the
  931. * is_flagged() method instead.
  932. *
  933. * This method is similar to is_flagged() except that it does direct SQL and
  934. * doesn't do caching. Use it when you want to not affect the cache, or to
  935. * bypass it.
  936. *
  937. * @return
  938. * If the object is flagged, returns the value of the 'flagging_id' column.
  939. * Else, returns FALSE.
  940. *
  941. * @private
  942. */
  943. function _is_flagged($entity_id, $uid, $sid) {
  944. return db_select('flagging', 'fc')
  945. ->fields('fc', array('flagging_id'))
  946. ->condition('fid', $this->fid)
  947. ->condition('uid', $uid)
  948. ->condition('sid', $sid)
  949. ->condition('entity_id', $entity_id)
  950. ->execute()
  951. ->fetchField();
  952. }
  953. /**
  954. * Increases the flag count for an object and clears the static counts cache.
  955. *
  956. * @param $entity_id
  957. * For which item should the count be increased.
  958. * @param $number
  959. * The amount of counts to increasing. Defaults to 1.
  960. *
  961. * @private
  962. */
  963. function _increase_count($entity_id, $number = 1) {
  964. db_merge('flag_counts')
  965. ->key(array(
  966. 'fid' => $this->fid,
  967. 'entity_id' => $entity_id,
  968. ))
  969. ->fields(array(
  970. 'entity_type' => $this->entity_type,
  971. 'count' => $number,
  972. 'last_updated' => REQUEST_TIME,
  973. ))
  974. ->updateFields(array(
  975. 'last_updated' => REQUEST_TIME,
  976. ))
  977. ->expression('count', 'count + :inc', array(':inc' => $number))
  978. ->execute();
  979. // Reset the static cache of flag counts, so code running after this gets
  980. // correct counts.
  981. drupal_static_reset('flag_get_counts');
  982. drupal_static_reset('flag_get_flag_counts');
  983. }
  984. /**
  985. * Decreases the flag count for an object and clears the static counts cache.
  986. *
  987. * @param $entity_id
  988. * For which item should the count be descreased.
  989. * @param $number
  990. * The amount of counts to decrease. Defaults to 1.
  991. *
  992. * @private
  993. */
  994. function _decrease_count($entity_id, $number = 1) {
  995. // Delete rows with count 0, for data consistency and space-saving.
  996. // Done before the db_update() to prevent out-of-bounds errors on "count".
  997. db_delete('flag_counts')
  998. ->condition('fid', $this->fid)
  999. ->condition('entity_id', $entity_id)
  1000. ->condition('count', $number, '<=')
  1001. ->execute();
  1002. // Update the count with the new value otherwise.
  1003. db_update('flag_counts')
  1004. ->expression('count', 'count - :inc', array(':inc' => $number))
  1005. ->fields(array(
  1006. 'last_updated' => REQUEST_TIME,
  1007. ))
  1008. ->condition('fid', $this->fid)
  1009. ->condition('entity_id', $entity_id)
  1010. ->execute();
  1011. // Reset the static cache of flag counts, so code running after this gets
  1012. // correct counts.
  1013. drupal_static_reset('flag_get_counts');
  1014. drupal_static_reset('flag_get_flag_counts');
  1015. }
  1016. /**
  1017. * Set a cookie for anonymous users to record their flagging.
  1018. *
  1019. * @private
  1020. */
  1021. function _flag_anonymous($entity_id) {
  1022. $storage = FlagCookieStorage::factory($this);
  1023. $storage->flag($entity_id);
  1024. }
  1025. /**
  1026. * Remove the cookie for anonymous users to record their unflagging.
  1027. *
  1028. * @private
  1029. */
  1030. function _unflag_anonymous($entity_id) {
  1031. $storage = FlagCookieStorage::factory($this);
  1032. $storage->unflag($entity_id);
  1033. }
  1034. /**
  1035. * Returns the number of times an item is flagged.
  1036. *
  1037. * Thanks to using a cache, inquiring several different flags about the same
  1038. * item results in only one SQL query.
  1039. */
  1040. function get_count($entity_id) {
  1041. $counts = flag_get_counts($this->entity_type, $entity_id);
  1042. return isset($counts[$this->name]) ? $counts[$this->name] : 0;
  1043. }
  1044. /**
  1045. * Returns the number of items a user has flagged.
  1046. *
  1047. * For global flags, pass '0' as the user ID and session ID.
  1048. */
  1049. function get_user_count($uid, $sid = NULL) {
  1050. if (!isset($sid)) {
  1051. $sid = flag_get_sid($uid);
  1052. }
  1053. return db_select('flagging', 'fc')->fields('fc', array('flagging_id'))
  1054. ->condition('fid', $this->fid)
  1055. ->condition('uid', $uid)
  1056. ->condition('sid', $sid)
  1057. ->countQuery()
  1058. ->execute()
  1059. ->fetchField();
  1060. }
  1061. /**
  1062. * Processes a flag label for display. This means language translation and
  1063. * token replacements.
  1064. *
  1065. * You should always call this function and not get at the label directly.
  1066. * E.g., do `print $flag->get_label('title')` instead of `print
  1067. * $flag->title`.
  1068. *
  1069. * @param $label
  1070. * The label to get, e.g. 'title', 'flag_short', 'unflag_short', etc.
  1071. * @param $entity_id
  1072. * The ID in whose context to interpret tokens. If not given, only global
  1073. * tokens will be substituted.
  1074. *
  1075. * @return
  1076. * The processed label.
  1077. */
  1078. function get_label($label, $entity_id = NULL) {
  1079. if (!isset($this->$label)) {
  1080. return;
  1081. }
  1082. $label = t($this->$label);
  1083. if (strpos($label, '[') !== FALSE) {
  1084. $label = $this->replace_tokens($label, array(), array('sanitize' => FALSE), $entity_id);
  1085. }
  1086. return filter_xss_admin($label);
  1087. }
  1088. /**
  1089. * Get the link type for this flag.
  1090. */
  1091. function get_link_type() {
  1092. $link_types = flag_get_link_types();
  1093. return (isset($this->link_type) && isset($link_types[$this->link_type])) ? $link_types[$this->link_type] : $link_types['normal'];
  1094. }
  1095. /**
  1096. * Replaces tokens in a label. Only the 'global' token context is recognized
  1097. * by default, so derived classes should override this method to add all
  1098. * token contexts they understand.
  1099. */
  1100. function replace_tokens($label, $contexts, $options, $entity_id) {
  1101. if (strpos($label , 'flagging:') !== FALSE) {
  1102. if (($flagging = $this->get_flagging($entity_id))) {
  1103. $contexts['flagging'] = $flagging;
  1104. }
  1105. }
  1106. return token_replace($label, $contexts, $options);
  1107. }
  1108. /**
  1109. * Returns the token types this flag understands in labels. These are used
  1110. * for narrowing down the token list shown in the help box to only the
  1111. * relevant ones.
  1112. *
  1113. * Derived classes should override this.
  1114. */
  1115. function get_labels_token_types() {
  1116. return array('flagging');
  1117. }
  1118. /**
  1119. * A convenience method for getting the flag title.
  1120. *
  1121. * `$flag->get_title()` is shorthand for `$flag->get_label('title')`.
  1122. */
  1123. function get_title($entity_id = NULL, $reset = FALSE) {
  1124. static $titles = array();
  1125. if ($reset) {
  1126. $titles = array();
  1127. }
  1128. $slot = intval($entity_id); // Convert NULL to 0.
  1129. if (!isset($titles[$this->fid][$slot])) {
  1130. $titles[$this->fid][$slot] = $this->get_label('title', $entity_id);
  1131. }
  1132. return $titles[$this->fid][$slot];
  1133. }
  1134. /**
  1135. * Returns a 'flag action' object. It exists only for the sake of its
  1136. * informative tokens. Currently, it's utilized only for the 'mail' action.
  1137. *
  1138. * Derived classes should populate the 'content_title' and 'content_url'
  1139. * slots.
  1140. */
  1141. function get_flag_action($entity_id) {
  1142. $flag_action = new stdClass();
  1143. $flag_action->flag = $this->name;
  1144. $flag_action->entity_type = $this->entity_type;
  1145. $flag_action->entity_id = $entity_id;
  1146. return $flag_action;
  1147. }
  1148. /**
  1149. * Returns an array of errors set during validation.
  1150. */
  1151. function get_errors() {
  1152. return $this->errors;
  1153. }
  1154. /**
  1155. * @addtogroup actions
  1156. * @{
  1157. * Methods that can be overridden to support Actions.
  1158. */
  1159. /**
  1160. * Returns an array of all actions that are executable with this flag.
  1161. */
  1162. function get_valid_actions() {
  1163. $actions = module_invoke_all('action_info');
  1164. foreach ($actions as $callback => $action) {
  1165. if ($action['type'] != $this->entity_type && !in_array('any', $action['triggers'])) {
  1166. unset($actions[$callback]);
  1167. }
  1168. }
  1169. return $actions;
  1170. }
  1171. /**
  1172. * Returns objects the action may possibly need. This method should return at
  1173. * least the 'primary' object the action operates on.
  1174. *
  1175. * This method is needed because get_valid_actions() returns actions that
  1176. * don't necessarily operate on an object of a type this flag manages. For
  1177. * example, flagging a comment may trigger an 'Unpublish post' action on a
  1178. * node; So the comment flag needs to tell the action about some node.
  1179. *
  1180. * Derived classes must implement this.
  1181. *
  1182. * @abstract
  1183. */
  1184. function get_relevant_action_objects($entity_id) {
  1185. return array();
  1186. }
  1187. /**
  1188. * @} End of "addtogroup actions".
  1189. */
  1190. /**
  1191. * @addtogroup views
  1192. * @{
  1193. * Methods that can be overridden to support the Views module.
  1194. */
  1195. /**
  1196. * Returns information needed for Views integration. E.g., the Views table
  1197. * holding the flagged object, its primary key, and various labels. See
  1198. * derived classes for examples.
  1199. *
  1200. * @static
  1201. */
  1202. function get_views_info() {
  1203. return array();
  1204. }
  1205. /**
  1206. * @} End of "addtogroup views".
  1207. */
  1208. /**
  1209. * Saves a flag to the database. It is a wrapper around update() and insert().
  1210. */
  1211. function save() {
  1212. // Allow the 'global' property to be a boolean, particularly when defined in
  1213. // hook_flag_default_flags(). Without this, a value of FALSE gets casted to
  1214. // an empty string which violates our schema. Other boolean properties are
  1215. // fine, as they are serialized.
  1216. $this->global = (int) $this->global;
  1217. if (isset($this->fid)) {
  1218. $this->update();
  1219. $this->is_new = FALSE;
  1220. }
  1221. else {
  1222. $this->insert();
  1223. $this->is_new = TRUE;
  1224. }
  1225. // Clear the page cache for anonymous users.
  1226. cache_clear_all('*', 'cache_page', TRUE);
  1227. }
  1228. /**
  1229. * Saves an existing flag to the database. Better use save().
  1230. */
  1231. function update() {
  1232. db_update('flag')->fields(array(
  1233. 'name' => $this->name,
  1234. 'title' => $this->title,
  1235. 'global' => $this->global,
  1236. 'options' => $this->get_serialized_options()))
  1237. ->condition('fid', $this->fid)
  1238. ->execute();
  1239. db_delete('flag_types')->condition('fid', $this->fid)->execute();
  1240. foreach ($this->types as $type) {
  1241. db_insert('flag_types')->fields(array(
  1242. 'fid' => $this->fid,
  1243. 'type' => $type))
  1244. ->execute();
  1245. }
  1246. }
  1247. /**
  1248. * Saves a new flag to the database. Better use save().
  1249. */
  1250. function insert() {
  1251. $this->fid = db_insert('flag')
  1252. ->fields(array(
  1253. 'entity_type' => $this->entity_type,
  1254. 'name' => $this->name,
  1255. 'title' => $this->title,
  1256. 'global' => $this->global,
  1257. 'options' => $this->get_serialized_options(),
  1258. ))
  1259. ->execute();
  1260. foreach ($this->types as $type) {
  1261. db_insert('flag_types')
  1262. ->fields(array(
  1263. 'fid' => $this->fid,
  1264. 'type' => $type,
  1265. ))
  1266. ->execute();
  1267. }
  1268. }
  1269. /**
  1270. * Options are stored serialized in the database.
  1271. */
  1272. function get_serialized_options() {
  1273. $option_names = array_keys($this->options());
  1274. $options = array();
  1275. foreach ($option_names as $option) {
  1276. $options[$option] = $this->$option;
  1277. }
  1278. return serialize($options);
  1279. }
  1280. /**
  1281. * Deletes a flag from the database.
  1282. */
  1283. function delete() {
  1284. db_delete('flag')->condition('fid', $this->fid)->execute();
  1285. db_delete('flagging')->condition('fid', $this->fid)->execute();
  1286. db_delete('flag_types')->condition('fid', $this->fid)->execute();
  1287. db_delete('flag_counts')->condition('fid', $this->fid)->execute();
  1288. module_invoke_all('flag_delete', $this);
  1289. }
  1290. /**
  1291. * Returns TRUE if this flag's declared API version is compatible with this
  1292. * module.
  1293. *
  1294. * An "incompatible" flag is one exported (and now being imported or exposed
  1295. * via hook_flag_default_flags()) by a different version of the Flag module.
  1296. * An incompatible flag should be treated as a "black box": it should not be
  1297. * saved or exported because our code may not know to handle its internal
  1298. * structure.
  1299. */
  1300. function is_compatible() {
  1301. if (isset($this->fid)) {
  1302. // Database flags are always compatible.
  1303. return TRUE;
  1304. }
  1305. else {
  1306. if (!isset($this->api_version)) {
  1307. $this->api_version = 1;
  1308. }
  1309. return $this->api_version == FLAG_API_VERSION;
  1310. }
  1311. }
  1312. /**
  1313. * Finds the "default flag" corresponding to this flag.
  1314. *
  1315. * Flags defined in code ("default flags") can be overridden. This method
  1316. * returns the default flag that is being overridden by $this. Returns NULL
  1317. * if $this overrides no default flag.
  1318. */
  1319. function find_default_flag() {
  1320. if ($this->fid) {
  1321. $default_flags = flag_get_default_flags(TRUE);
  1322. if (isset($default_flags[$this->name])) {
  1323. return $default_flags[$this->name];
  1324. }
  1325. }
  1326. }
  1327. /**
  1328. * Reverts an overriding flag to its default state.
  1329. *
  1330. * Note that $this isn't altered. To see the reverted flag you'll have to
  1331. * call flag_get_flag($this->name) again.
  1332. *
  1333. * @return
  1334. * TRUE if the flag was reverted successfully; FALSE if there was an error;
  1335. * NULL if this flag overrides no default flag.
  1336. */
  1337. function revert() {
  1338. if (($default_flag = $this->find_default_flag())) {
  1339. if ($default_flag->is_compatible()) {
  1340. $default_flag = clone $default_flag;
  1341. $default_flag->fid = $this->fid;
  1342. $default_flag->save();
  1343. drupal_static_reset('flag_get_flags');
  1344. return TRUE;
  1345. }
  1346. else {
  1347. return FALSE;
  1348. }
  1349. }
  1350. }
  1351. /**
  1352. * Disable a flag provided by a module.
  1353. */
  1354. function disable() {
  1355. if (isset($this->module)) {
  1356. $flag_status = variable_get('flag_default_flag_status', array());
  1357. $flag_status[$this->name] = FALSE;
  1358. variable_set('flag_default_flag_status', $flag_status);
  1359. }
  1360. }
  1361. /**
  1362. * Enable a flag provided by a module.
  1363. */
  1364. function enable() {
  1365. if (isset($this->module)) {
  1366. $flag_status = variable_get('flag_default_flag_status', array());
  1367. $flag_status[$this->name] = TRUE;
  1368. variable_set('flag_default_flag_status', $flag_status);
  1369. }
  1370. }
  1371. /**
  1372. * Returns administrative menu path for carrying out some action.
  1373. */
  1374. function admin_path($action) {
  1375. if ($action == 'edit') {
  1376. // Since 'edit' is the default tab, we omit the action.
  1377. return FLAG_ADMIN_PATH . '/manage/' . $this->name;
  1378. }
  1379. else {
  1380. return FLAG_ADMIN_PATH . '/manage/' . $this->name . '/' . $action;
  1381. }
  1382. }
  1383. /**
  1384. * Renders a flag/unflag link.
  1385. *
  1386. * This is a wrapper around theme('flag') that channels the call to the right
  1387. * template file.
  1388. *
  1389. * @param $action
  1390. * The action the link is about to carry out, either "flag" or "unflag".
  1391. * @param $entity_id
  1392. * The ID of the object to flag.
  1393. * @param $variables = array()
  1394. * An array of further variables to pass to theme('flag'). For the full list
  1395. * of parameters, see flag.tpl.php. Of particular interest:
  1396. * - after_flagging: Set to TRUE if this flag link is being displayed as the
  1397. * result of a flagging action.
  1398. * - errors: An array of error messages.
  1399. *
  1400. * @return
  1401. * The HTML for the flag link.
  1402. */
  1403. function theme($action, $entity_id, $variables = array()) {
  1404. static $js_added = array();
  1405. global $user;
  1406. $after_flagging = !empty($variables['after_flagging']);
  1407. // If the flagging user is anonymous, set a boolean for the benefit of
  1408. // JavaScript code. Currently, only our "anti-crawlers" mechanism uses it.
  1409. if ($user->uid == 0 && !isset($js_added['anonymous'])) {
  1410. $js_added['anonymous'] = TRUE;
  1411. drupal_add_js(array('flag' => array('anonymous' => TRUE)), 'setting');
  1412. }
  1413. // If the flagging user is anonymous and the page cache is enabled, we
  1414. // update the links through JavaScript.
  1415. if ($this->uses_anonymous_cookies() && !$after_flagging) {
  1416. if ($this->global) {
  1417. // In case of global flags, the JavaScript template is to contain
  1418. // the opposite of the current state.
  1419. $js_action = ($action == 'flag' ? 'unflag' : 'flag');
  1420. }
  1421. else {
  1422. // In case of non-global flags, we always show the "flag!" link,
  1423. // and then replace it with the "unflag!" link through JavaScript.
  1424. $js_action = 'unflag';
  1425. $action = 'flag';
  1426. }
  1427. if (!isset($js_added[$this->name . '_' . $entity_id])) {
  1428. $js_added[$this->name . '_' . $entity_id] = TRUE;
  1429. $js_template = theme($this->theme_suggestions(), array(
  1430. 'flag' => $this,
  1431. 'action' => $js_action,
  1432. 'entity_id' => $entity_id,
  1433. 'after_flagging' => $after_flagging,
  1434. ));
  1435. drupal_add_js(array('flag' => array('templates' => array($this->name . '_' . $entity_id => $js_template))), 'setting');
  1436. }
  1437. }
  1438. return theme($this->theme_suggestions(), array(
  1439. 'flag' => $this,
  1440. 'action' => $action,
  1441. 'entity_id' => $entity_id,
  1442. ) + $variables);
  1443. }
  1444. /**
  1445. * Provides an array of possible themes to try for a given flag.
  1446. */
  1447. function theme_suggestions() {
  1448. $suggestions = array();
  1449. $suggestions[] = 'flag__' . $this->name;
  1450. $suggestions[] = 'flag__' . $this->link_type;
  1451. $suggestions[] = 'flag';
  1452. return $suggestions;
  1453. }
  1454. /**
  1455. * A shortcut function to output the link URL.
  1456. */
  1457. function _flag_url($path, $fragment = NULL, $absolute = TRUE) {
  1458. return url($path, array('fragment' => $fragment, 'absolute' => $absolute));
  1459. }
  1460. }
  1461. /**
  1462. * A dummy flag to be used where the real implementation can't be found.
  1463. */
  1464. class flag_broken extends flag_flag {
  1465. function options_form(&$form) {
  1466. drupal_set_message(t("The module providing this flag wasn't found, or this flag type, %type, isn't valid.", array('%type' => $this->entity_type)), 'error');
  1467. $form = array();
  1468. }
  1469. }