flag.module

Tracking 7.x-3.x branch
  1. drupal
    1. 5 contributions/flag/flag.module
    2. 6 contributions/flag/flag.module
    3. 7 contributions/flag/flag.module

The Flag module.

Functions & methods

NameDescription
flagFlags or unflags an item.
flagging_loadLoads a flagging entity.
flag_admin_menu_mapImplements hook_admin_menu_map().
flag_check_tokenCheck to see if a token value matches the specified node.
flag_contextual_links_view_alter
flag_create_handlerInstantiates a new flag handler.
flag_create_linkA utility function for outputting a flag link.
flag_ctools_plugin_directoryImplements hook_ctools_plugin_directory().
flag_entity_deleteImplements hook_entity_delete().
flag_entity_infoImplements hook_entity_info().
flag_entity_query_alterImplements hook_entity_query_alter().
flag_entity_viewImplements hook_entity_view().
flag_features_apiImplements hook_features_api().
flag_fetch_definitionReturns a flag definition.
flag_field_attach_delete_bundleImplements hook_field_attach_delete_bundle().
flag_field_attach_formImplements hook_field_attach_form().
flag_field_attach_insertImplements hook_field_attach_insert().
flag_field_attach_saveShared saving routine between flag_field_attach_insert/update().
flag_field_attach_submitImplements hook_field_attach_submit().
flag_field_attach_updateImplements hook_field_attach_update().
flag_field_extra_fieldsImplements hook_field_extra_fields().
flag_flag_accessImplements hook_flag_access().
flag_flag_access_multipleImplements hook_flag_access_multiple().
flag_flag_flagImplements hook_flag_flag().
flag_flag_linkImplements hook_flag_link().
flag_flag_triggerTrigger actions if any are available. Helper for hook_flag_(un)flag().
flag_flag_unflagImplements hook_flag_unflag().
flag_form_alterImplements hook_form_alter().
flag_form_node_type_form_alterImplements hook_form_FORM_ID_alter(): node_type_form.
flag_form_user_admin_permissions_alterImplements hook_form_FORM_ID_alter(): user_admin_permissions.
flag_get_countsGet flag counts for all flags on a node.
flag_get_default_flagsRetrieve a list of flags defined by modules.
flag_get_entity_flagsReturn a list of users who have flagged an entity.
flag_get_entity_idGet entity ID from a flag content ID.
flag_get_flagLoad a single flag either by name or by flag ID.
flag_get_flagsList all flags available.
flag_get_flag_countsGet the total count of items flagged within a flag.
flag_get_flag_flagging_dataGet all flagged entities in a flag.
flag_get_link_typesReturn an array of link types provided by modules.
flag_get_sidGet the Session ID for a user. Utilizes the Session API module.
flag_get_tokenGet a private token used to protect links from spoofing - CSRF.
flag_get_typesReturns all flag types defined on the system.
flag_get_user_flagsFind what a user has flagged, either a single node or on the entire site.
flag_helpImplements hook_help().
flag_hook_infoImplements hook_hook_info().
flag_initImplements hook_init().
flag_linkImplements hook_link().
flag_loadMenu loader for '%flag' arguments.
flag_mailImplements hook_mail().
flag_menuImplements hook_menu().
flag_nodesCallback function for hook_node_operations().
flag_node_deleteImplements hook_node_delete().
flag_node_insertImplements hook_node_insert().
flag_node_operationsImplements hook_node_operations().
flag_node_saveShared saving routine between flag_node_insert() and flag_node_update().
flag_node_translation_changeImplements hook_node_translation_change().
flag_node_updateImplements hook_node_update().
flag_permissionImplements hook_permission().
flag_reset_flagRemove all flagged entities from a flag.
flag_session_api_cleanupImplements hook_session_api_cleanup().
flag_set_sidSet the Session ID for a user. Utilizes the Session API module.
flag_themeImplements hook_theme().
flag_trim_flagTrim a flag to a certain size.
flag_usersCallback function for hook_user_operations().
flag_user_account_removalCallback function for user account cancellation or deletion.
flag_user_cancelImplements hook_user_cancel().
flag_user_deleteImplements hook_user_delete().
flag_user_loginImplements hook_user_login().
flag_user_operationsImplements hook_user_operations().
flag_user_viewImplements hook_user_view().
flag_views_apiImplements hook_views_api().
template_preprocess_flagA preprocess function for our theme('flag'). It generates the variables needed there.
_flag_compare_weightComparison function for uasort().
_flag_entity_deleteDeletes flagging records for the entity.
_flag_entity_enabledUtility function: Checks whether a flag applies to a certain type, and possibly subtype, of entity.
_flag_get_flag_namesReturn an array of flag names keyed by fid.
_flag_link_type_descriptionsReturn an array of flag link type descriptions.
_flag_link_type_fieldsReturn an array of flag link fields that are dependent on a link type.
_flag_link_type_optionsReturn an array of flag link types suitable for a select list or radios.
_flag_menu_titleMenu title callback.
_flag_urlA shortcut function to output the link URL.

Constants

NameDescription
FLAG_ADMIN_PATH
FLAG_ADMIN_PATH_START
FLAG_API_VERSION

File

