flag_flag.inc 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624
  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 int $entity_id
  336. * The ID of the object to cache.
  337. * @param stdClass $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 string $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 bool
  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 int $entity_id
  419. * The entity ID to flag/unflag.
  420. * @param string|NULL $action
  421. * The action to test. Either 'flag' or 'unflag'. Leave NULL to determine
  422. * by flag status.
  423. * @param stdClass $account
  424. * The user on whose behalf to test the flagging action. Leave NULL for the
  425. * current user.
  426. *
  427. * @return bool
  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 array $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 stdClass $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 array
  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 string $entity_type
  563. * The type of entity being checked, such as "node".
  564. * @param string|NULL $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 string $action
  610. * Either 'flag' or 'unflag'.
  611. * @param int $entity_id
  612. * The ID of the item to flag or unflag.
  613. * @param string|NULL $account
  614. * The user on whose behalf to flag. Leave empty for the current user.
  615. * @param bool $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 bool
  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. // along with a transaction mechanism.
  700. $transaction = db_transaction();
  701. try {
  702. if ($action == 'unflag') {
  703. if ($flagged) {
  704. $this->flagging_delete($flagging, $entity_id, $account);
  705. }
  706. // We do nothing in the case of an attempt to unflag something that isn't
  707. // actually flagged.
  708. }
  709. elseif ($action == 'flag') {
  710. if (!$flagged) {
  711. $this->flagging_insert($flagging, $entity_id, $account);
  712. }
  713. else {
  714. $this->flagging_update($flagging, $entity_id, $account);
  715. }
  716. }
  717. }
  718. catch (Exception $e) {
  719. $transaction->rollback();
  720. watchdog_exception('flag', $e);
  721. throw $e;
  722. }
  723. return TRUE;
  724. }
  725. /**
  726. * Create a new Flagging to flag an entity.
  727. *
  728. * @param $flagging
  729. * The flagging entity that is to be saved.
  730. * @param $entity_id
  731. * The entity ID of entity being flagged.
  732. * @param $account
  733. * The account performing the flagging.
  734. */
  735. private function flagging_insert($flagging, $entity_id, $account) {
  736. if ($this->uses_anonymous_cookies()) {
  737. $this->_flag_anonymous($entity_id);
  738. }
  739. // Invoke presave hooks.
  740. field_attach_presave('flagging', $flagging);
  741. // Invoke hook_entity_presave().
  742. module_invoke_all('entity_presave', $flagging, 'flagging');
  743. // Set the timestamp.
  744. $flagging->timestamp = REQUEST_TIME;
  745. // Save the flagging entity.
  746. drupal_write_record('flagging', $flagging);
  747. // Clear various caches; we don't want code running after us to report
  748. // wrong counts or false flaggings.
  749. drupal_static_reset('flag_get_user_flags');
  750. drupal_static_reset('flag_get_entity_flags');
  751. // Despite being named in the same pattern as the count API functions, these
  752. // query the {flagging} table, so are reset here.
  753. drupal_static_reset('flag_get_entity_flag_counts');
  754. drupal_static_reset('flag_get_user_flag_counts');
  755. $this->_increase_count($entity_id);
  756. // We're writing out a flagging entity even when we aren't passed one
  757. // (e.g., when flagging via JavaScript toggle links); in this case
  758. // Field API will assign the fields their default values.
  759. // Invoke insert hooks.
  760. field_attach_insert('flagging', $flagging);
  761. // Invoke hook_entity_insert().
  762. module_invoke_all('entity_insert', $flagging, 'flagging');
  763. module_invoke_all('flag_flag', $this, $entity_id, $account, $flagging);
  764. // Invoke Rules event.
  765. if (module_exists('rules')) {
  766. $this->invoke_rules_event('flag', $flagging, $entity_id, $account);
  767. }
  768. }
  769. /**
  770. * Update a Flagging.
  771. *
  772. * @param $flagging
  773. * The flagging entity that is being updated.
  774. * @param $entity_id
  775. * The entity ID of entity the flagging is on.
  776. * @param $account
  777. * The account performing the action.
  778. */
  779. private function flagging_update($flagging, $entity_id, $account) {
  780. // Invoke presave hooks.
  781. // This is technically still a presave, even though the {flagging} table
  782. // itself is not changed.
  783. field_attach_presave('flagging', $flagging);
  784. // Invoke hook_entity_presave().
  785. module_invoke_all('entity_presave', $flagging, 'flagging');
  786. // This check exists solely to preserve previous behaviour with re-flagging.
  787. // TODO: consider removing it.
  788. if (!empty($flagging->given_as_parameter)) {
  789. field_attach_update('flagging', $flagging);
  790. // Update the cache.
  791. entity_get_controller('flagging')->resetCache();
  792. // Invoke hook_entity_update().
  793. // Since there are no fields on the {flagging} table that can be
  794. // meaningfully changed, we don't perform an update on it. However, this
  795. // technically still counts as updating the flagging entity, since we update
  796. // its fields.
  797. module_invoke_all('entity_update', $flagging, 'flagging');
  798. }
  799. }
  800. /**
  801. * Unflag an entity by deleting a Flagging.
  802. *
  803. * @param $flagging
  804. * The flagging entity that is to be removed.
  805. * @param $entity_id
  806. * The entity ID of entity being unflagged.
  807. * @param $account
  808. * The account performing the unflagging.
  809. */
  810. private function flagging_delete($flagging, $entity_id, $account) {
  811. if ($this->uses_anonymous_cookies()) {
  812. $this->_unflag_anonymous($entity_id);
  813. }
  814. $transaction = db_transaction();
  815. try {
  816. // Note the order: We decrease the count first so hooks have accurate
  817. // data, then invoke hooks, then delete the flagging entity.
  818. $this->_decrease_count($entity_id);
  819. module_invoke_all('flag_unflag', $this, $entity_id, $account, $flagging);
  820. // Invoke Rules event.
  821. if (module_exists('rules')) {
  822. $this->invoke_rules_event('unflag', $flagging, $entity_id, $account);
  823. }
  824. // Invoke hook_entity_delete().
  825. module_invoke_all('entity_delete', $flagging, 'flagging');
  826. // Delete field data.
  827. field_attach_delete('flagging', $flagging);
  828. // Delete the flagging entity.
  829. db_delete('flagging')->condition('flagging_id', $flagging->flagging_id)->execute();
  830. // Remove from the cache.
  831. entity_get_controller('flagging')->resetCache();
  832. // Clear various caches; we don't want code running after us to report
  833. // wrong counts or false flaggings.
  834. drupal_static_reset('flag_get_user_flags');
  835. drupal_static_reset('flag_get_entity_flags');
  836. // Despite being named in the same pattern as the count API functions, these
  837. // query the {flagging} table, so are reset here.
  838. drupal_static_reset('flag_get_entity_flag_counts');
  839. drupal_static_reset('flag_get_user_flag_counts');
  840. }
  841. catch (Exception $e) {
  842. $transaction->rollback();
  843. watchdog_exception('flag', $e);
  844. throw $e;
  845. }
  846. }
  847. /**
  848. * Invoke a Rules event in reaction to a flagging or unflagging.
  849. *
  850. * @param $action
  851. * Either 'flag' or 'unflag'.
  852. * @param $flagging
  853. * The flagging entity that is either newly created or about to be deleted.
  854. * @param $entity_id
  855. * The entity ID of entity being flagged or unflagged.
  856. * @param $account
  857. * The account performing the action.
  858. */
  859. protected function invoke_rules_event($action, $flagging, $entity_id, $account) {
  860. // We only support flags on entities: do nothing in this class.
  861. // See flag_entity::invoke_rules_event().
  862. return;
  863. }
  864. /**
  865. * Construct a new, empty flagging entity object.
  866. *
  867. * @param mixed $entity_id
  868. * The unique identifier of the object being flagged.
  869. * @param int $uid
  870. * (optional) The user id of the user doing the flagging.
  871. * @param mixed $sid
  872. * (optional) The user SID (provided by Session API) who is doing the
  873. * flagging. The SID is 0 for logged in users.
  874. *
  875. * @return stdClass
  876. * The returned object has at least the 'flag_name' property set, which
  877. * enables Field API to figure out the bundle, but it's your responsibility
  878. * to eventually populate 'entity_id' and 'flagging_id'.
  879. */
  880. function new_flagging($entity_id = NULL, $uid = NULL, $sid = NULL) {
  881. return (object) array(
  882. 'flagging_id' => NULL,
  883. 'flag_name' => $this->name,
  884. 'fid' => $this->fid,
  885. 'entity_type' => $this->entity_type,
  886. 'entity_id' => $entity_id,
  887. 'uid' => $uid,
  888. 'sid' => $sid,
  889. // The timestamp is not set until this is saved.
  890. );
  891. }
  892. /**
  893. * Determines if a certain user has flagged this object.
  894. *
  895. * Thanks to using a cache, inquiring several different flags about the same
  896. * item results in only one SQL query.
  897. *
  898. * @param int $uid
  899. * (optional) The user ID whose flags we're checking. If none given, the
  900. * current user will be used.
  901. *
  902. * @return bool
  903. * TRUE if the object is flagged, FALSE otherwise.
  904. */
  905. function is_flagged($entity_id, $uid = NULL, $sid = NULL) {
  906. return (bool) $this->get_flagging_record($entity_id, $uid, $sid);
  907. }
  908. /**
  909. * Returns the flagging record.
  910. *
  911. * This method returns the "flagging record": the {flagging} record that
  912. * exists for each flagged item (for a certain user). If the item isn't
  913. * flagged, returns NULL. This method could be useful, for example, when you
  914. * want to find out the 'flagging_id' or 'timestamp' values.
  915. *
  916. * Thanks to using a cache, inquiring several different flags about the same
  917. * item results in only one SQL query.
  918. *
  919. * Parameters are the same as is_flagged()'s.
  920. */
  921. function get_flagging_record($entity_id, $uid = NULL, $sid = NULL) {
  922. $uid = $this->global ? 0 : (!isset($uid) ? $GLOBALS['user']->uid : $uid);
  923. $sid = $this->global ? 0 : (!isset($sid) ? flag_get_sid($uid) : $sid);
  924. // Get all the flaggings for this user on the entity from
  925. // flag_get_user_flags(), which will statically cache them. This means that
  926. // when this method is called multiple times for all the flags on an entity,
  927. // only the first call incurs a database query.
  928. $user_flags = flag_get_user_flags($this->entity_type, $entity_id, $uid, $sid);
  929. return isset($user_flags[$this->name]) ? $user_flags[$this->name] : NULL;
  930. }
  931. /**
  932. * Similar to is_flagged() excepts it returns the flagging entity.
  933. */
  934. function get_flagging($entity_id, $uid = NULL, $sid = NULL) {
  935. if (($record = $this->get_flagging_record($entity_id, $uid, $sid))) {
  936. return flagging_load($record->flagging_id);
  937. }
  938. }
  939. /**
  940. * Determines if a certain user has flagged this object.
  941. *
  942. * You probably shouldn't call this raw private method: call the
  943. * is_flagged() method instead.
  944. *
  945. * This method is similar to is_flagged() except that it does direct SQL and
  946. * doesn't do caching. Use it when you want to not affect the cache, or to
  947. * bypass it.
  948. *
  949. * @return
  950. * If the object is flagged, returns the value of the 'flagging_id' column.
  951. * Else, returns FALSE.
  952. *
  953. * @private
  954. */
  955. function _is_flagged($entity_id, $uid, $sid) {
  956. return db_select('flagging', 'fc')
  957. ->fields('fc', array('flagging_id'))
  958. ->condition('fid', $this->fid)
  959. ->condition('uid', $uid)
  960. ->condition('sid', $sid)
  961. ->condition('entity_id', $entity_id)
  962. ->execute()
  963. ->fetchField();
  964. }
  965. /**
  966. * Increases the flag count for an object and clears the static counts cache.
  967. *
  968. * @param int $entity_id
  969. * For which item should the count be increased.
  970. * @param int $number
  971. * The amount of counts to increasing. Defaults to 1.
  972. *
  973. * @private
  974. */
  975. function _increase_count($entity_id, $number = 1) {
  976. db_merge('flag_counts')
  977. ->key(array(
  978. 'fid' => $this->fid,
  979. 'entity_id' => $entity_id,
  980. ))
  981. ->fields(array(
  982. 'entity_type' => $this->entity_type,
  983. 'count' => $number,
  984. 'last_updated' => REQUEST_TIME,
  985. ))
  986. ->updateFields(array(
  987. 'last_updated' => REQUEST_TIME,
  988. ))
  989. ->expression('count', 'count + :inc', array(':inc' => $number))
  990. ->execute();
  991. // Reset the static cache of flag counts, so code running after this gets
  992. // correct counts.
  993. drupal_static_reset('flag_get_counts');
  994. drupal_static_reset('flag_get_flag_counts');
  995. }
  996. /**
  997. * Decreases the flag count for an object and clears the static counts cache.
  998. *
  999. * @param int $entity_id
  1000. * For which item should the count be descreased.
  1001. * @param int $number
  1002. * The amount of counts to decrease. Defaults to 1.
  1003. *
  1004. * @private
  1005. */
  1006. function _decrease_count($entity_id, $number = 1) {
  1007. // Delete rows with count 0, for data consistency and space-saving.
  1008. // Done before the db_update() to prevent out-of-bounds errors on "count".
  1009. db_delete('flag_counts')
  1010. ->condition('fid', $this->fid)
  1011. ->condition('entity_id', $entity_id)
  1012. ->condition('count', $number, '<=')
  1013. ->execute();
  1014. // Update the count with the new value otherwise.
  1015. db_update('flag_counts')
  1016. ->expression('count', 'count - :inc', array(':inc' => $number))
  1017. ->fields(array(
  1018. 'last_updated' => REQUEST_TIME,
  1019. ))
  1020. ->condition('fid', $this->fid)
  1021. ->condition('entity_id', $entity_id)
  1022. ->execute();
  1023. // Reset the static cache of flag counts, so code running after this gets
  1024. // correct counts.
  1025. drupal_static_reset('flag_get_counts');
  1026. drupal_static_reset('flag_get_flag_counts');
  1027. }
  1028. /**
  1029. * Set a cookie for anonymous users to record their flagging.
  1030. *
  1031. * @private
  1032. */
  1033. function _flag_anonymous($entity_id) {
  1034. $storage = FlagCookieStorage::factory($this);
  1035. $storage->flag($entity_id);
  1036. }
  1037. /**
  1038. * Remove the cookie for anonymous users to record their unflagging.
  1039. *
  1040. * @private
  1041. */
  1042. function _unflag_anonymous($entity_id) {
  1043. $storage = FlagCookieStorage::factory($this);
  1044. $storage->unflag($entity_id);
  1045. }
  1046. /**
  1047. * Returns the number of times an item is flagged.
  1048. *
  1049. * Thanks to using a cache, inquiring several different flags about the same
  1050. * item results in only one SQL query.
  1051. */
  1052. function get_count($entity_id) {
  1053. $counts = flag_get_counts($this->entity_type, $entity_id);
  1054. return isset($counts[$this->name]) ? $counts[$this->name] : 0;
  1055. }
  1056. /**
  1057. * Returns the number of items a user has flagged.
  1058. *
  1059. * For global flags, pass '0' as the user ID and session ID.
  1060. */
  1061. function get_user_count($uid, $sid = NULL) {
  1062. if (!isset($sid)) {
  1063. $sid = flag_get_sid($uid);
  1064. }
  1065. return db_select('flagging', 'fc')->fields('fc', array('flagging_id'))
  1066. ->condition('fid', $this->fid)
  1067. ->condition('uid', $uid)
  1068. ->condition('sid', $sid)
  1069. ->countQuery()
  1070. ->execute()
  1071. ->fetchField();
  1072. }
  1073. /**
  1074. * Processes a flag label for display. This means language translation and
  1075. * token replacements.
  1076. *
  1077. * You should always call this function and not get at the label directly.
  1078. * E.g., do `print $flag->get_label('title')` instead of `print
  1079. * $flag->title`.
  1080. *
  1081. * @param string $label
  1082. * The label to get, e.g. 'title', 'flag_short', 'unflag_short', etc.
  1083. * @param int $entity_id
  1084. * The ID in whose context to interpret tokens. If not given, only global
  1085. * tokens will be substituted.
  1086. *
  1087. * @return string
  1088. * The processed label.
  1089. */
  1090. function get_label($label, $entity_id = NULL) {
  1091. if (!isset($this->$label)) {
  1092. return;
  1093. }
  1094. $label = t($this->$label);
  1095. if (strpos($label, '[') !== FALSE) {
  1096. $label = $this->replace_tokens($label, array(), array('sanitize' => FALSE), $entity_id);
  1097. }
  1098. return filter_xss_admin($label);
  1099. }
  1100. /**
  1101. * Get the link type for this flag.
  1102. */
  1103. function get_link_type() {
  1104. $link_types = flag_get_link_types();
  1105. return (isset($this->link_type) && isset($link_types[$this->link_type])) ? $link_types[$this->link_type] : $link_types['normal'];
  1106. }
  1107. /**
  1108. * Replaces tokens in a label. Only the 'global' token context is recognized
  1109. * by default, so derived classes should override this method to add all
  1110. * token contexts they understand.
  1111. */
  1112. function replace_tokens($label, $contexts, $options, $entity_id) {
  1113. if (strpos($label , 'flagging:') !== FALSE) {
  1114. if (($flagging = $this->get_flagging($entity_id))) {
  1115. $contexts['flagging'] = $flagging;
  1116. }
  1117. }
  1118. return token_replace($label, $contexts, $options);
  1119. }
  1120. /**
  1121. * Returns the token types this flag understands in labels. These are used
  1122. * for narrowing down the token list shown in the help box to only the
  1123. * relevant ones.
  1124. *
  1125. * Derived classes should override this.
  1126. */
  1127. function get_labels_token_types() {
  1128. return array('flagging');
  1129. }
  1130. /**
  1131. * A convenience method for getting the flag title.
  1132. *
  1133. * `$flag->get_title()` is shorthand for `$flag->get_label('title')`.
  1134. */
  1135. function get_title($entity_id = NULL, $reset = FALSE) {
  1136. static $titles = array();
  1137. if ($reset) {
  1138. $titles = array();
  1139. }
  1140. $slot = intval($entity_id); // Convert NULL to 0.
  1141. if (!isset($titles[$this->fid][$slot])) {
  1142. $titles[$this->fid][$slot] = $this->get_label('title', $entity_id);
  1143. }
  1144. return $titles[$this->fid][$slot];
  1145. }
  1146. /**
  1147. * Returns a 'flag action' object. It exists only for the sake of its
  1148. * informative tokens. Currently, it's utilized only for the 'mail' action.
  1149. *
  1150. * Derived classes should populate the 'content_title' and 'content_url'
  1151. * slots.
  1152. */
  1153. function get_flag_action($entity_id) {
  1154. $flag_action = new stdClass();
  1155. $flag_action->flag = $this->name;
  1156. $flag_action->entity_type = $this->entity_type;
  1157. $flag_action->entity_id = $entity_id;
  1158. return $flag_action;
  1159. }
  1160. /**
  1161. * Returns an array of errors set during validation.
  1162. */
  1163. function get_errors() {
  1164. return $this->errors;
  1165. }
  1166. /**
  1167. * @addtogroup actions
  1168. * @{
  1169. * Methods that can be overridden to support Actions.
  1170. */
  1171. /**
  1172. * Returns an array of all actions that are executable with this flag.
  1173. */
  1174. function get_valid_actions() {
  1175. $actions = module_invoke_all('action_info');
  1176. foreach ($actions as $callback => $action) {
  1177. if ($action['type'] != $this->entity_type && !in_array('any', $action['triggers'])) {
  1178. unset($actions[$callback]);
  1179. }
  1180. }
  1181. return $actions;
  1182. }
  1183. /**
  1184. * Returns objects the action may possibly need. This method should return at
  1185. * least the 'primary' object the action operates on.
  1186. *
  1187. * This method is needed because get_valid_actions() returns actions that
  1188. * don't necessarily operate on an object of a type this flag manages. For
  1189. * example, flagging a comment may trigger an 'Unpublish post' action on a
  1190. * node; So the comment flag needs to tell the action about some node.
  1191. *
  1192. * Derived classes must implement this.
  1193. *
  1194. * @abstract
  1195. */
  1196. function get_relevant_action_objects($entity_id) {
  1197. return array();
  1198. }
  1199. /**
  1200. * @} End of "addtogroup actions".
  1201. */
  1202. /**
  1203. * @addtogroup views
  1204. * @{
  1205. * Methods that can be overridden to support the Views module.
  1206. */
  1207. /**
  1208. * Returns information needed for Views integration. E.g., the Views table
  1209. * holding the flagged object, its primary key, and various labels. See
  1210. * derived classes for examples.
  1211. *
  1212. * @static
  1213. */
  1214. function get_views_info() {
  1215. return array();
  1216. }
  1217. /**
  1218. * @} End of "addtogroup views".
  1219. */
  1220. /**
  1221. * Saves a flag to the database. It is a wrapper around update() and insert().
  1222. */
  1223. function save() {
  1224. // Allow the 'global' property to be a boolean, particularly when defined in
  1225. // hook_flag_default_flags(). Without this, a value of FALSE gets casted to
  1226. // an empty string which violates our schema. Other boolean properties are
  1227. // fine, as they are serialized.
  1228. $this->global = (int) $this->global;
  1229. if (isset($this->fid)) {
  1230. $this->update();
  1231. $this->is_new = FALSE;
  1232. }
  1233. else {
  1234. $this->insert();
  1235. $this->is_new = TRUE;
  1236. }
  1237. // Clear the page cache for anonymous users.
  1238. cache_clear_all('*', 'cache_page', TRUE);
  1239. }
  1240. /**
  1241. * Saves an existing flag to the database. Better use save().
  1242. */
  1243. function update() {
  1244. db_update('flag')->fields(array(
  1245. 'name' => $this->name,
  1246. 'title' => $this->title,
  1247. 'global' => $this->global,
  1248. 'options' => $this->get_serialized_options()))
  1249. ->condition('fid', $this->fid)
  1250. ->execute();
  1251. db_delete('flag_types')->condition('fid', $this->fid)->execute();
  1252. foreach ($this->types as $type) {
  1253. db_insert('flag_types')->fields(array(
  1254. 'fid' => $this->fid,
  1255. 'type' => $type))
  1256. ->execute();
  1257. }
  1258. }
  1259. /**
  1260. * Saves a new flag to the database. Better use save().
  1261. */
  1262. function insert() {
  1263. $this->fid = db_insert('flag')
  1264. ->fields(array(
  1265. 'entity_type' => $this->entity_type,
  1266. 'name' => $this->name,
  1267. 'title' => $this->title,
  1268. 'global' => $this->global,
  1269. 'options' => $this->get_serialized_options(),
  1270. ))
  1271. ->execute();
  1272. foreach ($this->types as $type) {
  1273. db_insert('flag_types')
  1274. ->fields(array(
  1275. 'fid' => $this->fid,
  1276. 'type' => $type,
  1277. ))
  1278. ->execute();
  1279. }
  1280. }
  1281. /**
  1282. * Options are stored serialized in the database.
  1283. */
  1284. function get_serialized_options() {
  1285. $option_names = array_keys($this->options());
  1286. $options = array();
  1287. foreach ($option_names as $option) {
  1288. $options[$option] = $this->$option;
  1289. }
  1290. return serialize($options);
  1291. }
  1292. /**
  1293. * Deletes a flag from the database.
  1294. */
  1295. function delete() {
  1296. field_attach_delete_bundle('flagging', $this->name);
  1297. db_delete('flag')->condition('fid', $this->fid)->execute();
  1298. db_delete('flagging')->condition('fid', $this->fid)->execute();
  1299. db_delete('flag_types')->condition('fid', $this->fid)->execute();
  1300. db_delete('flag_counts')->condition('fid', $this->fid)->execute();
  1301. module_invoke_all('flag_delete', $this);
  1302. }
  1303. /**
  1304. * Returns TRUE if this flag's declared API version is compatible with this
  1305. * module.
  1306. *
  1307. * An "incompatible" flag is one exported (and now being imported or exposed
  1308. * via hook_flag_default_flags()) by a different version of the Flag module.
  1309. * An incompatible flag should be treated as a "black box": it should not be
  1310. * saved or exported because our code may not know to handle its internal
  1311. * structure.
  1312. */
  1313. function is_compatible() {
  1314. if (isset($this->fid)) {
  1315. // Database flags are always compatible.
  1316. return TRUE;
  1317. }
  1318. else {
  1319. if (!isset($this->api_version)) {
  1320. $this->api_version = 1;
  1321. }
  1322. return $this->api_version == FLAG_API_VERSION;
  1323. }
  1324. }
  1325. /**
  1326. * Finds the "default flag" corresponding to this flag.
  1327. *
  1328. * Flags defined in code ("default flags") can be overridden. This method
  1329. * returns the default flag that is being overridden by $this. Returns NULL
  1330. * if $this overrides no default flag.
  1331. */
  1332. function find_default_flag() {
  1333. if ($this->fid) {
  1334. $default_flags = flag_get_default_flags(TRUE);
  1335. if (isset($default_flags[$this->name])) {
  1336. return $default_flags[$this->name];
  1337. }
  1338. }
  1339. }
  1340. /**
  1341. * Reverts an overriding flag to its default state.
  1342. *
  1343. * Note that $this isn't altered. To see the reverted flag you'll have to
  1344. * call flag_get_flag($this->name) again.
  1345. *
  1346. * @return
  1347. * TRUE if the flag was reverted successfully; FALSE if there was an error;
  1348. * NULL if this flag overrides no default flag.
  1349. */
  1350. function revert() {
  1351. if (($default_flag = $this->find_default_flag())) {
  1352. if ($default_flag->is_compatible()) {
  1353. $default_flag = clone $default_flag;
  1354. $default_flag->fid = $this->fid;
  1355. $default_flag->save();
  1356. drupal_static_reset('flag_get_flags');
  1357. return TRUE;
  1358. }
  1359. else {
  1360. return FALSE;
  1361. }
  1362. }
  1363. }
  1364. /**
  1365. * Disable a flag provided by a module.
  1366. */
  1367. function disable() {
  1368. if (isset($this->module)) {
  1369. $flag_status = variable_get('flag_default_flag_status', array());
  1370. $flag_status[$this->name] = FALSE;
  1371. variable_set('flag_default_flag_status', $flag_status);
  1372. }
  1373. }
  1374. /**
  1375. * Enable a flag provided by a module.
  1376. */
  1377. function enable() {
  1378. if (isset($this->module)) {
  1379. $flag_status = variable_get('flag_default_flag_status', array());
  1380. $flag_status[$this->name] = TRUE;
  1381. variable_set('flag_default_flag_status', $flag_status);
  1382. }
  1383. }
  1384. /**
  1385. * Returns administrative menu path for carrying out some action.
  1386. */
  1387. function admin_path($action) {
  1388. if ($action == 'edit') {
  1389. // Since 'edit' is the default tab, we omit the action.
  1390. return FLAG_ADMIN_PATH . '/manage/' . $this->name;
  1391. }
  1392. else {
  1393. return FLAG_ADMIN_PATH . '/manage/' . $this->name . '/' . $action;
  1394. }
  1395. }
  1396. /**
  1397. * Renders a flag/unflag link.
  1398. *
  1399. * This is a wrapper around theme('flag') that channels the call to the right
  1400. * template file.
  1401. *
  1402. * @param string $action
  1403. * The action the link is about to carry out, either "flag" or "unflag".
  1404. * @param int $entity_id
  1405. * The ID of the object to flag.
  1406. * @param array $variables
  1407. * An array of further variables to pass to theme('flag'). For the full list
  1408. * of parameters, see flag.tpl.php. Of particular interest:
  1409. * - after_flagging: Set to TRUE if this flag link is being displayed as the
  1410. * result of a flagging action.
  1411. * - errors: An array of error messages.
  1412. *
  1413. * @return string
  1414. * The HTML for the flag link.
  1415. */
  1416. function theme($action, $entity_id, $variables = array()) {
  1417. static $js_added = array();
  1418. global $user;
  1419. $after_flagging = !empty($variables['after_flagging']);
  1420. // If the flagging user is anonymous, set a boolean for the benefit of
  1421. // JavaScript code. Currently, only our "anti-crawlers" mechanism uses it.
  1422. if ($user->uid == 0 && !isset($js_added['anonymous'])) {
  1423. $js_added['anonymous'] = TRUE;
  1424. drupal_add_js(array('flag' => array('anonymous' => TRUE)), 'setting');
  1425. }
  1426. // If the flagging user is anonymous and the page cache is enabled, we
  1427. // update the links through JavaScript.
  1428. if ($this->uses_anonymous_cookies() && !$after_flagging) {
  1429. if ($this->global) {
  1430. // In case of global flags, the JavaScript template is to contain
  1431. // the opposite of the current state.
  1432. $js_action = ($action == 'flag' ? 'unflag' : 'flag');
  1433. }
  1434. else {
  1435. // In case of non-global flags, we always show the "flag!" link,
  1436. // and then replace it with the "unflag!" link through JavaScript.
  1437. $js_action = 'unflag';
  1438. $action = 'flag';
  1439. }
  1440. if (!isset($js_added[$this->name . '_' . $entity_id])) {
  1441. $js_added[$this->name . '_' . $entity_id] = TRUE;
  1442. $js_template = theme($this->theme_suggestions(), array(
  1443. 'flag' => $this,
  1444. 'action' => $js_action,
  1445. 'entity_id' => $entity_id,
  1446. 'after_flagging' => $after_flagging,
  1447. ));
  1448. drupal_add_js(array('flag' => array('templates' => array($this->name . '_' . $entity_id => $js_template))), 'setting');
  1449. }
  1450. }
  1451. return theme($this->theme_suggestions(), array(
  1452. 'flag' => $this,
  1453. 'action' => $action,
  1454. 'entity_id' => $entity_id,
  1455. ) + $variables);
  1456. }
  1457. /**
  1458. * Provides an array of possible themes to try for a given flag.
  1459. */
  1460. function theme_suggestions() {
  1461. $suggestions = array();
  1462. $suggestions[] = 'flag__' . $this->name;
  1463. $suggestions[] = 'flag__' . $this->link_type;
  1464. $suggestions[] = 'flag';
  1465. return $suggestions;
  1466. }
  1467. /**
  1468. * A shortcut function to output the link URL.
  1469. */
  1470. function _flag_url($path, $fragment = NULL, $absolute = TRUE) {
  1471. return url($path, array('fragment' => $fragment, 'absolute' => $absolute));
  1472. }
  1473. }
  1474. /**
  1475. * A dummy flag to be used where the real implementation can't be found.
  1476. */
  1477. class flag_broken extends flag_flag {
  1478. function options_form(&$form) {
  1479. 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');
  1480. $form = array();
  1481. }
  1482. }