View source
  1. <?php
  2. /**
  3. * @file
  4. * The Flag module.
  5. */
  6. define('FLAG_API_VERSION', 3);
  7. define('FLAG_ADMIN_PATH', 'admin/structure/flags');
  8. define('FLAG_ADMIN_PATH_START', 3);
  9. /**
  10. * Implements hook_entity_info().
  11. */
  12. function flag_entity_info() {
  13. $return = array(
  14. 'flagging' => array(
  15. 'label' => t('Flagging'),
  16. 'controller class' => 'FlaggingController',
  17. 'base table' => 'flagging',
  18. 'fieldable' => TRUE,
  19. 'entity keys' => array(
  20. 'id' => 'flagging_id',
  21. 'bundle' => 'flag_name',
  22. ),
  23. // The following tells Field UI how to extract the bundle name from a
  24. // $flag object when we're visiting ?q=admin/.../manage/%flag/fields.
  25. 'bundle keys' => array(
  26. 'bundle' => 'name',
  27. ),
  28. 'bundles' => array(),
  29. ),
  30. );
  31. // Add bundle info but bypass flag_get_flags() as we cannot use it here, as
  32. // it calls entity_get_info().
  33. $result = db_query("SELECT name, title FROM {flag}");
  34. $flag_names = $result->fetchAllKeyed();
  35. foreach ($flag_names as $flag_name => $flag_title) {
  36. $return['flagging']['bundles'][$flag_name] = array(
  37. 'label' => $flag_title,
  38. 'admin' => array(
  39. 'path' => FLAG_ADMIN_PATH . '/manage/%flag',
  40. 'real path' => FLAG_ADMIN_PATH . '/manage/' . $flag_name,
  41. 'bundle argument' => FLAG_ADMIN_PATH_START + 1,
  42. 'access arguments' => array('administer flags'),
  43. ),
  44. );
  45. }
  46. return $return;
  47. }
  48. /**
  49. * Loads a flagging entity.
  50. *
  51. * @param $flagging_id
  52. * The 'flagging_id' database serial column.
  53. * @param $reset
  54. * Whether to reset the DrupalDefaultEntityController cache.
  55. *
  56. * @return
  57. * The entity object, or FALSE if it can't be found.
  58. */
  59. function flagging_load($flagging_id, $reset = FALSE) {
  60. // The flag machine name is loaded in by FlaggingController::buildQuery().
  61. $result = entity_load('flagging', array($flagging_id), array(), $reset);
  62. return reset($result);
  63. }
  64. // @todo: Implement flagging_save(). It's not required but other modules may expect it.
  65. // @todo: Implement flagging_view(). Not extremely useful. I already have it.
  66. // @todo: When renaming a flag: Call field_attach_rename_bundle().
  67. // @todo: When creating a flag: Call field_attach_create_bundle().
  68. // @todo: When deleting a flag: Call field_attach_delete_bundle().
  69. // @tood: Discuss: Should flag deleting call flag_reset_flag()? No.
  70. // @todo: flag_reset_flag():
  71. // - it should delete the flaggings.
  72. // - (it has other issues; see http://drupal.org/node/894992.)
  73. // - (is problematic: it might not be possible to delete all data in a single page request.)
  74. // @todo: Discuss: Note that almost all functions/identifiers dealing with
  75. // flaggings *aren't* prefixed by "flag_". For example:
  76. // - The menu argument is %flagging, not %flag_flagging.
  77. // - The entity type is "flagging", not "flag_flagging".
  78. // On the one hand this succinct version is readable and nice. On the other hand, it isn't
  79. // very "correct".
  80. /**
  81. * Implements hook_entity_query_alter().
  82. *
  83. * Converts EntityFieldQuery instances on flaggings that have an entity
  84. * condition on bundles (flag machine names).
  85. *
  86. * Based on taxonomy_entity_query_alter().
  87. */
  88. function flag_entity_query_alter($query) {
  89. $conditions = &$query->entityConditions;
  90. // Alter only flagging queries with bundle conditions.
  91. if (isset($conditions['entity_type']) && $conditions['entity_type']['value'] == 'flagging' && isset($conditions['bundle'])) {
  92. // Convert flag machine names to flag IDs.
  93. $flags = flag_get_flags();
  94. $fids = array();
  95. if (is_array($conditions['bundle']['value'])) {
  96. foreach ($conditions['bundle']['value'] as $flag_name) {
  97. $fids[] = $flags[$flag_name]->fid;
  98. }
  99. }
  100. else {
  101. $flag_name = $conditions['bundle']['value'];
  102. $fids = $flags[$flag_name]->fid;
  103. }
  104. $query->propertyCondition('fid', $fids, $conditions['bundle']['operator']);
  105. unset($conditions['bundle']);
  106. }
  107. }
  108. /**
  109. * Implements hook_menu().
  110. */
  111. function flag_menu() {
  112. $items[FLAG_ADMIN_PATH] = array(
  113. 'title' => 'Flags',
  114. 'page callback' => 'flag_admin_page',
  115. 'access callback' => 'user_access',
  116. 'access arguments' => array('administer flags'),
  117. 'description' => 'Configure flags for marking content with arbitrary information (such as <em>offensive</em> or <em>bookmarked</em>).',
  118. 'file' => 'includes/flag.admin.inc',
  119. 'type' => MENU_NORMAL_ITEM,
  120. );
  121. $items[FLAG_ADMIN_PATH . '/list'] = array(
  122. 'title' => 'List',
  123. 'type' => MENU_DEFAULT_LOCAL_TASK,
  124. 'weight' => -10,
  125. );
  126. $items[FLAG_ADMIN_PATH . '/add'] = array(
  127. 'title' => 'Add flag',
  128. 'page callback' => 'flag_add_page',
  129. 'access callback' => 'user_access',
  130. 'access arguments' => array('administer flags'),
  131. 'file' => 'includes/flag.admin.inc',
  132. 'type' => MENU_LOCAL_ACTION,
  133. 'weight' => 1,
  134. );
  135. $items[FLAG_ADMIN_PATH . '/import'] = array(
  136. 'title' => 'Import',
  137. 'page callback' => 'drupal_get_form',
  138. 'page arguments' => array('flag_import_form'),
  139. 'access arguments' => array('use flag import'),
  140. 'file' => 'includes/flag.export.inc',
  141. 'type' => MENU_LOCAL_TASK,
  142. 'weight' => 2,
  143. );
  144. $items[FLAG_ADMIN_PATH . '/export'] = array(
  145. 'title' => 'Export',
  146. 'page callback' => 'drupal_get_form',
  147. 'page arguments' => array('flag_export_form'),
  148. 'access arguments' => array('administer flags'),
  149. 'file' => 'includes/flag.export.inc',
  150. 'type' => MENU_LOCAL_TASK,
  151. 'weight' => 3,
  152. );
  153. $items[FLAG_ADMIN_PATH . '/manage/%flag'] = array(
  154. 'load arguments' => array(TRUE), // Allow for disabled flags.
  155. 'page callback' => 'drupal_get_form',
  156. 'page arguments' => array('flag_form', FLAG_ADMIN_PATH_START + 1),
  157. 'access callback' => 'user_access',
  158. 'access arguments' => array('administer flags'),
  159. 'file' => 'includes/flag.admin.inc',
  160. 'type' => MENU_CALLBACK,
  161. // Make the flag title the default title for descendant menu items.
  162. 'title callback' => '_flag_menu_title',
  163. 'title arguments' => array(FLAG_ADMIN_PATH_START + 1),
  164. );
  165. $items[FLAG_ADMIN_PATH . '/manage/%flag/edit'] = array(
  166. 'load arguments' => array(TRUE), // Allow for disabled flags.
  167. 'title' => 'Edit flag',
  168. 'type' => MENU_DEFAULT_LOCAL_TASK,
  169. 'weight' => -10,
  170. );
  171. $items[FLAG_ADMIN_PATH . '/manage/%flag/export'] = array(
  172. 'title' => 'Export',
  173. 'page callback' => 'drupal_get_form',
  174. 'page arguments' => array('flag_export_form', FLAG_ADMIN_PATH_START + 1),
  175. 'access arguments' => array('administer flags'),
  176. 'file' => 'includes/flag.export.inc',
  177. 'type' => MENU_LOCAL_TASK,
  178. 'weight' => 20,
  179. );
  180. $items[FLAG_ADMIN_PATH . '/manage/%flag/delete'] = array(
  181. 'title' => 'Delete flag',
  182. 'page callback' => 'drupal_get_form',
  183. 'page arguments' => array('flag_delete_confirm', FLAG_ADMIN_PATH_START + 1),
  184. 'access callback' => 'user_access',
  185. 'access arguments' => array('administer flags'),
  186. 'file' => 'includes/flag.admin.inc',
  187. 'type' => MENU_CALLBACK,
  188. );
  189. $items[FLAG_ADMIN_PATH . '/manage/%flag/update'] = array(
  190. 'load arguments' => array(TRUE), // Allow for disabled flags.
  191. 'title' => 'Update',
  192. 'page callback' => 'flag_update_page',
  193. 'page arguments' => array(FLAG_ADMIN_PATH_START + 1),
  194. 'access arguments' => array('administer flags'),
  195. 'file' => 'includes/flag.export.inc',
  196. 'type' => MENU_CALLBACK,
  197. );
  198. $items['flag/%/%flag/%'] = array(
  199. 'title' => 'Flag',
  200. 'page callback' => 'flag_page',
  201. 'page arguments' => array(1, 2, 3),
  202. 'access callback' => 'user_access',
  203. 'access arguments' => array('access content'),
  204. 'file' => 'includes/flag.pages.inc',
  205. 'type' => MENU_CALLBACK,
  206. );
  207. $items['flag/confirm/%/%flag/%'] = array(
  208. 'title' => 'Flag confirm',
  209. 'page callback' => 'drupal_get_form',
  210. 'page arguments' => array('flag_confirm', 2, 3, 4),
  211. 'access callback' => 'user_access',
  212. 'access arguments' => array('access content'),
  213. 'file' => 'includes/flag.pages.inc',
  214. 'type' => MENU_CALLBACK,
  215. );
  216. return $items;
  217. }
  218. /**
  219. * Implements hook_admin_menu_map().
  220. */
  221. function flag_admin_menu_map() {
  222. if (!user_access('administer flags')) {
  223. return;
  224. }
  225. $map = array();
  226. $map[FLAG_ADMIN_PATH . '/manage/%flag'] = array(
  227. 'parent' => FLAG_ADMIN_PATH,
  228. 'arguments' => array(
  229. array(
  230. '%flag' => array_keys(flag_get_flags()),
  231. ),
  232. ),
  233. );
  234. if (module_exists('field_ui')) {
  235. foreach (entity_get_info() as $obj_type => $info) {
  236. if ($obj_type == 'flagging') {
  237. foreach ($info['bundles'] as $bundle_name => $bundle_info) {
  238. if (isset($bundle_info['admin'])) {
  239. $fields = array();
  240. foreach (field_info_instances($obj_type, $bundle_name) as $field) {
  241. $fields[] = $field['field_name'];
  242. }
  243. $arguments = array(
  244. '%flag' => array($bundle_name),
  245. '%field_ui_menu' => $fields,
  246. );
  247. $path = $bundle_info['admin']['path'];
  248. $map["$path/fields/%field_ui_menu"]['parent'] = "$path/fields";
  249. $map["$path/fields/%field_ui_menu"]['arguments'][] = $arguments;
  250. }
  251. }
  252. }
  253. }
  254. }
  255. return $map;
  256. }
  257. /**
  258. * Menu loader for '%flag' arguments.
  259. *
  260. * @param $include_disabled
  261. * Whether to return a disabled flag too. Normally only enabled flags are
  262. * returned. Some menu items operate on disabled flags and in this case
  263. * you need to turn on this switch by doing
  264. * <code>'load arguments' => array(TRUE)</code> in your menu router.
  265. *
  266. * @return
  267. * Either the flag object, or FALSE if none was found.
  268. */
  269. function flag_load($flag_name, $include_disabled = FALSE) {
  270. if (($flag = flag_get_flag($flag_name))) {
  271. return $flag;
  272. }
  273. else {
  274. // No enabled flag was found. Search among the disabled ones.
  275. if ($include_disabled) {
  276. $default_flags = flag_get_default_flags(TRUE);
  277. if (isset($default_flags[$flag_name])) {
  278. return $default_flags[$flag_name];
  279. }
  280. }
  281. }
  282. // A menu loader has to return FALSE (not NULL) when no object is found.
  283. return FALSE;
  284. }
  285. /**
  286. * Menu title callback.
  287. */
  288. function _flag_menu_title($flag) {
  289. // The following conditional it to handle a D7 bug (@todo: link).
  290. return $flag ? $flag->get_title() : '';
  291. }
  292. /**
  293. * Implements hook_help().
  294. */
  295. function flag_help($path, $arg) {
  296. switch ($path) {
  297. case FLAG_ADMIN_PATH:
  298. $output = '<p>' . t('This page lists all the <em>flags</em> that are currently defined on this system.') . '</p>';
  299. return $output;
  300. case FLAG_ADMIN_PATH . '/add':
  301. $output = '<p>' . t('Select the type of flag to create. An individual flag can only affect one type of object. This cannot be changed once the flag is created.') . '</p>';
  302. return $output;
  303. case FLAG_ADMIN_PATH . '/manage/%/fields':
  304. // Get the existing link types that provide a flagging form.
  305. $link_types = flag_get_link_types();
  306. $form_link_types = array();
  307. foreach (flag_get_link_types() as $link_type) {
  308. if ($link_type['provides form']) {
  309. $form_link_types[] = '<em>' . $link_type['title'] . '</em>';
  310. }
  311. }
  312. // Get the flag for which we're managing fields.
  313. $flag = menu_get_object('flag', FLAG_ADMIN_PATH_START + 1);
  314. // Common text.
  315. $output = '<p>' . t('Flags can have fields added to them. For example, a "Spam" flag could have a <em>Reason</em> field where a user could type in why he believes the item flagged is spam. A "Bookmarks" flag could have a <em>Folder</em> field into which a user could arrange her bookmarks.') . '</p>';
  316. $output .= '<p>' . t('On this page you can add fields to flags, delete them, and otherwise manage them.') . '</p>';
  317. // Three cases:
  318. if ($flag->link_type == 'form') {
  319. // Case 1: the current link type is the flagging form. Don't tell the
  320. // user anything extra, all is fine.
  321. }
  322. elseif ($link_types[$flag->link_type]['provides form']) {
  323. // Case 2: the current link type shows the form for creation of the
  324. // flagging, but it not the flagging form. Tell the user they can't edit
  325. // existing flagging fields.
  326. $output .= t("Field values may be edited when flaggings are created because this flag's link type shows a form for the flagging. However, to edit field values on existing flaggings, you will need to set your flag to use the <em>Flagging form</em> link type. This is provided by the <em><a href='!flagging-form-url'>Flagging Form</a></em> module.", array(
  327. '!flagging-form-url' => 'http://drupal.org/project/flagging_form',
  328. ));
  329. if (!module_exists('flagging_form')) {
  330. $output .= ' <span class="warning">'
  331. . t("You do not currently have this module enabled.")
  332. . '</span>';
  333. }
  334. $output .= '</p>';
  335. }
  336. else {
  337. // Case 3: the current link type does not allow access to the flagging
  338. // form. Tell the user they should change it.
  339. $output .= '<p class="warning">' . t("To allow users to enter values for fields you will need to <a href='!form-link-type-url'>set your flag</a> to use one of the following link types which allow users to access the flagging form: !link-types-list. (In case a form isn't used, the fields are assigned their default values.)", array(
  340. '!form-link-type-url' => url('admin/structure/flags/manage/' . $flag->name, array('fragment' => 'edit-link-type')),
  341. // The list of labels from link types. These are all defined in code
  342. // in hook_flag_link_type_info() and therefore safe to output raw.
  343. '!link-types-list' => implode(', ', $form_link_types),
  344. )) . '</p>';
  345. $output .= '<p>' . t("Additionally, to edit field values on existing flaggings, you will need to set your flag to use the Flagging form link type. This is provided by the <em><a href='!flagging-form-url'>Flagging Form</a></em> module.", array(
  346. '!flagging-form-url' => 'http://drupal.org/project/flagging_form',
  347. ));
  348. if (!module_exists('flagging_form')) {
  349. $output .= ' <span class="warning">'
  350. . t("You do not currently have this module enabled.")
  351. . '</span>';
  352. }
  353. $output .= '</p>';
  354. }
  355. return $output;
  356. }
  357. }
  358. /**
  359. * Implements hook_init().
  360. */
  361. function flag_init() {
  362. $path = drupal_get_path('module', 'flag');
  363. include_once $path . '/includes/flag.actions.inc';
  364. }
  365. /**
  366. * Implements hook_hook_info().
  367. */
  368. function flag_hook_info() {
  369. $hooks['flag_type_info'] = array(
  370. 'group' => 'flag',
  371. );
  372. $hooks['flag_type_info_alter'] = array(
  373. 'group' => 'flag',
  374. );
  375. $hooks['flag_link_type_info'] = array(
  376. 'group' => 'flag',
  377. );
  378. $hooks['flag_link_type_info_alter'] = array(
  379. 'group' => 'flag',
  380. );
  381. return $hooks;
  382. }
  383. /**
  384. * Returns a flag definition.
  385. */
  386. function flag_fetch_definition($entity_type = NULL) {
  387. $definitions = &drupal_static(__FUNCTION__);
  388. if (!isset($definitions)) {
  389. if ($cache = cache_get('flag_type_info')) {
  390. $definitions = $cache->data;
  391. }
  392. else {
  393. $definitions = module_invoke_all('flag_type_info');
  394. drupal_alter('flag_type_info', $definitions);
  395. cache_set('flag_type_info', $definitions);
  396. }
  397. }
  398. if (isset($entity_type)) {
  399. if (isset($definitions[$entity_type])) {
  400. return $definitions[$entity_type];
  401. }
  402. }
  403. else {
  404. return $definitions;
  405. }
  406. }
  407. /**
  408. * Returns all flag types defined on the system.
  409. */
  410. function flag_get_types() {
  411. $types = &drupal_static(__FUNCTION__);
  412. if (!isset($types)) {
  413. $types = array_keys(flag_fetch_definition());
  414. }
  415. return $types;
  416. }
  417. /**
  418. * Instantiates a new flag handler.
  419. *
  420. * A flag handler is more commonly know as "a flag". A factory method usually
  421. * populates this empty flag with settings loaded from the database.
  422. *
  423. * @param $entity_type
  424. * The entity type to create a flag handler for. This may be FALSE if the
  425. * entity type property could not be found in the flag configuration data.
  426. *
  427. * @return
  428. * A flag handler object. This may be the special class flag_broken is there is
  429. * a problem with the flag.
  430. */
  431. function flag_create_handler($entity_type) {
  432. $definition = flag_fetch_definition($entity_type);
  433. if (isset($definition) && class_exists($definition['handler'])) {
  434. $handler = new $definition['handler'];
  435. }
  436. else {
  437. $handler = new flag_broken;
  438. }
  439. $handler->entity_type = $entity_type;
  440. $handler->construct();
  441. return $handler;
  442. }
  443. /**
  444. * A shortcut function to output the link URL.
  445. */
  446. function _flag_url($path, $fragment = NULL, $absolute = TRUE) {
  447. return url($path, array('fragment' => $fragment, 'absolute' => $absolute));
  448. }
  449. /**
  450. * Implements hook_views_api().
  451. */
  452. function flag_views_api() {
  453. return array(
  454. 'api' => 3.0,
  455. 'path' => drupal_get_path('module', 'flag') . '/includes/views',
  456. );
  457. }
  458. /**
  459. * Implements hook_features_api().
  460. */
  461. function flag_features_api() {
  462. return array(
  463. 'flag' => array(
  464. 'name' => t('Flag'),
  465. 'feature_source' => TRUE,
  466. 'default_hook' => 'flag_default_flags',
  467. 'file' => drupal_get_path('module', 'flag') . '/includes/flag.features.inc',
  468. ),
  469. );
  470. }
  471. /**
  472. * Implements hook_permission().
  473. */
  474. function flag_permission() {
  475. $permissions = array(
  476. 'administer flags' => array(
  477. 'title' => t('Administer flags'),
  478. 'description' => t('Create and edit site-wide flags.'),
  479. ),
  480. 'use flag import' => array(
  481. 'title' => t('Use flag importer'),
  482. 'description' => t('Access the flag import functionality.'),
  483. 'restrict access' => TRUE,
  484. ),
  485. );
  486. $flags = flag_get_flags();
  487. // Provide flag and unflag permissions for each flag.
  488. foreach ($flags as $flag_name => $flag) {
  489. $permissions += $flag->get_permissions();
  490. }
  491. return $permissions;
  492. }
  493. /**
  494. * Implements hook_form_FORM_ID_alter(): user_admin_permissions.
  495. *
  496. * Disable permission on the permissions form that don't make sense for
  497. * anonymous users when Session API module is not enabled.
  498. */
  499. function flag_form_user_admin_permissions_alter(&$form, &$form_state, $form_id) {
  500. if (!module_exists('session_api')) {
  501. $flags = flag_get_flags();
  502. // Disable flag and unflag permission checkboxes for anonymous users.
  503. foreach ($flags as $flag_name => $flag) {
  504. $form['checkboxes'][DRUPAL_ANONYMOUS_RID]["flag $flag_name"]['#disabled'] = TRUE;
  505. $form['checkboxes'][DRUPAL_ANONYMOUS_RID]["unflag $flag_name"]['#disabled'] = TRUE;
  506. }
  507. }
  508. }
  509. /**
  510. * Implements hook_link().
  511. *
  512. * This hook does not exist in Drupal 7, it is called from flag_entity_view().
  513. */
  514. function flag_link($type, $object = NULL, $teaser = FALSE) {
  515. if (!isset($object) || !flag_fetch_definition($type)) {
  516. return;
  517. }
  518. global $user;
  519. // Get all possible flags for this entity type.
  520. $flags = flag_get_flags($type);
  521. foreach ($flags as $flag) {
  522. $entity_id = $flag->get_entity_id($object);
  523. if (!$flag->uses_hook_link($teaser)) {
  524. // Flag is not configured to show its link here.
  525. continue;
  526. }
  527. if (!$flag->access($entity_id) && (!$flag->is_flagged($entity_id) || !$flag->access($entity_id, 'flag'))) {
  528. // User has no permission to use this flag or flag does not apply to this
  529. // entity. The link is not skipped if the user has "flag" access but
  530. // not "unflag" access (this way the unflag denied message is shown).
  531. continue;
  532. }
  533. // The flag links are actually fully rendered theme functions.
  534. // The HTML attribute is set to TRUE to allow whatever the themer desires.
  535. $links['flag-' . $flag->name] = array(
  536. 'title' => $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id),
  537. 'html' => TRUE,
  538. );
  539. }
  540. if (isset($links)) {
  541. return $links;
  542. }
  543. }
  544. /**
  545. * Implements hook_flag_link().
  546. */
  547. function flag_flag_link($flag, $action, $entity_id) {
  548. $token = flag_get_token($entity_id);
  549. return array(
  550. 'href' => 'flag/' . ($flag->link_type == 'confirm' ? 'confirm/' : '') . "$action/$flag->name/$entity_id",
  551. 'query' => drupal_get_destination() + ($flag->link_type == 'confirm' ? array() : array('token' => $token)),
  552. );
  553. }
  554. /**
  555. * Implements hook_field_extra_fields().
  556. */
  557. function flag_field_extra_fields() {
  558. $extra = array();
  559. $flags = flag_get_flags();
  560. foreach ($flags as $name => $flag) {
  561. // Skip flags that aren't on entities.
  562. if (!is_subclass_of($flag, 'flag_entity')) {
  563. continue;
  564. }
  565. foreach ($flag->types as $bundle_name) {
  566. if ($flag->show_on_form) {
  567. $extra[$flag->entity_type][$bundle_name]['form']['flag'] = array(
  568. 'label' => t('Flags'),
  569. 'description' => t('Checkboxes for toggling flags'),
  570. 'weight' => 10
  571. );
  572. }
  573. }
  574. }
  575. return $extra;
  576. }
  577. /**
  578. * Implements hook_form_FORM_ID_alter(): node_type_form.
  579. */
  580. function flag_form_node_type_form_alter(&$form, &$form_state, $form_id) {
  581. global $user;
  582. $flags = flag_get_flags('node', $form['#node_type']->type, $user);
  583. foreach ($flags as $flag) {
  584. if ($flag->show_on_form) {
  585. $var = 'flag_' . $flag->name . '_default';
  586. $form['workflow']['flag'][$var] = array(
  587. '#type' => 'checkbox',
  588. '#title' => $flag->get_label('flag_short', $form['#node_type']->type),
  589. '#default_value' => variable_get($var . '_' . $form['#node_type']->type, 0),
  590. '#return_value' => 1,
  591. );
  592. }
  593. }
  594. if (isset($form['workflow']['flag'])) {
  595. $form['workflow']['flag'] += array(
  596. '#type' => 'item',
  597. '#title' => t('Default flags'),
  598. '#description' => t('Above are the <a href="@flag-url">flags</a> you elected to show on the node editing form. You may specify their initial state here.', array('@flag-url' => url(FLAG_ADMIN_PATH))),
  599. // Make the spacing a bit more compact:
  600. '#prefix' => '<div class="form-checkboxes">',
  601. '#suffix' => '</div>',
  602. );
  603. }
  604. }
  605. /**
  606. * Implements hook_field_attach_form().
  607. *
  608. * Handles the 'show_on_form' flag option.
  609. *
  610. * Warning: will not work on entity types that are not fieldable, as this relies
  611. * on a field module hook.
  612. *
  613. * @see flag_field_attach_submit().
  614. */
  615. function flag_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
  616. // Nodes have their own special form handling: see flag_form_alter().
  617. if ($entity_type == 'node') {
  618. return;
  619. }
  620. list($id) = entity_extract_ids($entity_type, $entity);
  621. // Some modules are being stupid here. Commerce!
  622. if (empty($id)) {
  623. $id = NULL;
  624. }
  625. // Get all possible flags for this entity type.
  626. $flags = flag_get_flags($entity_type);
  627. // Filter out flags which need to be included on the node form.
  628. $flags_in_form = 0;
  629. $flags_visible = 0;
  630. foreach ($flags as $flag) {
  631. if (!$flag->show_on_form) {
  632. continue;
  633. }
  634. // Get the flag status.
  635. // We don't have per-bundle defaults on general entities yet.
  636. $flag_status = $id ? $flag->is_flagged($id) : FALSE;
  637. // If the flag is not global and the user doesn't have access, skip it.
  638. // Global flags have their value set even if the user doesn't have access
  639. // to it, similar to the way "published" and "promote" keep the default
  640. // values even if the user doesn't have "administer nodes" permission.
  641. global $user;
  642. $access = $flag->access($id, $flag_status ? 'unflag' : 'flag');
  643. if (!$access && !$flag->global) {
  644. continue;
  645. }
  646. $form['flag'][$flag->name] = array(
  647. '#type' => 'checkbox',
  648. '#title' => $flag->get_label('flag_short', $id),
  649. '#description' => $flag->get_label('flag_long', $id),
  650. '#default_value' => $flag_status,
  651. '#return_value' => 1,
  652. );
  653. // If the user does not have access to the flag, set as a value.
  654. if (!$access) {
  655. $form['flag'][$flag->name]['#type'] = 'value';
  656. $form['flag'][$flag->name]['#value'] = $flag_status;
  657. }
  658. else {
  659. $flags_visible++;
  660. }
  661. $flags_in_form++;
  662. }
  663. if ($flags_in_form) {
  664. $form['flag'] += array(
  665. '#weight' => 1,
  666. '#tree' => TRUE,
  667. );
  668. }
  669. if ($flags_visible) {
  670. $form['flag'] += array(
  671. '#type' => 'fieldset',
  672. '#title' => t('Flags'),
  673. '#collapsible' => TRUE,
  674. );
  675. }
  676. }
  677. /**
  678. * Implements hook_field_attach_submit().
  679. *
  680. * @see flag_field_attach_form().
  681. */
  682. function flag_field_attach_submit($entity_type, $entity, $form, &$form_state) {
  683. // This is invoked for each flag_field_attach_form(), but possibly more than
  684. // once for a particular form in the case that a form is showing multiple
  685. // entities (field collection, inline entity form). Hence we can't simply
  686. // assume our submitted form values are in $form_state['values']['flag'].
  687. if (isset($form['flag'])) {
  688. $parents = $form['flag']['#parents'];
  689. $flag_values = drupal_array_get_nested_value($form_state['values'], $parents);
  690. // Put the form values in the entity so flag_field_attach_save() can find
  691. // them. We can't call flag() here as new entities have no id yet.
  692. $entity->flag = $flag_values;
  693. }
  694. }
  695. /**
  696. * Implements hook_field_attach_insert().
  697. */
  698. function flag_field_attach_insert($entity_type, $entity) {
  699. if (isset($entity->flag)) {
  700. flag_field_attach_save($entity_type, $entity);
  701. }
  702. }
  703. /**
  704. * Implements hook_field_attach_update().
  705. */
  706. function flag_field_attach_update($entity_type, $entity) {
  707. if (isset($entity->flag)) {
  708. flag_field_attach_save($entity_type, $entity);
  709. }
  710. }
  711. /**
  712. * Shared saving routine between flag_field_attach_insert/update().
  713. *
  714. * @see flag_field_attach_form().
  715. */
  716. function flag_field_attach_save($entity_type, $entity) {
  717. list($id) = entity_extract_ids($entity_type, $entity);
  718. // Get the flag values we stashed in the entity in flag_field_attach_submit().
  719. foreach ($entity->flag as $flag_name => $state) {
  720. flag($state ? 'flag' : 'unflag', $flag_name, $id);
  721. }
  722. }
  723. /**
  724. * Implements hook_form_alter().
  725. */
  726. function flag_form_alter(&$form, &$form_state, $form_id) {
  727. // Alter node forms.
  728. if (isset($form['type']) && isset($form['#node']) && ($form_id == $form['type']['#value'] . '_node_form')) {
  729. global $user;
  730. $nid = !empty($form['nid']['#value']) ? $form['nid']['#value'] : NULL;
  731. $flags = flag_get_flags('node', $form['type']['#value'], $user);
  732. // Filter out flags which need to be included on the node form.
  733. $flags_in_form = 0;
  734. $flags_visible = 0;
  735. foreach ($flags as $name => $flag) {
  736. if (!$flag->show_on_form) {
  737. continue;
  738. }
  739. if (isset($form['#node']->flag[$flag->name])) {
  740. $flag_status = $form['#node']->flag[$flag->name];
  741. }
  742. else {
  743. $flag_status_default = variable_get('flag_' . $flag->name . '_default_' . $form['type']['#value'], 0);
  744. $flag_status = $nid ? $flag->is_flagged($nid) : $flag_status_default;
  745. }
  746. // If the flag is not global and the user doesn't have access, skip it.
  747. // Global flags have their value set even if the user doesn't have access
  748. // to it, similar to the way "published" and "promote" keep the default
  749. // values even if the user doesn't have "administer nodes" permission.
  750. $access = $flag->access($nid, $flag_status ? 'unflag' : 'flag', $user);
  751. if (!$access && !$flag->global) {
  752. continue;
  753. }
  754. // Add the flag checkbox if displaying on the form.
  755. $form['flag'][$flag->name] = array(
  756. '#type' => 'checkbox',
  757. '#title' => $flag->get_label('flag_short', $nid ? $nid : $form['type']['#value']),
  758. '#description' => $flag->get_label('flag_long', $nid ? $nid : $form['type']['#value']),
  759. '#default_value' => $flag_status,
  760. '#return_value' => 1,
  761. '#attributes' => array('title' => $flag->get_title()), // Used by our drupalSetSummary().
  762. );
  763. // If the user does not have access to the flag, set as a value.
  764. if (!$access) {
  765. $form['flag'][$flag->name]['#type'] = 'value';
  766. $form['flag'][$flag->name]['#value'] = $flag_status;
  767. }
  768. else {
  769. $flags_visible++;
  770. }
  771. $flags_in_form++;
  772. }
  773. if ($flags_in_form) {
  774. $form['flag'] += array(
  775. '#weight' => 1,
  776. '#tree' => TRUE,
  777. );
  778. }
  779. if ($flags_visible) {
  780. $form['flag'] += array(
  781. '#type' => 'fieldset',
  782. '#title' => t('Flags'),
  783. '#collapsible' => TRUE,
  784. // Vertical tabs support:
  785. '#group' => 'additional_settings',
  786. '#attributes' => array('class' => array('flag-fieldset')),
  787. '#attached' => array(
  788. 'js' => array(
  789. 'vertical-tabs' => drupal_get_path('module', 'flag') . '/theme/flag-admin.js',
  790. ),
  791. ),
  792. );
  793. }
  794. }
  795. }
  796. /*
  797. * Implements hook_contextual_links_view_alter().
  798. */
  799. function flag_contextual_links_view_alter(&$element, $items) {
  800. if (isset($element['#element']['#entity_type'])) {
  801. $entity_type = $element['#element']['#entity_type'];
  802. // Get the entity out of the element. This requires a bit of legwork.
  803. if (isset($element['#element']['#entity'])) {
  804. // EntityAPI entities will all have the entity in the same place.
  805. $entity = $element['#element']['#entity'];
  806. }
  807. elseif (isset($element['#element']['#' . $entity_type])) {
  808. // Node module at least puts it here.
  809. $entity = $element['#element']['#' . $entity_type];
  810. }
  811. else {
  812. // Give up.
  813. return;
  814. }
  815. // Get all possible flags for this entity type.
  816. $flags = flag_get_flags($entity_type);
  817. foreach ($flags as $name => $flag) {
  818. if (!$flag->show_contextual_link) {
  819. continue;
  820. }
  821. list($entity_id) = entity_extract_ids($entity_type, $entity);
  822. if (!$flag->access($entity_id) && (!$flag->is_flagged($entity_id) || !$flag->access($entity_id, 'flag'))) {
  823. // User has no permission to use this flag or flag does not apply to this
  824. // object. The link is not skipped if the user has "flag" access but
  825. // not "unflag" access (this way the unflag denied message is shown).
  826. continue;
  827. }
  828. $element['#links']['flag-'. $name] = array(
  829. 'title' => $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id),
  830. 'html' => TRUE,
  831. );
  832. }
  833. }
  834. }
  835. /**
  836. * Implements hook_entity_view().
  837. *
  838. * Handles the 'show_on_entity' flag option.
  839. *
  840. * Note this is broken for taxonomy terms. @see http://drupal.org/node/1067120
  841. */
  842. function flag_entity_view($entity, $type, $view_mode, $langcode) {
  843. $links = flag_link($type, $entity, $view_mode == 'teaser');
  844. if (isset($links)) {
  845. $entity->content['links']['flag'] = array(
  846. '#theme' => 'links',
  847. '#links' => $links,
  848. '#attributes' => array('class' => array('links', 'inline')),
  849. );
  850. }
  851. }
  852. /**
  853. * Implements hook_node_insert().
  854. */
  855. function flag_node_insert($node) {
  856. flag_node_save($node);
  857. }
  858. /**
  859. * Implements hook_node_update().
  860. */
  861. function flag_node_update($node) {
  862. flag_node_save($node);
  863. }
  864. /**
  865. * Shared saving routine between flag_node_insert() and flag_node_update().
  866. */
  867. function flag_node_save($node) {
  868. global $user;
  869. // Response to the flag checkboxes added to the form in flag_form_alter().
  870. $remembered = FALSE;
  871. if (isset($node->flag)) {
  872. foreach ($node->flag as $name => $state) {
  873. $flag = flag_get_flag($name);
  874. // Flagging may trigger actions. We want actions to get the current
  875. // node, not a stale database-loaded one:
  876. if (!$remembered) {
  877. $flag->remember_entity($node->nid, $node);
  878. // Actions may modify a node, and we don't want to overwrite this
  879. // modification:
  880. $remembered = TRUE;
  881. }
  882. flag($state ? 'flag' : 'unflag', $name, $node->nid, $user);
  883. }
  884. }
  885. }
  886. /**
  887. * Implements hook_entity_delete().
  888. */
  889. function flag_entity_delete($entity, $type) {
  890. // Node and user flags handle things through the entity type delete hooks.
  891. // @todo: make this configurable in the flag type definition?
  892. if ($type == 'node' || $type == 'user') {
  893. return;
  894. }
  895. list($id) = entity_extract_ids($type, $entity);
  896. _flag_entity_delete($type, $id);
  897. }
  898. /**
  899. * Implements hook_node_delete().
  900. */
  901. function flag_node_delete($node) {
  902. foreach (flag_get_flags('node') as $flag) {
  903. // If the flag is being tracked by translation set and the node is part
  904. // of a translation set, don't delete the flagging record.
  905. // Instead, data will be updated in hook_node_translation_change(), below.
  906. if (!$flag->i18n || empty($node->tnid)) {
  907. _flag_entity_delete('node', $node->nid, $flag->fid);
  908. }
  909. }
  910. }
  911. /**
  912. * Implements hook_node_translation_change().
  913. *
  914. * (Hook provided by translation_helpers module.)
  915. */
  916. function flag_node_translation_change($node) {
  917. if (isset($node->translation_change)) {
  918. // If there is only one node remaining, track by nid rather than tnid.
  919. // Otherwise, use the new tnid.
  920. $entity_id = $node->translation_change['new_tnid'] == 0 ? $node->translation_change['remaining_nid'] : $node->translation_change['new_tnid'];
  921. foreach (flag_get_flags('node') as $flag) {
  922. if ($flag->i18n) {
  923. db_update('flagging')->fields(array('entity_id' => $entity_id))
  924. ->condition('fid', $flag->fid)
  925. ->condition('entity_id', $node->translation_change['old_tnid'])
  926. ->execute();
  927. db_update('flag_counts')->fields(array('entity_id' => $entity_id))
  928. ->condition('fid', $flag->fid)
  929. ->condition('entity_id', $node->translation_change['old_tnid'])
  930. ->execute();
  931. }
  932. }
  933. }
  934. }
  935. /**
  936. * Deletes flagging records for the entity.
  937. *
  938. * @param $entity_type
  939. * The type of the entity being deleted; e.g. 'node' or 'comment'.
  940. * @param $entity_id
  941. * The ID of the entity being deleted.
  942. * @param $fid
  943. * The flag id
  944. */
  945. function _flag_entity_delete($entity_type, $entity_id, $fid = NULL) {
  946. $query_content = db_delete('flagging')
  947. ->condition('entity_type', $entity_type)
  948. ->condition('entity_id', $entity_id);
  949. $query_counts = db_delete('flag_counts')
  950. ->condition('entity_type', $entity_type)
  951. ->condition('entity_id', $entity_id);
  952. if (isset($fid)) {
  953. $query_content->condition('fid', $fid);
  954. $query_counts->condition('fid', $fid);
  955. }
  956. $query_content->execute();
  957. $query_counts->execute();
  958. }
  959. /**
  960. * Implements hook_user_login().
  961. */
  962. function flag_user_login(&$edit, &$account) {
  963. // Migrate anonymous flags to this user's account.
  964. if (module_exists('session_api') && ($sid = flag_get_sid(0))) {
  965. // Get a list of flagging IDs that will be moved over.
  966. $duplicate_flaggings = array();
  967. $flaggings = db_select('flagging', 'fc')
  968. ->fields('fc', array('flagging_id', 'fid', 'entity_id'))
  969. ->condition('uid', 0)
  970. ->condition('sid', $sid)
  971. ->execute()
  972. ->fetchAllAssoc('flagging_id', PDO::FETCH_ASSOC);
  973. // Convert anonymous flaggings to their authenticated account.
  974. foreach ($flaggings as $flagging_id => $flagging) {
  975. // Each update is wrapped in a try block to prevent unique key errors.
  976. // Any duplicate object that was flagged as anonoymous is deleted in the
  977. // subsequent db_delete() call.
  978. try {
  979. db_update('flagging')
  980. ->fields(array(
  981. 'uid' => $account->uid,
  982. 'sid' => 0,
  983. ))
  984. ->condition('flagging_id', $flagging_id)
  985. ->execute();
  986. }
  987. catch (Exception $e) {
  988. $duplicate_flaggings[$flagging_id] = $flagging;
  989. }
  990. }
  991. // Delete any remaining flags this user had as an anonymous user. We use the
  992. // proper unflag action here to make sure the count gets decremented again
  993. // and so that other modules can clean up their tables if needed.
  994. $anonymous_user = drupal_anonymous_user();
  995. foreach ($duplicate_flaggings as $flagging_id => $flagging) {
  996. $flag = flag_get_flag(NULL, $flagging['fid']);
  997. $flag->flag('unflag', $flagging['entity_id'], $anonymous_user, TRUE);
  998. }
  999. // Clean up anonymous cookies.
  1000. FlagCookieStorage::drop();
  1001. }
  1002. }
  1003. /**
  1004. * Implements hook_user_cancel().
  1005. */
  1006. function flag_user_cancel($edit, $account, $method) {
  1007. flag_user_account_removal($account);
  1008. }
  1009. /**
  1010. * Implements hook_user_delete().
  1011. */
  1012. function flag_user_delete($account) {
  1013. flag_user_account_removal($account);
  1014. }
  1015. /**
  1016. * Callback function for user account cancellation or deletion.
  1017. */
  1018. function flag_user_account_removal($account) {
  1019. // Remove flags by this user.
  1020. $query = db_select('flagging', 'fc');
  1021. $query->leftJoin('flag_counts', 'c', 'fc.entity_id = c.entity_id AND fc.entity_type = c.entity_type');
  1022. $result = $query
  1023. ->fields('fc', array('fid', 'entity_id'))
  1024. ->fields('c', array('count'))
  1025. ->condition('fc.uid', $account->uid)
  1026. ->execute();
  1027. foreach ($result as $flag_data) {
  1028. // Only decrement the flag count table if it's greater than 1.
  1029. if ($flag_data->count > 0) {
  1030. $flag_data->count--;
  1031. db_update('flag_counts')
  1032. ->fields(array(
  1033. 'count' => $flag_data->count,
  1034. ))
  1035. ->condition('fid', $flag_data->fid)
  1036. ->condition('entity_id', $flag_data->entity_id)
  1037. ->execute();
  1038. }
  1039. elseif ($flag_data->count == 0) {
  1040. db_delete('flag_counts')
  1041. ->condition('fid', $flag_data->fid)
  1042. ->condition('entity_id', $flag_data->entity_id)
  1043. ->execute();
  1044. }
  1045. }
  1046. db_delete('flagging')
  1047. ->condition('uid', $account->uid)
  1048. ->execute();
  1049. // Remove flags that have been done to this user.
  1050. _flag_entity_delete('user', $account->uid);
  1051. }
  1052. /**
  1053. * Implements hook_user_view().
  1054. */
  1055. function flag_user_view($account, $view_mode) {
  1056. $flags = flag_get_flags('user');
  1057. $flag_items = array();
  1058. foreach ($flags as $flag) {
  1059. if (!$flag->access($account->uid)) {
  1060. // User has no permission to use this flag.
  1061. continue;
  1062. }
  1063. if (!$flag->uses_hook_link(array())) {
  1064. // Flag not set to appear on profile.
  1065. continue;
  1066. }
  1067. $flag_items[$flag->name] = array(
  1068. '#type' => 'user_profile_item',
  1069. '#title' => $flag->get_title($account->uid),
  1070. '#markup' => $flag->theme($flag->is_flagged($account->uid) ? 'unflag' : 'flag', $account->uid),
  1071. '#attributes' => array('class' => array('flag-profile-' . $flag->name)),
  1072. );
  1073. }
  1074. if (!empty($flag_items)) {
  1075. $account->content['flags'] = $flag_items;
  1076. $account->content['flags'] += array(
  1077. '#type' => 'user_profile_category',
  1078. '#title' => t('Actions'),
  1079. '#attributes' => array('class' => array('flag-profile')),
  1080. );
  1081. }
  1082. }
  1083. /**
  1084. * Implements hook_session_api_cleanup().
  1085. *
  1086. * Clear out anonymous user flaggings during Session API cleanup.
  1087. */
  1088. function flag_session_api_cleanup($arg = 'run') {
  1089. // Session API 1.1 version:
  1090. if ($arg == 'run') {
  1091. $query = db_select('flagging', 'fc');
  1092. $query->leftJoin('session_api', 's', 'fc.sid = s.sid');
  1093. $result = $query
  1094. ->fields('fc', array('sid'))
  1095. ->condition('fc.sid', 0, '<>')
  1096. ->isNull('s.sid')
  1097. ->execute();
  1098. foreach ($result as $row) {
  1099. db_delete('flagging')
  1100. ->condition('sid', $row->sid)
  1101. ->execute();
  1102. }
  1103. }
  1104. // Session API 1.2+ version.
  1105. elseif (is_array($arg)) {
  1106. $outdated_sids = $arg;
  1107. db_delete('flagging')->condition('sid', $outdated_sids, 'IN')->execute();
  1108. }
  1109. }
  1110. /**
  1111. * Implements hook_field_attach_delete_bundle().
  1112. *
  1113. * Delete any flags' applicability to the deleted bundle.
  1114. */
  1115. function flag_field_attach_delete_bundle($entity_type, $bundle, $instances) {
  1116. // This query can't use db_delete() because that doesn't support a
  1117. // subquery: see http://drupal.org/node/1267508.
  1118. db_query("DELETE FROM {flag_types} WHERE type = :bundle AND fid IN (SELECT fid FROM {flag} WHERE entity_type = :entity_type)", array(
  1119. ':bundle' => $bundle,
  1120. ':entity_type' => $entity_type,
  1121. ));
  1122. }
  1123. /**
  1124. * Flags or unflags an item.
  1125. *
  1126. * @param $account
  1127. * The user on whose behalf to flag. Leave empty for the current user.
  1128. * @return
  1129. * FALSE if some error occured (e.g., user has no permission, flag isn't
  1130. * applicable to the item, etc.), TRUE otherwise.
  1131. */
  1132. function flag($action, $flag_name, $entity_id, $account = NULL) {
  1133. if (!($flag = flag_get_flag($flag_name))) {
  1134. // Flag does not exist.
  1135. return FALSE;
  1136. }
  1137. return $flag->flag($action, $entity_id, $account);
  1138. }
  1139. /**
  1140. * Implements hook_flag_flag().
  1141. */
  1142. function flag_flag_flag($flag, $entity_id, $account, $flagging) {
  1143. if (module_exists('trigger')) {
  1144. flag_flag_trigger('flag', $flag, $entity_id, $account, $flagging);
  1145. }
  1146. }
  1147. /**
  1148. * Implements hook_flag_unflag().
  1149. */
  1150. function flag_flag_unflag($flag, $entity_id, $account, $flagging) {
  1151. if (module_exists('trigger')) {
  1152. flag_flag_trigger('unflag', $flag, $entity_id, $account, $flagging);
  1153. }
  1154. }
  1155. /**
  1156. * Trigger actions if any are available. Helper for hook_flag_(un)flag().
  1157. *
  1158. * @param $op
  1159. * The operation being performed: one of 'flag' or 'unflag'.
  1160. * @param $flag
  1161. * The flag object.
  1162. * @param $entity_id
  1163. * The id of the entity the flag is on.
  1164. * @param $account
  1165. * The user account performing the action.
  1166. * @param $flagging_id
  1167. * The flagging entity.
  1168. */
  1169. function flag_flag_trigger($action, $flag, $entity_id, $account, $flagging) {
  1170. $context['hook'] = 'flag';
  1171. $context['account'] = $account;
  1172. $context['flag'] = $flag;
  1173. $context['op'] = $action;
  1174. // We add to the $context all the objects we know about:
  1175. $context = array_merge($flag->get_relevant_action_objects($entity_id), $context);
  1176. // The primary object the actions work on.
  1177. $object = $flag->fetch_entity($entity_id);
  1178. // Generic "all flags" actions.
  1179. foreach (trigger_get_assigned_actions('flag_' . $action) as $aid => $action_info) {
  1180. // The 'if ($aid)' is a safeguard against http://drupal.org/node/271460#comment-886564
  1181. if ($aid) {
  1182. actions_do($aid, $object, $context);
  1183. }
  1184. }
  1185. // Actions specifically for this flag.
  1186. foreach (trigger_get_assigned_actions('flag_' . $action . '_' . $flag->name) as $aid => $action_info) {
  1187. if ($aid) {
  1188. actions_do($aid, $object, $context);
  1189. }
  1190. }
  1191. }
  1192. /**
  1193. * Implements hook_flag_access().
  1194. */
  1195. function flag_flag_access($flag, $entity_id, $action, $account) {
  1196. // Do nothing if there is no restriction by authorship.
  1197. if (empty($flag->access_author)) {
  1198. return;
  1199. }
  1200. // Restrict access by authorship. It's important that TRUE is never returned
  1201. // here, otherwise we'd grant permission even if other modules denied access.
  1202. if ($flag->entity_type == 'node') {
  1203. // For non-existent nodes (such as on the node add form), assume that the
  1204. // current user is creating the content.
  1205. if (empty($entity_id) || !($node = node_load($entity_id))) {
  1206. return $flag->access_author == 'others' ? FALSE : NULL;
  1207. }
  1208. if ($flag->access_author == 'own' && $node->uid != $account->uid) {
  1209. return FALSE;
  1210. }
  1211. elseif ($flag->access_author == 'others' && $node->uid == $account->uid) {
  1212. return FALSE;
  1213. }
  1214. }
  1215. // Restrict access by comment authorship.
  1216. if ($flag->entity_type == 'comment') {
  1217. // For non-existent comments (such as on the comment add form), assume that
  1218. // the current user is creating the content.
  1219. if (empty($entity_id) || !($comment = $flag->fetch_entity($entity_id))) {
  1220. return $flag->access_author == 'comment_others' ? FALSE : NULL;
  1221. }
  1222. $node = node_load($comment->nid);
  1223. if ($flag->access_author == 'node_own' && $node->uid != $account->uid) {
  1224. return FALSE;
  1225. }
  1226. elseif ($flag->access_author == 'node_others' && $node->uid == $account->uid) {
  1227. return FALSE;
  1228. }
  1229. elseif ($flag->access_author == 'comment_own' && $comment->uid != $account->uid) {
  1230. return FALSE;
  1231. }
  1232. elseif ($flag->access_author == 'comment_others' && $comment->uid == $account->uid) {
  1233. return FALSE;
  1234. }
  1235. }
  1236. }
  1237. /**
  1238. * Implements hook_flag_access_multiple().
  1239. */
  1240. function flag_flag_access_multiple($flag, $entity_ids, $account) {
  1241. $access = array();
  1242. // Do nothing if there is no restriction by authorship.
  1243. if (empty($flag->access_author)) {
  1244. return $access;
  1245. }
  1246. if ($flag->entity_type == 'node') {
  1247. // Restrict access by authorship. This is similar to flag_flag_access()
  1248. // above, but returns an array of 'nid' => $access values. Similarly, we
  1249. // should never return TRUE in any of these access values, only FALSE if we
  1250. // want to deny access, or use the current access value provided by Flag.
  1251. $result = db_select('node', 'n')
  1252. ->fields('n', array('nid', 'uid'))
  1253. ->condition('nid', array_keys($entity_ids), 'IN')
  1254. ->condition('type', $flag->types, 'IN')
  1255. ->execute();
  1256. foreach ($result as $row) {
  1257. if ($flag->access_author == 'own') {
  1258. $access[$row->nid] = $row->uid != $account->uid ? FALSE : NULL;
  1259. }
  1260. elseif ($flag->access_author == 'others') {
  1261. $access[$row->nid] = $row->uid == $account->uid ? FALSE : NULL;
  1262. }
  1263. }
  1264. }
  1265. if ($flag->entity_type == 'comment') {
  1266. // Restrict access by comment ownership.
  1267. $query = db_select('comment', 'c');
  1268. $query->leftJoin('node', 'n', 'c.nid = n.nid');
  1269. $query
  1270. ->fields('c', array('cid', 'nid', 'uid'))
  1271. ->condition('c.cid', $entity_ids, 'IN');
  1272. $query->addField('c', 'uid', 'comment_uid');
  1273. $result = $query->execute();
  1274. foreach ($result as $row) {
  1275. if ($flag->access_author == 'node_own') {
  1276. $access[$row->cid] = $row->node_uid != $account->uid ? FALSE : NULL;
  1277. }
  1278. elseif ($flag->access_author == 'node_others') {
  1279. $access[$row->cid] = $row->node_uid == $account->uid ? FALSE : NULL;
  1280. }
  1281. elseif ($flag->access_author == 'comment_own') {
  1282. $access[$row->cid] = $row->comment_uid != $account->uid ? FALSE : NULL;
  1283. }
  1284. elseif ($flag->access_author == 'comment_others') {
  1285. $access[$row->cid] = $row->comment_uid == $account->uid ? FALSE : NULL;
  1286. }
  1287. }
  1288. }
  1289. // Always return an array (even if empty) of accesses.
  1290. return $access;
  1291. }
  1292. /**
  1293. * Trim a flag to a certain size.
  1294. *
  1295. * @param $fid
  1296. * The flag object.
  1297. * @param $account
  1298. * The user object on behalf the trimming will occur.
  1299. * @param $cutoff_size
  1300. * The number of flaggings allowed. Any flaggings beyond that will be trimmed.
  1301. */
  1302. function flag_trim_flag($flag, $account, $cutoff_size) {
  1303. $result = db_select('flagging', 'fc')
  1304. ->fields('fc')
  1305. ->condition('fid', $flag->fid)
  1306. ->condition(db_or()->condition('uid', $account->uid)->condition('uid', 0))
  1307. ->orderBy('timestamp', 'DESC')
  1308. ->execute();
  1309. $i = 1;
  1310. foreach ($result as $row) {
  1311. if ($i++ > $cutoff_size) {
  1312. flag('unflag', $flag->name, $row->entity_id, $account);
  1313. }
  1314. }
  1315. }
  1316. /**
  1317. * Remove all flagged entities from a flag.
  1318. *
  1319. * @param $flag
  1320. * The flag object.
  1321. * @param $entity_id
  1322. * Optional. The entity ID on which all flaggings will be removed. If left
  1323. * empty, this will remove all of this flag's entities.
  1324. */
  1325. function flag_reset_flag($flag, $entity_id = NULL) {
  1326. $query = db_select('flagging', 'fc')
  1327. ->fields('fc')
  1328. ->condition('fid', $flag->fid);
  1329. if ($entity_id) {
  1330. $query->condition('entity_id', $entity_id);
  1331. }
  1332. $result = $query->execute()->fetchAllAssoc('flagging_id', PDO::FETCH_ASSOC);
  1333. $rows = array();
  1334. foreach ($result as $row) {
  1335. $rows[] = $row;
  1336. }
  1337. module_invoke_all('flag_reset', $flag, $entity_id, $rows);
  1338. $query = db_delete('flagging')->condition('fid' , $flag->fid);
  1339. // Update the flag_counts table.
  1340. $count_query = db_delete('flag_counts')->condition('fid', $flag->fid);
  1341. if ($entity_id) {
  1342. $query->condition('entity_id', $entity_id);
  1343. $count_query->condition('entity_id', $entity_id);
  1344. }
  1345. $count_query->execute();
  1346. return $query->execute();
  1347. }
  1348. /**
  1349. * Implements hook_node_operations().
  1350. *
  1351. * Add additional options on the admin/build/node page.
  1352. */
  1353. function flag_node_operations() {
  1354. global $user;
  1355. $flags = flag_get_flags('node', NULL, $user);
  1356. $operations = array();
  1357. foreach ($flags as $flag) {
  1358. $operations['flag_' . $flag->name] = array(
  1359. 'label' => $flag->get_label('flag_short'),
  1360. 'callback' => 'flag_nodes',
  1361. 'callback arguments' => array('flag', $flag->name),
  1362. 'behavior' => array(),
  1363. );
  1364. $operations['unflag_' . $flag->name] = array(
  1365. 'label' => $flag->get_label('unflag_short'),
  1366. 'callback' => 'flag_nodes',
  1367. 'callback arguments' => array('unflag', $flag->name),
  1368. 'behavior' => array(),
  1369. );
  1370. }
  1371. return $operations;
  1372. }
  1373. /**
  1374. * Callback function for hook_node_operations().
  1375. */
  1376. function flag_nodes($nodes, $action, $flag_name) {
  1377. $performed = FALSE;
  1378. foreach ($nodes as $nid) {
  1379. $performed |= flag($action, $flag_name, $nid);
  1380. }
  1381. if ($performed) {
  1382. drupal_set_message(t('The update has been performed.'));
  1383. }
  1384. }
  1385. /**
  1386. * Implements hook_user_operations().
  1387. */
  1388. function flag_user_operations() {
  1389. global $user;
  1390. $flags = flag_get_flags('user', NULL, $user);
  1391. $operations = array();
  1392. foreach ($flags as $flag) {
  1393. $operations['flag_' . $flag->name] = array(
  1394. 'label' => $flag->get_label('flag_short'),
  1395. 'callback' => 'flag_users',
  1396. 'callback arguments' => array('flag', $flag->name),
  1397. );
  1398. $operations['unflag_' . $flag->name] = array(
  1399. 'label' => $flag->get_label('unflag_short'),
  1400. 'callback' => 'flag_users',
  1401. 'callback arguments' => array('unflag', $flag->name),
  1402. );
  1403. }
  1404. return $operations;
  1405. }
  1406. /**
  1407. * Callback function for hook_user_operations().
  1408. */
  1409. function flag_users($users, $action, $flag_name) {
  1410. foreach ($users as $uid) {
  1411. flag($action, $flag_name, $uid);
  1412. }
  1413. }
  1414. /**
  1415. * Implements hook_mail().
  1416. */
  1417. function flag_mail($key, &$message, $params) {
  1418. switch ($key) {
  1419. case 'over_threshold':
  1420. $message['subject'] = $params['subject'];
  1421. $message['body'] = $params['body'];
  1422. break;
  1423. }
  1424. }
  1425. /**
  1426. * Implements hook_theme().
  1427. */
  1428. function flag_theme() {
  1429. $path = drupal_get_path('module', 'flag') . '/theme';
  1430. return array(
  1431. 'flag' => array(
  1432. 'variables' => array('flag' => NULL, 'action' => NULL, 'entity_id' => NULL, 'after_flagging' => FALSE),
  1433. 'template' => 'flag',
  1434. 'pattern' => 'flag__',
  1435. 'path' => $path,
  1436. ),
  1437. 'flag_tokens_browser' => array(
  1438. 'variables' => array('types' => array('all'), 'global_types' => TRUE),
  1439. 'file' => 'flag.tokens.inc',
  1440. ),
  1441. 'flag_admin_listing' => array(
  1442. 'render element' => 'form',
  1443. 'file' => 'includes/flag.admin.inc',
  1444. ),
  1445. 'flag_admin_listing_disabled' => array(
  1446. 'variables' => array('flags' => NULL, 'default_flags' => NULL),
  1447. 'file' => 'includes/flag.admin.inc',
  1448. ),
  1449. 'flag_admin_page' => array(
  1450. 'variables' => array('flags' => NULL, 'default_flags' => NULL, 'flag_admin_listing' => NULL),
  1451. 'file' => 'includes/flag.admin.inc',
  1452. ),
  1453. 'flag_form_roles' => array(
  1454. 'render element' => 'element',
  1455. 'file' => 'includes/flag.admin.inc',
  1456. ),
  1457. );
  1458. }
  1459. /**
  1460. * A preprocess function for our theme('flag'). It generates the
  1461. * variables needed there.
  1462. *
  1463. * The $variables array initially contains the following arguments:
  1464. * - $flag
  1465. * - $action
  1466. * - $entity_id
  1467. * - $after_flagging
  1468. *
  1469. * See 'flag.tpl.php' for their documentation.
  1470. */
  1471. function template_preprocess_flag(&$variables) {
  1472. global $user;
  1473. $initialized = &drupal_static(__FUNCTION__, array());
  1474. // Some typing shotcuts:
  1475. $flag =& $variables['flag'];
  1476. $action = $variables['action'];
  1477. $entity_id = $variables['entity_id'];
  1478. $flag_css_name = str_replace('_', '-', $flag->name);
  1479. // Generate the link URL.
  1480. $link_type = $flag->get_link_type();
  1481. $link = module_invoke($link_type['module'], 'flag_link', $flag, $action, $entity_id);
  1482. if (isset($link['title']) && empty($link['html'])) {
  1483. $link['title'] = check_plain($link['title']);
  1484. }
  1485. // Replace the link with the access denied text if unable to flag.
  1486. if ($action == 'unflag' && !$flag->access($entity_id, 'unflag')) {
  1487. $link['title'] = $flag->get_label('unflag_denied_text', $entity_id);
  1488. unset($link['href']);
  1489. }
  1490. // Anonymous users always need the JavaScript to maintain their flag state.
  1491. if ($user->uid == 0) {
  1492. $link_type['uses standard js'] = TRUE;
  1493. }
  1494. // Load the JavaScript/CSS, if the link type requires it.
  1495. if (!isset($initialized[$link_type['name']])) {
  1496. if ($link_type['uses standard css']) {
  1497. drupal_add_css(drupal_get_path('module', 'flag') . '/theme/flag.css');
  1498. }
  1499. if ($link_type['uses standard js']) {
  1500. drupal_add_js(drupal_get_path('module', 'flag') . '/theme/flag.js');
  1501. }
  1502. $initialized[$link_type['name']] = TRUE;
  1503. }
  1504. $variables['link'] = $link;
  1505. $variables['link_href'] = isset($link['href']) ? check_url(url($link['href'], $link)) : FALSE;
  1506. $variables['link_text'] = isset($link['title']) ? $link['title'] : $flag->get_label($action . '_short', $entity_id);
  1507. $variables['link_title'] = isset($link['attributes']['title']) ? check_plain($link['attributes']['title']) : check_plain(strip_tags($flag->get_label($action . '_long', $entity_id)));
  1508. $variables['status'] = ($action == 'flag' ? 'unflagged' : 'flagged');
  1509. $variables['flag_name_css'] = $flag_css_name;
  1510. $variables['flag_wrapper_classes_array'] = array();
  1511. $variables['flag_wrapper_classes_array'][] = 'flag-wrapper';
  1512. $variables['flag_wrapper_classes_array'][] = 'flag-' . $flag_css_name;
  1513. $variables['flag_wrapper_classes_array'][] = 'flag-' . $flag_css_name . '-' . $entity_id;
  1514. $variables['flag_wrapper_classes'] = implode(' ', $variables['flag_wrapper_classes_array']);
  1515. $variables['flag_classes_array'] = array();
  1516. $variables['flag_classes_array'][] = 'flag';
  1517. if (isset($link['href'])) {
  1518. $variables['flag_classes_array'][] = $variables['action'] . '-action';
  1519. $variables['flag_classes_array'][] = 'flag-link-' . $flag->link_type;
  1520. }
  1521. else {
  1522. $variables['flag_classes_array'][] = $variables['action'] . '-disabled';
  1523. }
  1524. if (isset($link['attributes']['class'])) {
  1525. $link['attributes']['class'] = is_string($link['attributes']['class']) ? array_filter(explode(' ', $link['attributes']['class'])) : $link['attributes']['class'];
  1526. $variables['flag_classes_array'] = array_merge($variables['flag_classes_array'], $link['attributes']['class']);
  1527. }
  1528. if ($variables['after_flagging']) {
  1529. $inverse_action = ($action == 'flag' ? 'unflag' : 'flag');
  1530. $variables['message_text'] = $flag->get_label($inverse_action . '_message', $entity_id);
  1531. $variables['flag_classes_array'][] = $variables['status'];
  1532. }
  1533. $variables['flag_classes'] = implode(' ', $variables['flag_classes_array']);
  1534. // Backward compatibility: The following section preserves some deprecated
  1535. // variables either to make old flag.tpl.php files continue to work, or to
  1536. // prevent PHP from generating E_NOTICEs there. @todo: Remove these sometime.
  1537. $variables['setup'] = FALSE;
  1538. $variables['last_action'] = $variables['status'];
  1539. }
  1540. /**
  1541. * Return an array of flag names keyed by fid.
  1542. */
  1543. function _flag_get_flag_names() {
  1544. $flags = flag_get_flags();
  1545. $flag_names = array();
  1546. foreach ($flags as $flag) {
  1547. $flag_names[$flag->fid] = $flag->name;
  1548. }
  1549. return $flag_names;
  1550. }
  1551. /**
  1552. * Return an array of flag link types suitable for a select list or radios.
  1553. */
  1554. function _flag_link_type_options() {
  1555. $options = array();
  1556. $types = flag_get_link_types();
  1557. foreach ($types as $type_name => $type) {
  1558. $options[$type_name] = $type['title'];
  1559. }
  1560. return $options;
  1561. }
  1562. /**
  1563. * Return an array of flag link type descriptions.
  1564. */
  1565. function _flag_link_type_descriptions() {
  1566. $options = array();
  1567. $types = flag_get_link_types();
  1568. foreach ($types as $type_name => $type) {
  1569. $options[$type_name] = $type['description'];
  1570. }
  1571. return $options;
  1572. }
  1573. /**
  1574. * Return an array of flag link fields that are dependent on a link type.
  1575. */
  1576. function _flag_link_type_fields() {
  1577. $options = array();
  1578. $types = flag_get_link_types();
  1579. foreach ($types as $type_name => $type) {
  1580. $options[$type_name] = array_keys($type['options']);
  1581. }
  1582. return $options;
  1583. }
  1584. // ---------------------------------------------------------------------------
  1585. // Non-Views public API
  1586. /**
  1587. * Get flag counts for all flags on a node.
  1588. *
  1589. * @param $entity_type
  1590. * The entity type (usually 'node').
  1591. * @param $entity_id
  1592. * The entity ID (usually the node ID).
  1593. * @param $reset
  1594. * Reset the internal cache and execute the SQL query another time.
  1595. *
  1596. * @return $flags
  1597. * An array of the structure [name] => [number of flags].
  1598. */
  1599. function flag_get_counts($entity_type, $entity_id, $reset = FALSE) {
  1600. $counts = &drupal_static(__FUNCTION__);
  1601. if ($reset) {
  1602. $counts = array();
  1603. if (!isset($entity_type)) {
  1604. return;
  1605. }
  1606. }
  1607. if (!isset($counts[$entity_type][$entity_id])) {
  1608. $counts[$entity_type][$entity_id] = array();
  1609. $query = db_select('flag', 'f');
  1610. $query->leftJoin('flag_counts', 'fc', 'f.fid = fc.fid');
  1611. $result = $query
  1612. ->fields('f', array('name'))
  1613. ->fields('fc', array('count'))
  1614. ->condition('fc.entity_type', $entity_type)
  1615. ->condition('fc.entity_id', $entity_id)
  1616. ->execute();
  1617. foreach ($result as $row) {
  1618. $counts[$entity_type][$entity_id][$row->name] = $row->count;
  1619. }
  1620. }
  1621. return $counts[$entity_type][$entity_id];
  1622. }
  1623. /**
  1624. * Get the total count of items flagged within a flag.
  1625. *
  1626. * @param $flag_name
  1627. * The flag name for which to retrieve a flag count.
  1628. * @param $reset
  1629. * Reset the internal cache and execute the SQL query another time.
  1630. */
  1631. function flag_get_flag_counts($flag_name, $reset = FALSE) {
  1632. $counts = &drupal_static(__FUNCTION__);
  1633. if ($reset) {
  1634. $counts = array();
  1635. }
  1636. if (!isset($counts[$flag_name])) {
  1637. $flag = flag_get_flag($flag_name);
  1638. $counts[$flag_name] = db_select('flag_counts', 'fc')
  1639. ->fields('fc', array('flagging_id'))
  1640. ->condition('fid', $flag->fid)
  1641. ->countQuery()
  1642. ->execute()
  1643. ->fetchField();
  1644. }
  1645. return $counts[$flag_name];
  1646. }
  1647. /**
  1648. * Load a single flag either by name or by flag ID.
  1649. *
  1650. * @param $name
  1651. * (optional) The flag name.
  1652. * @param $fid
  1653. * (optional) The the flag id.
  1654. *
  1655. * @return
  1656. * The flag object, or FALSE if no matching flag was found.
  1657. */
  1658. function flag_get_flag($name = NULL, $fid = NULL) {
  1659. $flags = flag_get_flags();
  1660. if (isset($name)) {
  1661. if (isset($flags[$name])) {
  1662. return $flags[$name];
  1663. }
  1664. }
  1665. elseif (isset($fid)) {
  1666. foreach ($flags as $flag) {
  1667. if ($flag->fid == $fid) {
  1668. return $flag;
  1669. }
  1670. }
  1671. }
  1672. return FALSE;
  1673. }
  1674. /**
  1675. * List all flags available.
  1676. *
  1677. * If node type or account are entered, a list of all possible flags will be
  1678. * returned.
  1679. *
  1680. * @param $entity_type
  1681. * Optional. The type of entity for which to load the flags. Usually 'node'.
  1682. * @param $content_subtype
  1683. * Optional. The node type for which to load the flags.
  1684. * @param $account
  1685. * Optional. The user accont to filter available flags. If not set, all
  1686. * flags for will this node will be returned.
  1687. *
  1688. * @return $flags
  1689. * An array of the structure [fid] = flag_object.
  1690. */
  1691. function flag_get_flags($entity_type = NULL, $content_subtype = NULL, $account = NULL) {
  1692. $flags = &drupal_static(__FUNCTION__);
  1693. // Retrieve a list of all flags, regardless of the parameters.
  1694. if (!isset($flags)) {
  1695. $flags = array();
  1696. // Database flags.
  1697. $query = db_select('flag', 'f');
  1698. $query->leftJoin('flag_types', 'fn', 'fn.fid = f.fid');
  1699. $result = $query
  1700. ->fields('f', array('fid', 'entity_type', 'name', 'title', 'global', 'options'))
  1701. ->fields('fn', array('type'))
  1702. ->execute();
  1703. foreach ($result as $row) {
  1704. if (!isset($flags[$row->name])) {
  1705. $flags[$row->name] = flag_flag::factory_by_row($row);
  1706. }
  1707. else {
  1708. $flags[$row->name]->types[] = $row->type;
  1709. }
  1710. }
  1711. // Add code-based flags provided by modules.
  1712. $default_flags = flag_get_default_flags();
  1713. foreach ($default_flags as $name => $default_flag) {
  1714. // Insert new enabled flags into the database to give them an FID.
  1715. if ($default_flag->status && !isset($flags[$name])) {
  1716. $default_flag->save();
  1717. $flags[$name] = $default_flag;
  1718. }
  1719. if (isset($flags[$name])) {
  1720. // Ensure overridden flags are associated with their parent module.
  1721. $flags[$name]->module = $default_flag->module;
  1722. // Update the flag with any properties that are "locked" by the code version.
  1723. if (isset($default_flag->locked)) {
  1724. $flags[$name]->locked = $default_flag->locked;
  1725. foreach ($default_flag->locked as $property) {
  1726. $flags[$name]->$property = $default_flag->$property;
  1727. }
  1728. }
  1729. }
  1730. }
  1731. // Sort the list of flags by weight.
  1732. uasort($flags, '_flag_compare_weight');
  1733. foreach ($flags as $flag) {
  1734. // Allow modules implementing hook_flag_alter(&$flag) to modify each flag.
  1735. drupal_alter('flag', $flag);
  1736. }
  1737. }
  1738. // Make a variable copy to filter types and account.
  1739. $filtered_flags = $flags;
  1740. // Filter out flags based on type and subtype.
  1741. if (isset($entity_type) || isset($content_subtype)) {
  1742. foreach ($filtered_flags as $name => $flag) {
  1743. if (!_flag_entity_enabled($flag, $entity_type, $content_subtype)) {
  1744. unset($filtered_flags[$name]);
  1745. }
  1746. }
  1747. }
  1748. // Filter out flags based on account permissions.
  1749. if (isset($account) && $account->uid != 1) {
  1750. foreach ($filtered_flags as $name => $flag) {
  1751. // We test against the 'flag' action, which is the minimum permission to
  1752. // use a flag.
  1753. if (!$flag->user_access('flag', $account)) {
  1754. unset($filtered_flags[$name]);
  1755. }
  1756. }
  1757. }
  1758. return $filtered_flags;
  1759. }
  1760. /**
  1761. * Comparison function for uasort().
  1762. */
  1763. function _flag_compare_weight($flag1, $flag2) {
  1764. if ($flag1->weight == $flag2->weight) {
  1765. return 0;
  1766. }
  1767. return $flag1->weight < $flag2->weight ? -1 : 1;
  1768. }
  1769. /**
  1770. * Utility function: Checks whether a flag applies to a certain type, and
  1771. * possibly subtype, of entity.
  1772. *
  1773. * @param $entity_type
  1774. * The type of entity being checked, usually "node".
  1775. * @param $content_subtype
  1776. * The subtype (node type) being checked.
  1777. *
  1778. * @return
  1779. * TRUE if the flag is enabled for this type and subtype.
  1780. */
  1781. function _flag_entity_enabled($flag, $entity_type, $content_subtype = NULL) {
  1782. $return = ($flag->entity_type == $entity_type) &&
  1783. (!isset($content_subtype) || !count($flag->types) || in_array($content_subtype, $flag->types));
  1784. return $return;
  1785. }
  1786. /**
  1787. * Retrieve a list of flags defined by modules.
  1788. *
  1789. * @param $include_disabled
  1790. * Unless specified, only enabled flags will be returned.
  1791. * @return
  1792. * An array of flag prototypes, not usable for flagging. Use flag_get_flags()
  1793. * if needing to perform a flagging with any enabled flag.
  1794. */
  1795. function flag_get_default_flags($include_disabled = FALSE) {
  1796. $default_flags = array();
  1797. $flag_status = variable_get('flag_default_flag_status', array());
  1798. foreach (module_implements('flag_default_flags') as $module) {
  1799. $function = $module . '_flag_default_flags';
  1800. foreach ($function() as $flag_name => $flag_info) {
  1801. // Backward compatibility: old exported default flags have their names
  1802. // in $flag_info instead, so we use the += operator to not overwrite it.
  1803. $flag_info += array(
  1804. 'name' => $flag_name,
  1805. 'module' => $module,
  1806. );
  1807. $flag = flag_flag::factory_by_array($flag_info);
  1808. // Disable flags that are not at the current API version.
  1809. if (!$flag->is_compatible()) {
  1810. $flag->status = FALSE;
  1811. }
  1812. // Add flags that have been enabled.
  1813. if ((!isset($flag_status[$flag->name]) && (!isset($flag->status) || $flag->status)) || !empty($flag_status[$flag->name])) {
  1814. $flag->status = TRUE;
  1815. $default_flags[$flag->name] = $flag;
  1816. }
  1817. // Add flags that have been disabled.
  1818. elseif ($include_disabled) {
  1819. $flag->status = FALSE;
  1820. $default_flags[$flag->name] = $flag;
  1821. }
  1822. }
  1823. }
  1824. return $default_flags;
  1825. }
  1826. /**
  1827. * Get all flagged entities in a flag.
  1828. *
  1829. * @param
  1830. * The flag name for which to retrieve flagged entites.
  1831. */
  1832. function flag_get_flag_flagging_data($flag_name) {
  1833. $return = array();
  1834. $flag = flag_get_flag($flag_name);
  1835. $result = db_select('flagging', 'fc')
  1836. ->fields('fc')
  1837. ->condition('fid', $flag->fid)
  1838. ->execute();
  1839. return $result->fetchAllAssoc('flagging_id');
  1840. }
  1841. /**
  1842. * Get entity ID from a flag content ID.
  1843. *
  1844. * @param $flagging_id
  1845. * The flag content ID for which to look up the entity ID.
  1846. */
  1847. function flag_get_entity_id($flagging_id) {
  1848. return db_select('flagging', 'fc')
  1849. ->fields('fc', array('entity_id'))
  1850. ->condition('flagging_id', $flagging_id)
  1851. ->execute()
  1852. ->fetchField();
  1853. }
  1854. /**
  1855. * Find what a user has flagged, either a single node or on the entire site.
  1856. *
  1857. * @param $entity_type
  1858. * The type of entity that will be retrieved. Usually 'node'.
  1859. * @param $entity_id
  1860. * Optional. The entity ID to check for flagging. If none given, all
  1861. * entities flagged by this user will be returned.
  1862. * @param $uid
  1863. * Optional. The user ID whose flags we're checking. If none given, the
  1864. * current user will be used.
  1865. * @param $sid
  1866. * Optional. The user SID (provided by Session API) whose flags we're
  1867. * checking. If none given, the current user will be used. The SID is 0 for
  1868. * logged in users.
  1869. * @param $reset
  1870. * Reset the internal cache and execute the SQL query another time.
  1871. *
  1872. * @return $flags
  1873. * If returning a single item's flags (that is, when $entity_id isn't NULL),
  1874. * an array of the structure
  1875. * [flag_name] => (flagging_id => [flagging_id], uid => [uid], entity_id => [entity_id], timestamp => [timestamp], ...)
  1876. *
  1877. * If returning all items' flags, an array of arrays for each flag:
  1878. * [flag_name] => [entity_id] => Object from above.
  1879. *
  1880. */
  1881. function flag_get_user_flags($entity_type, $entity_id = NULL, $uid = NULL, $sid = NULL, $reset = FALSE) {
  1882. $flagged_content = &drupal_static(__FUNCTION__);
  1883. if ($reset) {
  1884. $flagged_content = array();
  1885. if (!isset($entity_type)) {
  1886. return;
  1887. }
  1888. }
  1889. $uid = !isset($uid) ? $GLOBALS['user']->uid : $uid;
  1890. $sid = !isset($sid) ? flag_get_sid($uid) : $sid;
  1891. if (isset($entity_id)) {
  1892. if (!isset($flagged_content[$uid][$sid][$entity_type][$entity_id])) {
  1893. $flag_names = _flag_get_flag_names();
  1894. $flagged_content[$uid][$sid][$entity_type][$entity_id] = array();
  1895. $result = db_select('flagging', 'fc')
  1896. ->fields('fc')
  1897. ->condition('entity_type', $entity_type)
  1898. ->condition('entity_id', $entity_id)
  1899. ->condition(db_or()
  1900. ->condition('uid', $uid)
  1901. ->condition('uid', 0)
  1902. )
  1903. ->condition('sid', $sid)
  1904. ->execute();
  1905. foreach ($result as $flag_content) {
  1906. $flagged_content[$uid][$sid][$entity_type][$entity_id][$flag_names[$flag_content->fid]] = $flag_content;
  1907. }
  1908. }
  1909. return $flagged_content[$uid][$sid][$entity_type][$entity_id];
  1910. }
  1911. else {
  1912. if (!isset($flagged_content[$uid][$sid][$entity_type]['all'])) {
  1913. $flag_names = _flag_get_flag_names();
  1914. $flagged_content[$uid][$sid][$entity_type]['all'] = array();
  1915. $result = db_select('flagging', 'fc')
  1916. ->fields('fc')
  1917. ->condition('entity_type', $entity_type)
  1918. ->condition(db_or()
  1919. ->condition('uid', $uid)
  1920. ->condition('uid', 0)
  1921. )
  1922. ->condition('sid', $sid)
  1923. ->execute();
  1924. foreach ($result as $flag_content) {
  1925. $flagged_content[$uid][$sid][$entity_type]['all'][$flag_names[$flag_content->fid]][$flag_content->entity_id] = $flag_content;
  1926. }
  1927. }
  1928. return $flagged_content[$uid][$sid][$entity_type]['all'];
  1929. }
  1930. }
  1931. /**
  1932. * Return a list of users who have flagged an entity.
  1933. *
  1934. * @param $entity_type
  1935. * The type of entity that will be retrieved. Usually 'node'.
  1936. * @param $entity_id
  1937. * The entity ID to check for flagging.
  1938. * @param $flag_name
  1939. * Optional. The name of a flag if wanting a list specific to a single flag.
  1940. *
  1941. * @return
  1942. * If no flag name is given, an array of flagging data, keyed by the user
  1943. * ID that flagged the entity. Each flagging array is structured as
  1944. * an array of flag information for each flag, keyed by the flag name. If
  1945. * a flag name is specified, only the information for that flag is returned.
  1946. * If no flags were found an empty array is returned.
  1947. */
  1948. function flag_get_entity_flags($entity_type, $entity_id, $flag_name = NULL) {
  1949. $entity_flags = &drupal_static(__FUNCTION__, array());
  1950. if (!isset($entity_flags[$entity_type][$entity_id])) {
  1951. $flag_names = _flag_get_flag_names();
  1952. $result = db_select('flagging', 'fc')
  1953. ->fields('fc')
  1954. ->condition('entity_type', $entity_type)
  1955. ->condition('entity_id', $entity_id)
  1956. ->orderBy('timestamp', 'DESC')
  1957. ->execute();
  1958. $entity_flags[$entity_type][$entity_id] = array();
  1959. foreach ($result as $flag_content) {
  1960. // Build a list of flaggings for all flags by user.
  1961. $entity_flags[$entity_type][$entity_id]['users'][$flag_content->uid][$flag_names[$flag_content->fid]] = $flag_content;
  1962. // Build a list of flaggings for each individual flag.
  1963. $entity_flags[$entity_type][$entity_id]['flags'][$flag_names[$flag_content->fid]][$flag_content->uid] = $flag_content;
  1964. }
  1965. }
  1966. if (empty($entity_flags[$entity_type][$entity_id])) {
  1967. return array();
  1968. }
  1969. if (isset($flag_name)) {
  1970. if (isset($entity_flags[$entity_type][$entity_id]['flags'][$flag_name])) {
  1971. return $entity_flags[$entity_type][$entity_id]['flags'][$flag_name];
  1972. }
  1973. return array();
  1974. }
  1975. return $entity_flags[$entity_type][$entity_id]['users'];
  1976. }
  1977. /**
  1978. * A utility function for outputting a flag link.
  1979. *
  1980. * You should call this function from your template when you want to put the
  1981. * link on the page yourself. For example, you could call this function from
  1982. * your 'node.tpl.php':
  1983. *
  1984. * <?php print flag_create_link('bookmarks', $node->nid); ?>
  1985. *
  1986. * @param $flag_name
  1987. * The "machine readable" name of the flag; e.g. 'bookmarks'.
  1988. * @param $entity_id
  1989. * The entity ID to check for flagging, for example a node ID.
  1990. */
  1991. function flag_create_link($flag_name, $entity_id) {
  1992. $flag = flag_get_flag($flag_name);
  1993. if (!$flag) {
  1994. // Flag does not exist.
  1995. return;
  1996. }
  1997. if (!$flag->access($entity_id) && (!$flag->is_flagged($entity_id) || !$flag->access($entity_id, 'flag'))) {
  1998. // User has no permission to use this flag.
  1999. return;
  2000. }
  2001. return $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id);
  2002. }
  2003. /**
  2004. * Return an array of link types provided by modules.
  2005. *
  2006. * @return
  2007. * An array of link types as defined by hook_flag_link_type_info(). These are keyed
  2008. * by the type name, and each value is an array of properties. In addition to
  2009. * those defined in hook_flag_link_type_info(), the following properties are set:
  2010. * - 'module': The providing module.
  2011. * - 'name': The machine name of the type.
  2012. *
  2013. * @see hook_flag_link_type_info()
  2014. * @see hook_flag_link_type_info_alter()
  2015. */
  2016. function flag_get_link_types() {
  2017. $link_types = &drupal_static(__FUNCTION__);
  2018. if (!isset($link_types)) {
  2019. if ($cache = cache_get('flag_link_type_info')) {
  2020. $link_types = $cache->data;
  2021. }
  2022. else {
  2023. $link_types = array();
  2024. foreach (module_implements('flag_link_type_info') as $module) {
  2025. $module_types = module_invoke($module, 'flag_link_type_info');
  2026. foreach ($module_types as $type_name => $info) {
  2027. $link_types[$type_name] = $info + array(
  2028. 'module' => $module,
  2029. 'name' => $type_name,
  2030. 'title' => '',
  2031. 'description' => '',
  2032. 'options' => array(),
  2033. 'uses standard js' => TRUE,
  2034. 'uses standard css' => TRUE,
  2035. 'provides form' => FALSE,
  2036. );
  2037. }
  2038. }
  2039. drupal_alter('flag_link_type_info', $link_types);
  2040. cache_set('flag_link_type_info', $link_types);
  2041. }
  2042. }
  2043. return $link_types;
  2044. }
  2045. /**
  2046. * Get a private token used to protect links from spoofing - CSRF.
  2047. */
  2048. function flag_get_token($entity_id) {
  2049. // Anonymous users get a less secure token, since it must be the same for all
  2050. // anonymous users on the entire site to work with page caching.
  2051. return ($GLOBALS['user']->uid) ? drupal_get_token($entity_id) : md5(drupal_get_private_key() . $entity_id);
  2052. }
  2053. /**
  2054. * Check to see if a token value matches the specified node.
  2055. */
  2056. function flag_check_token($token, $entity_id) {
  2057. return flag_get_token($entity_id) == $token;
  2058. }
  2059. /**
  2060. * Set the Session ID for a user. Utilizes the Session API module.
  2061. */
  2062. function flag_set_sid($uid = NULL, $create = TRUE) {
  2063. $sids = &drupal_static(__FUNCTION__, array());
  2064. if (!isset($uid)) {
  2065. $uid = $GLOBALS['user']->uid;
  2066. }
  2067. if (!isset($sids[$uid])) {
  2068. if (module_exists('session_api') && session_api_available() && $uid == 0) {
  2069. $sids[$uid] = session_api_get_sid($create);
  2070. }
  2071. else {
  2072. $sids[$uid] = 0;
  2073. }
  2074. }
  2075. return $sids[$uid];
  2076. }
  2077. /**
  2078. * Get the Session ID for a user. Utilizes the Session API module.
  2079. *
  2080. * @param $uid
  2081. * The user ID. If the UID is 0 (anonymous users), then a SID will be
  2082. * returned. SID will always be 0 for any authenticated user.
  2083. * @param $create
  2084. * If the user doesn't yet have a session, should one be created? Defaults
  2085. * to FALSE.
  2086. */
  2087. function flag_get_sid($uid = NULL, $create = FALSE) {
  2088. return flag_set_sid($uid, $create);
  2089. }
  2090. /**
  2091. * Implements hook_ctools_plugin_directory().
  2092. */
  2093. function flag_ctools_plugin_directory($module, $plugin) {
  2094. if ($module == 'ctools' && !empty($plugin)) {
  2095. return "plugins/$plugin";
  2096. }
  2097. }