Projet

Général

Profil

Paste
Télécharger (85,3 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / flag / flag.module @ d719f12f

1
<?php
2

    
3
/**
4
 * @file
5
 * The Flag module.
6
 */
7

    
8
define('FLAG_API_VERSION', 3);
9

    
10
define('FLAG_ADMIN_PATH', 'admin/structure/flags');
11
define('FLAG_ADMIN_PATH_START', 3);
12

    
13
/**
14
 * Implements hook_entity_info().
15
 */
16
function flag_entity_info() {
17
  $return = array(
18
    'flagging' => array(
19
      'label' => t('Flagging'),
20
      'controller class' => 'FlaggingController',
21
      'base table' => 'flagging',
22
      'fieldable' => TRUE,
23
      'entity keys' => array(
24
        'id' => 'flagging_id',
25
        'bundle' => 'flag_name',
26
      ),
27
      // The following tells Field UI how to extract the bundle name from a
28
      // $flag object when we're visiting ?q=admin/.../manage/%flag/fields.
29
      'bundle keys' => array(
30
        'bundle' => 'name',
31
      ),
32
      'bundles' => array(),
33
      // The following tells EntityAPI how to save flaggings, thus allowing use
34
      // of Entity metadata wrappers (if present).
35
      'save callback' => 'flagging_save',
36
      'creation callback' => 'flagging_create',
37
    ),
38
  );
39

    
40
  // Check for our table before we query it. This is a workaround for a core
41
  // bug: https://www.drupal.org/node/1311820
42
  // TODO: Remove this when that bug is fixed.
43
  if (db_table_exists('flag')) {
44
    // Add bundle info but bypass flag_get_flags() as we cannot use it here, as
45
    // it calls entity_get_info().
46
    $result = db_query("SELECT name, title FROM {flag}");
47
    $flag_names = $result->fetchAllKeyed();
48
    foreach ($flag_names as $flag_name => $flag_title) {
49
      $return['flagging']['bundles'][$flag_name] = array(
50
        'label' => $flag_title,
51
        'admin' => array(
52
          'path' => FLAG_ADMIN_PATH . '/manage/%flag',
53
          'real path' => FLAG_ADMIN_PATH . '/manage/' . $flag_name,
54
          'bundle argument' => FLAG_ADMIN_PATH_START + 1,
55
          'access arguments' => array('administer flags'),
56
        ),
57
      );
58
    }
59
  }
60

    
61
  return $return;
62
}
63

    
64
/**
65
 * Loads a flagging entity.
66
 *
67
 * @param int $flagging_id
68
 *   The 'flagging_id' database serial column.
69
 * @param bool $reset
70
 *   Whether to reset the DrupalDefaultEntityController cache.
71
 *
72
 * @return stdClass
73
 *   The entity object, or FALSE if it can't be found.
74
 */
75
function flagging_load($flagging_id, $reset = FALSE) {
76
  // The flag machine name is loaded in by FlaggingController::buildQuery().
77
  $result = entity_load('flagging', array($flagging_id), array(), $reset);
78
  return reset($result);
79
}
80

    
81
/**
82
 * Entity API creation callback.
83
 *
84
 * Creates an unsaved flagging object for use with $flag->flag().
85
 *
86
 * @param array $values
87
 *   An array of values as described by the entity's property info. Only
88
 *   'flag_name' or 'fid' must be specified, since $flag->flag() does the rest.
89
 *
90
 * @return
91
 *   An unsaved flagging object containing the property values.
92
 */
93
function flagging_create($values = array()) {
94
  $flagging = (object) array();
95

    
96
  if (!isset($values['flag_name'])) {
97
    if (isset($values['fid'])) {
98
      // Add flag_name, determined from fid.
99
      $flag = flag_get_flag(NULL, $values['fid']);
100
      $values['flag_name'] = $flag->name;
101
    }
102
  }
103

    
104
  // Apply the given values.
105
  foreach ($values as $key => $value) {
106
    $flagging->$key = $value;
107
  }
108

    
109
  return $flagging;
110
}
111

    
112
/**
113
 * Saves a flagging entity.
114
 *
115
 * For a new flagging, throws an exception is the flag action is not allowed for
116
 * the given combination of flag, entity, and user.
117
 *
118
 * @param $flagging
119
 *   The flagging entity. This may have either flag_name or the flag fid set,
120
 *   and may also omit the uid property to use the current user.
121
 *
122
 * @throws Exception
123
 */
124
function flagging_save($flagging) {
125
  // Get the flag, either way.
126
  if (isset($flagging->flag_name)) {
127
    $flag = flag_get_flag($flagging->flag_name);
128
  }
129
  else {
130
    $flag = flag_get_flag(NULL, $flagging->fid);
131
  }
132

    
133
  if (!$flag) {
134
    throw new Exception('Flag not found for flagging entity.');
135
  }
136

    
137
  // Fill in properties that may be omitted.
138
  $flagging->fid = $flag->fid;
139
  $flagging->flag_name = $flag->name;
140

    
141
  if (!empty($flagging->uid)) {
142
    $account = user_load($flagging->uid);
143
  }
144
  else {
145
    $account = NULL;
146
  }
147

    
148
  $result = $flag->flag('flag', $flagging->entity_id, $account, FALSE, $flagging);
149

    
150
  if (!$result) {
151
    throw new Exception('Flag action not allowed for given flagging entity properties.');
152
  }
153
}
154

    
155
// @todo: Implement flagging_view(). Not extremely useful. I already have it.
156

    
157
// @tood: Discuss: Should flag deleting call flag_reset_flag()? No.
158

    
159
// @todo: flag_reset_flag():
160
// - it should delete the flaggings.
161
// - (it has other issues; see http://drupal.org/node/894992.)
162
// - (is problematic: it might not be possible to delete all data in a single
163
//   page request.)
164

    
165
// @todo: Discuss: Note that almost all functions/identifiers dealing with
166
// flaggings *aren't* prefixed by "flag_". For example:
167
// - The menu argument is %flagging, not %flag_flagging.
168
// - The entity type is "flagging", not "flag_flagging".
169
// On the one hand this succinct version is readable and nice. On the other
170
// hand, it isn't very "correct".
171

    
172
/**
173
 * Implements hook_entity_query_alter().
174
 *
175
 * Replaces bundle condition in EntityFieldQuery on flagging entities
176
 * with query condition on [name] field in [flag] table.
177
 *
178
 * @see flag_query_flagging_flag_names_alter()
179
 */
180
function flag_entity_query_alter(EntityFieldQuery $query) {
181
  $conditions = &$query->entityConditions;
182

    
183
  // Alter only flagging queries with bundle conditions.
184
  if (isset($conditions['entity_type']) && $conditions['entity_type']['value'] == 'flagging' && isset($conditions['bundle'])) {
185
    // Add tag to alter query.
186
    $query->addTag('flagging_flag_names');
187
    // Make value and operator of the bundle condition accessible
188
    // in hook_query_TAG_alter.
189
    $query->addMetaData('flag_name_value', $conditions['bundle']['value']);
190
    $query->addMetaData('flag_name_operator', $conditions['bundle']['operator']);
191
    unset($conditions['bundle']);
192
  }
193
}
194

    
195
/**
196
 * Implements hook_query_TAG_alter() for flagging_flag_names tag.
197
 *
198
 * @see flag_entity_query_alter()
199
 */
200
function flag_query_flagging_flag_names_alter(QueryAlterableInterface $query) {
201
  // Queries with this tag need to have the {flag} table joined on so they can
202
  // have a condition on the flag name.
203
  // However, we need to ensure the {flagging} table is there to join from. Not
204
  // all instances of EntityFieldQuery will add it; for example, with only a
205
  // field condition the entity base table is not added to the SelectQuery.
206
  $tables =& $query->getTables();
207
  if (!isset($tables['flagging'])) {
208
    // All tables that are in the query will be field tables and are equivalent,
209
    // so just join on the first one.
210
    $field_table = reset($tables);
211
    $field_table_alias = $field_table['alias'];
212

    
213
    $query->join('flagging', 'flagging', "$field_table_alias.entity_id = flagging.fid");
214
  }
215

    
216
  // Get value and operator for bundle condition from meta data.
217
  $value = $query->getMetaData('flag_name_value');
218
  $operator = $query->getMetaData('flag_name_operator');
219
  // Join [flag] and [flagging] tables by [fid] and
220
  // apply bundle condition on [flag].[name] field.
221
  $query->join('flag', 'f', 'flagging.fid = f.fid');
222
  $query->condition('f.name', $value, $operator);
223
}
224

    
225
/**
226
 * Implements hook_menu().
227
 */
228
function flag_menu() {
229
  $items[FLAG_ADMIN_PATH] = array(
230
    'title' => 'Flags',
231
    'page callback' => 'flag_admin_page',
232
    'access callback' => 'user_access',
233
    'access arguments' => array('administer flags'),
234
    'description' => 'Configure flags for marking content with arbitrary information (such as <em>offensive</em> or <em>bookmarked</em>).',
235
    'file' => 'includes/flag.admin.inc',
236
  );
237
  $items[FLAG_ADMIN_PATH . '/list'] = array(
238
    'title' => 'List',
239
    'type' => MENU_DEFAULT_LOCAL_TASK,
240
    'weight' => -10,
241
  );
242
  $items[FLAG_ADMIN_PATH . '/add'] = array(
243
    'title' => 'Add flag',
244
    'page callback' => 'flag_add_page',
245
    'access callback' => 'user_access',
246
    'access arguments' => array('administer flags'),
247
    'file' => 'includes/flag.admin.inc',
248
    'type' => MENU_LOCAL_ACTION,
249
    'weight' => 1,
250
  );
251
  $items[FLAG_ADMIN_PATH . '/import'] = array(
252
    'title' => 'Import',
253
    'page callback' => 'drupal_get_form',
254
    'page arguments' => array('flag_import_form'),
255
    'access arguments' => array('use flag import'),
256
    'file' => 'includes/flag.export.inc',
257
    'type' => MENU_LOCAL_ACTION,
258
    'weight' => 2,
259
  );
260
  $items[FLAG_ADMIN_PATH . '/export'] = array(
261
    'title' => 'Export',
262
    'page callback' => 'drupal_get_form',
263
    'page arguments' => array('flag_export_form'),
264
    'access arguments' => array('administer flags'),
265
    'file' => 'includes/flag.export.inc',
266
    'type' => MENU_LOCAL_ACTION,
267
    'weight' => 3,
268
  );
269

    
270
  $items[FLAG_ADMIN_PATH . '/manage/%flag'] = array(
271
    // Allow for disabled flags.
272
    'load arguments' => array(TRUE),
273
    'page callback' => 'drupal_get_form',
274
    'page arguments' => array('flag_form', FLAG_ADMIN_PATH_START + 1),
275
    'access callback' => 'user_access',
276
    'access arguments' => array('administer flags'),
277
    'file' => 'includes/flag.admin.inc',
278
    'type' => MENU_LOCAL_TASK,
279
    // Make the flag title the default title for descendant menu items.
280
    'title callback' => '_flag_menu_title',
281
    'title arguments' => array(FLAG_ADMIN_PATH_START + 1),
282
  );
283
  $items[FLAG_ADMIN_PATH . '/manage/%flag/edit'] = array(
284
    // Allow for disabled flags.
285
    'load arguments' => array(TRUE),
286
    'title' => 'Edit flag',
287
    'type' => MENU_DEFAULT_LOCAL_TASK,
288
    'weight' => -10,
289
  );
290
  $items[FLAG_ADMIN_PATH . '/manage/%flag/export'] = array(
291
    'title' => 'Export',
292
    'page callback' => 'drupal_get_form',
293
    'page arguments' => array('flag_export_form', FLAG_ADMIN_PATH_START + 1),
294
    'access arguments' => array('administer flags'),
295
    'file' => 'includes/flag.export.inc',
296
    'type' => MENU_LOCAL_TASK,
297
    'weight' => 20,
298
  );
299
  $items[FLAG_ADMIN_PATH . '/manage/%flag/delete'] = array(
300
    'title' => 'Delete flag',
301
    'page callback' => 'drupal_get_form',
302
    'page arguments' => array('flag_delete_confirm', FLAG_ADMIN_PATH_START + 1),
303
    'access callback' => 'user_access',
304
    'access arguments' => array('administer flags'),
305
    'file' => 'includes/flag.admin.inc',
306
    'type' => MENU_CALLBACK,
307
  );
308
  $items[FLAG_ADMIN_PATH . '/manage/%flag/update'] = array(
309
    // Allow for disabled flags.
310
    'load arguments' => array(TRUE),
311
    'title' => 'Update',
312
    'page callback' => 'flag_update_page',
313
    'page arguments' => array(FLAG_ADMIN_PATH_START + 1),
314
    'access arguments' => array('administer flags'),
315
    'file' => 'includes/flag.export.inc',
316
    'type' => MENU_CALLBACK,
317
  );
318

    
319
  $items['flag/%/%flag/%'] = array(
320
    'title' => 'Flag',
321
    'page callback' => 'flag_page',
322
    'page arguments' => array(1, 2, 3),
323
    'access callback' => 'flag_page_access',
324
    'access arguments' => array(1, 2, 3),
325
    'file' => 'includes/flag.pages.inc',
326
    'type' => MENU_CALLBACK,
327
  );
328
  $items['flag/confirm/%/%flag/%'] = array(
329
    'title' => 'Flag confirm',
330
    'page callback' => 'drupal_get_form',
331
    'page arguments' => array('flag_confirm', 2, 3, 4),
332
    'access callback' => 'flag_page_access',
333
    'access arguments' => array(2, 3, 4),
334
    'file' => 'includes/flag.pages.inc',
335
    'type' => MENU_CALLBACK,
336
  );
337

    
338
  return $items;
339
}
340

    
341
/**
342
 * Menu access callback for flagging pages.
343
 *
344
 * Same parameters as flag_page().
345
 *
346
 * @see flag_page()
347
 * @see flag_confirm()
348
 */
349
function flag_page_access($action, $flag, $entity_id) {
350
  $access = $flag->access($entity_id, $action);
351
  return $access;
352
}
353

    
354
/**
355
 * Implements hook_admin_menu_map().
356
 */
357
function flag_admin_menu_map() {
358
  if (!user_access('administer flags')) {
359
    return;
360
  }
361

    
362
  $map = array();
363
  $map[FLAG_ADMIN_PATH . '/manage/%flag'] = array(
364
    'parent' => FLAG_ADMIN_PATH,
365
    'arguments' => array(
366
      array(
367
        '%flag' => array_keys(flag_get_flags()),
368
      ),
369
    ),
370
  );
371

    
372
  return $map;
373
}
374

    
375
/**
376
 * Menu loader for '%flag' arguments.
377
 *
378
 * @param string $flag_name
379
 *   The machine name of the flag.
380
 * @param bool $include_disabled
381
 *   (optional) Whether to return a disabled flag too. Normally only enabled
382
 *   flags are returned. Some menu items operate on disabled flags and in this
383
 *   case you need to turn on this switch by doing:
384
 *   @code
385
 *   'load arguments' => array(TRUE)
386
 *   @endcode
387
 *   in your hook_menu().
388
 *
389
 * @return
390
 *   Either the flag object, or FALSE if none was found.
391
 */
392
function flag_load($flag_name, $include_disabled = FALSE) {
393
  if (($flag = flag_get_flag($flag_name))) {
394
    return $flag;
395
  }
396
  else {
397
    // No enabled flag was found. Search among the disabled ones.
398
    if ($include_disabled) {
399
      $default_flags = flag_get_default_flags(TRUE);
400
      if (isset($default_flags[$flag_name])) {
401
        return $default_flags[$flag_name];
402
      }
403
    }
404
  }
405
  // A menu loader has to return FALSE (not NULL) when no object is found.
406
  return FALSE;
407
}
408

    
409
/**
410
 * Menu title callback.
411
 */
412
function _flag_menu_title($flag) {
413
  // The following conditional it to handle a D7 bug (@todo: link).
414
  return $flag ? $flag->get_title() : '';
415
}
416

    
417
/**
418
 * Implements hook_help().
419
 */
420
function flag_help($path, $arg) {
421
  switch ($path) {
422
    case FLAG_ADMIN_PATH:
423
      $output = '<p>' . t('This page lists all the <em>flags</em> that are currently defined on this system.') . '</p>';
424
      return $output;
425

    
426
    case FLAG_ADMIN_PATH . '/add':
427
      $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>';
428
      return $output;
429

    
430
    case FLAG_ADMIN_PATH . '/manage/%/fields':
431
      // Get the existing link types that provide a flagging form.
432
      $link_types = flag_get_link_types();
433
      $form_link_types = array();
434
      foreach (flag_get_link_types() as $link_type) {
435
        if ($link_type['provides form']) {
436
          $form_link_types[] = '<em>' . $link_type['title'] . '</em>';
437
        }
438
      }
439

    
440
      // Get the flag for which we're managing fields.
441
      $flag = menu_get_object('flag', FLAG_ADMIN_PATH_START + 1);
442

    
443
      // Common text.
444
      $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>';
445
      $output .= '<p>' . t('On this page you can add fields to flags, delete them, and otherwise manage them.') . '</p>';
446

    
447
      // Three cases:
448
      if ($flag->link_type == 'form') {
449
        // Case 1: the current link type is the flagging form. Don't tell the
450
        // user anything extra, all is fine.
451
      }
452
      elseif ($link_types[$flag->link_type]['provides form']) {
453
        // Case 2: the current link type shows the form for creation of the
454
        // flagging, but it not the flagging form. Tell the user they can't edit
455
        // existing flagging fields.
456
        $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(
457
          '!flagging-form-url' => 'http://drupal.org/project/flagging_form',
458
        ));
459
        if (!module_exists('flagging_form')) {
460
          $output .= ' <span class="warning">'
461
            . t("You do not currently have this module enabled.")
462
            . '</span>';
463
        }
464
        $output .= '</p>';
465
      }
466
      else {
467
        // Case 3: the current link type does not allow access to the flagging
468
        // form. Tell the user they should change it.
469
        $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(
470
          '!form-link-type-url' => url('admin/structure/flags/manage/' . $flag->name, array('fragment' => 'edit-link-type')),
471
          // The list of labels from link types. These are all defined in code
472
          // in hook_flag_link_type_info() and therefore safe to output raw.
473
          '!link-types-list' => implode(', ', $form_link_types),
474
        )) . '</p>';
475
        $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(
476
          '!flagging-form-url' => 'http://drupal.org/project/flagging_form',
477
        ));
478
        if (!module_exists('flagging_form')) {
479
          $output .= ' <span class="warning">'
480
            . t("You do not currently have this module enabled.")
481
            . '</span>';
482
        }
483
        $output .= '</p>';
484
      }
485

    
486
      return $output;
487
  }
488
}
489

    
490
/**
491
 * Implements hook_init().
492
 */
493
function flag_init() {
494
  module_load_include('inc', 'flag', 'includes/flag.actions');
495
}
496

    
497
/**
498
 * Implements hook_hook_info().
499
 */
500
function flag_hook_info() {
501
  $hooks['flag_type_info'] = array(
502
    'group' => 'flag',
503
  );
504
  $hooks['flag_type_info_alter'] = array(
505
    'group' => 'flag',
506
  );
507
  $hooks['flag_link_type_info'] = array(
508
    'group' => 'flag',
509
  );
510
  $hooks['flag_link_type_info_alter'] = array(
511
    'group' => 'flag',
512
  );
513
  return $hooks;
514
}
515

    
516
/**
517
 * Get a flag type definition.
518
 *
519
 * @param string $entity_type
520
 *   (optional) The entity type to get the definition for, or NULL to return
521
 *   all flag types.
522
 *
523
 * @return
524
 *   The flag type definition array.
525
 *
526
 * @see hook_flag_type_info()
527
 */
528
function flag_fetch_definition($entity_type = NULL) {
529
  $definitions = &drupal_static(__FUNCTION__);
530
  if (!isset($definitions)) {
531
    if ($cache = cache_get('flag_type_info')) {
532
      $definitions = $cache->data;
533
    }
534
    else {
535
      $definitions = module_invoke_all('flag_type_info');
536
      drupal_alter('flag_type_info', $definitions);
537

    
538
      cache_set('flag_type_info', $definitions);
539
    }
540
  }
541

    
542
  if (isset($entity_type)) {
543
    if (isset($definitions[$entity_type])) {
544
      return $definitions[$entity_type];
545
    }
546
  }
547
  else {
548
    return $definitions;
549
  }
550
}
551

    
552
/**
553
 * Returns all flag types defined on the system.
554
 *
555
 * @return
556
 *   An array of flag type names.
557
 */
558
function flag_get_types() {
559
  $types = &drupal_static(__FUNCTION__);
560
  if (!isset($types)) {
561
    $types = array_keys(flag_fetch_definition());
562
  }
563
  return $types;
564
}
565

    
566
/**
567
 * Instantiates a new flag handler.
568
 *
569
 * A flag handler is more commonly know as "a flag". A factory method usually
570
 * populates this empty flag with settings loaded from the database.
571
 *
572
 * @param $entity_type
573
 *  The entity type to create a flag handler for. This may be FALSE if the
574
 *  entity type property could not be found in the flag configuration data.
575
 *
576
 * @return
577
 *  A flag handler object. This may be the special class flag_broken is there is
578
 *  a problem with the flag.
579
 */
580
function flag_create_handler($entity_type) {
581
  $definition = flag_fetch_definition($entity_type);
582
  if (isset($definition) && class_exists($definition['handler'])) {
583
    $handler = new $definition['handler']();
584
  }
585
  else {
586
    $handler = new flag_broken();
587
  }
588
  $handler->entity_type = $entity_type;
589
  $handler->construct();
590
  return $handler;
591
}
592

    
593
/**
594
 * Implements hook_permission().
595
 */
596
function flag_permission() {
597
  $permissions = array(
598
    'administer flags' => array(
599
      'title' => t('Administer flags'),
600
      'description' => t('Create and edit site-wide flags.'),
601
    ),
602
    'use flag import' => array(
603
      'title' => t('Use flag importer'),
604
      'description' => t('Access the flag import functionality.'),
605
      'restrict access' => TRUE,
606
    ),
607
  );
608

    
609
  // Reset static cache to ensure all flag permissions are available.
610
  drupal_static_reset('flag_get_flags');
611
  $flags = flag_get_flags();
612
  // Provide flag and unflag permissions for each flag.
613
  foreach ($flags as $flag_name => $flag) {
614
    $permissions += $flag->get_permissions();
615
  }
616

    
617
  return $permissions;
618
}
619

    
620
/**
621
 * Implements hook_form_FORM_ID_alter(): user_admin_permissions.
622
 *
623
 * Disable permission on the permissions form that don't make sense for
624
 * anonymous users when Session API module is not enabled.
625
 */
626
function flag_form_user_admin_permissions_alter(&$form, &$form_state, $form_id) {
627
  if (!module_exists('session_api')) {
628
    $flags = flag_get_flags();
629
    // Disable flag and unflag permission checkboxes for anonymous users.
630
    foreach ($flags as $flag_name => $flag) {
631
      $form['checkboxes'][DRUPAL_ANONYMOUS_RID]["flag $flag_name"]['#disabled'] = TRUE;
632
      $form['checkboxes'][DRUPAL_ANONYMOUS_RID]["unflag $flag_name"]['#disabled'] = TRUE;
633
    }
634
  }
635
}
636

    
637
/**
638
 * Implements hook_flag_link().
639
 */
640
function flag_flag_link($flag, $action, $entity_id) {
641
  $token = flag_get_token($entity_id);
642
  return array(
643
    'href' => 'flag/' . ($flag->link_type == 'confirm' ? 'confirm/' : '') . "$action/$flag->name/$entity_id",
644
    'query' => drupal_get_destination() + ($flag->link_type == 'confirm' ? array() : array('token' => $token)),
645
  );
646
}
647

    
648
/**
649
 * Implements hook_field_extra_fields().
650
 */
651
function flag_field_extra_fields() {
652
  $extra = array();
653

    
654
  $flags = flag_get_flags();
655
  foreach ($flags as $name => $flag) {
656
    // Skip flags that aren't on entities.
657
    if (!($flag instanceof flag_entity)) {
658
      continue;
659
    }
660

    
661
    $applicable_bundles = $flag->types;
662
    // If the list of bundles is empty, it indicates all bundles apply.
663
    if (empty($applicable_bundles)) {
664
      $entity_info = entity_get_info($flag->entity_type);
665
      $applicable_bundles = array_keys($entity_info['bundles']);
666
    }
667

    
668
    foreach ($applicable_bundles as $bundle_name) {
669
      if ($flag->show_on_form) {
670
        $extra[$flag->entity_type][$bundle_name]['form']['flag'] = array(
671
          'label' => t('Flags'),
672
          'description' => t('Checkboxes for toggling flags'),
673
          'weight' => 10,
674
        );
675
      }
676

    
677
      if ($flag->show_as_field) {
678
        $extra[$flag->entity_type][$bundle_name]['display']['flag_' . $name] = array(
679
          // It would be nicer to use % as the placeholder, but the label is
680
          // run through check_plain() by field_ui_display_overview_form()
681
          // (arguably incorrectly; see http://drupal.org/node/1991292).
682
          'label' => t('Flag: @title', array(
683
            '@title' => $flag->title,
684
          )),
685
          'description' => t('Individual flag link'),
686
          'weight' => 10,
687
        );
688
      }
689
    }
690
  }
691

    
692
  return $extra;
693
}
694

    
695
/**
696
 * Implements hook_form_FORM_ID_alter(): node_type_form.
697
 */
698
function flag_form_node_type_form_alter(&$form, &$form_state, $form_id) {
699
  global $user;
700
  $flags = flag_get_flags('node', $form['#node_type']->type, $user);
701
  foreach ($flags as $flag) {
702
    if ($flag->show_on_form) {
703
      // To be able to process node tokens in flag labels, we create a fake
704
      // node and store it in the flag's cache for replace_tokens() to find,
705
      // with a fake ID.
706
      $flag->remember_entity('fake', (object) array(
707
        'nid' => NULL,
708
        'type' => $form['#node_type']->type,
709
        'title' => '',
710
      ));
711
      $var = 'flag_' . $flag->name . '_default';
712
      $form['workflow']['flag'][$var] = array(
713
        '#type' => 'checkbox',
714
        '#title' => $flag->get_label('flag_short', 'fake'),
715
        '#default_value' => variable_get($var . '_' . $form['#node_type']->type, 0),
716
        '#return_value' => 1,
717
      );
718
    }
719
  }
720

    
721
  if (isset($form['workflow']['flag'])) {
722
    $form['workflow']['flag'] += array(
723
      '#type' => 'item',
724
      '#title' => t('Default flags'),
725
      '#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))),
726
      // Make the spacing a bit more compact:
727
      '#prefix' => '<div class="form-checkboxes">',
728
      '#suffix' => '</div>',
729
    );
730
  }
731
}
732

    
733
/**
734
 * Implements hook_field_attach_form().
735
 *
736
 * Handles the 'show_on_form' flag option.
737
 *
738
 * Warning: will not work on entity types that are not fieldable, as this relies
739
 * on a field module hook.
740
 *
741
 * @see flag_field_attach_submit()
742
 */
743
function flag_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
744
  list($id) = entity_extract_ids($entity_type, $entity);
745
  // Some modules are being stupid here. Commerce!
746
  if (empty($id)) {
747
    $id = NULL;
748
  }
749

    
750
  // Keep track of whether the entity is new or not, as we're about to fiddle
751
  // with the entity id for the flag's entity cache.
752
  $is_existing_entity = !empty($id);
753

    
754
  // Get all possible flags for this entity type.
755
  $flags = flag_get_flags($entity_type);
756

    
757
  // Filter out flags which need to be included on the node form.
758
  $flags_in_form = 0;
759
  $flags_visible = 0;
760
  foreach ($flags as $flag) {
761
    if (!$flag->show_on_form) {
762
      continue;
763
    }
764

    
765
    // Get the flag status.
766
    if ($is_existing_entity) {
767
      $flag_status = $flag->is_flagged($id);
768
    }
769
    else {
770
      // We don't have per-bundle defaults on general entities yet: default
771
      // status is just unflagged.
772
      $flag_status = FALSE;
773
      // Apply the per-bundle defaults for nodes.
774
      if ($entity_type == 'node') {
775
        $node_type = $entity->type;
776
        $flag_status = variable_get('flag_' . $flag->name . '_default_' . $node_type, 0);
777
      }
778

    
779
      // For a new, unsaved entity, make a dummy entity ID so that the flag
780
      // handler can remember the entity. This allows access to the flag to be
781
      // correctly handled in node and comment preview.
782
      $id = 'new';
783
      $flag->remember_entity($id, $entity);
784
    }
785

    
786
    // If the flag is not global and the user doesn't have access, skip it.
787
    // Global flags have their value set even if the user doesn't have access
788
    // to it, similar to the way "published" and "promote" keep the default
789
    // values even if the user doesn't have "administer nodes" permission.
790
    // Furthermore, a global flag is set to its default value on new nodes
791
    // even if the user creating the node doesn't have access to the flag.
792
    global $user;
793
    $access = $flag->access($id, $flag_status ? 'unflag' : 'flag');
794
    if (!$access && !$flag->global) {
795
      continue;
796
    }
797

    
798
    $form['flag'][$flag->name] = array(
799
      '#type' => 'checkbox',
800
      '#title' => $flag->get_label('flag_short', $id),
801
      '#description' => $flag->get_label('flag_long', $id),
802
      '#default_value' => $flag_status,
803
      '#return_value' => 1,
804
      // Used by our drupalSetSummary() on vertical tabs.
805
      '#attributes' => array('title' => $flag->get_title()),
806
    );
807

    
808
    // If the user does not have access to the flag, set as a value.
809
    if (!$access) {
810
      $form['flag'][$flag->name]['#type'] = 'value';
811
      $form['flag'][$flag->name]['#value'] = $flag_status;
812
    }
813
    else {
814
      $flags_visible++;
815
    }
816
    $flags_in_form++;
817
  }
818

    
819
  if ($flags_in_form) {
820
    $form['flag'] += array(
821
      '#weight' => 1,
822
      '#tree' => TRUE,
823
    );
824
  }
825
  if ($flags_visible) {
826
    $form['flag'] += array(
827
      '#type' => 'fieldset',
828
      '#title' => t('Flags'),
829
      '#collapsible' => TRUE,
830
    );
831

    
832
    if ($entity_type == 'node') {
833
      // Turn the fieldset into a vertical tab.
834
      $form['flag'] += array(
835
        '#group' => 'additional_settings',
836
        '#attributes' => array('class' => array('flag-fieldset')),
837
        '#attached' => array(
838
          'js' => array(
839
            'vertical-tabs' => drupal_get_path('module', 'flag') . '/theme/flag-admin.js',
840
          ),
841
        ),
842
      );
843
    }
844
  }
845
}
846

    
847
/**
848
 * Implements hook_field_attach_submit().
849
 *
850
 * @see flag_field_attach_form()
851
 */
852
function flag_field_attach_submit($entity_type, $entity, $form, &$form_state) {
853
  // This is invoked for each flag_field_attach_form(), but possibly more than
854
  // once for a particular form in the case that a form is showing multiple
855
  // entities (field collection, inline entity form). Hence we can't simply
856
  // assume our submitted form values are in $form_state['values']['flag'].
857
  if (isset($form['flag'])) {
858
    $parents = $form['flag']['#parents'];
859
    $flag_values = drupal_array_get_nested_value($form_state['values'], $parents);
860

    
861
    // Put the form values in the entity so flag_field_attach_save() can find
862
    // them. We can't call flag() here as new entities have no id yet.
863
    $entity->flag = $flag_values;
864
  }
865
}
866

    
867
/**
868
 * Implements hook_field_attach_insert().
869
 */
870
function flag_field_attach_insert($entity_type, $entity) {
871
  if (isset($entity->flag)) {
872
    flag_field_attach_save($entity_type, $entity);
873
  }
874
}
875

    
876
/**
877
 * Implements hook_field_attach_update().
878
 */
879
function flag_field_attach_update($entity_type, $entity) {
880
  if (isset($entity->flag)) {
881
    flag_field_attach_save($entity_type, $entity);
882
  }
883
}
884

    
885
/**
886
 * Shared saving routine between flag_field_attach_insert/update().
887
 *
888
 * @see flag_field_attach_form()
889
 */
890
function flag_field_attach_save($entity_type, $entity) {
891
  list($id) = entity_extract_ids($entity_type, $entity);
892
  // Get the flag values we stashed in the entity in flag_field_attach_submit().
893
  foreach ($entity->flag as $flag_name => $state) {
894
    flag($state ? 'flag' : 'unflag', $flag_name, $id);
895
  }
896
}
897

    
898
/*
899
 * Implements hook_contextual_links_view_alter().
900
 */
901
function flag_contextual_links_view_alter(&$element, $items) {
902
  if (isset($element['#element']['#entity_type'])) {
903
    $entity_type = $element['#element']['#entity_type'];
904

    
905
    // Get the entity out of the element. This requires a bit of legwork.
906
    if (isset($element['#element']['#entity'])) {
907
      // EntityAPI entities will all have the entity in the same place.
908
      $entity = $element['#element']['#entity'];
909
    }
910
    elseif (isset($element['#element']['#' . $entity_type])) {
911
      // Node module at least puts it here.
912
      $entity = $element['#element']['#' . $entity_type];
913
    }
914
    else {
915
      // Give up.
916
      return;
917
    }
918

    
919
    // Get all possible flags for this entity type.
920
    $flags = flag_get_flags($entity_type);
921

    
922
    foreach ($flags as $name => $flag) {
923
      if (!$flag->show_contextual_link) {
924
        continue;
925
      }
926

    
927
      list($entity_id) = entity_extract_ids($entity_type, $entity);
928
      if (!$flag->access($entity_id) && (!$flag->is_flagged($entity_id) || !$flag->access($entity_id, 'flag'))) {
929
        // User has no permission to use this flag or flag does not apply to
930
        // this object. The link is not skipped if the user has "flag" access
931
        // but not "unflag" access (this way the unflag denied message is
932
        // shown).
933
        continue;
934
      }
935

    
936
      $element['#links']['flag-' . $name] = array(
937
        'title' => $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id),
938
        'html' => TRUE,
939
      );
940
    }
941
  }
942
}
943

    
944
/**
945
 * Implements hook_entity_view().
946
 *
947
 * Handles the 'show_in_links' and 'show_as_field' flag options.
948
 *
949
 * Note this is broken for taxonomy terms for version of Drupal core < 7.17.
950
 */
951
function flag_entity_view($entity, $type, $view_mode, $langcode) {
952
  // Get all possible flags for this entity type.
953
  $flags = flag_get_flags($type);
954
  foreach ($flags as $flag) {
955
    // Check if the flag outputs on entity view.
956
    if (!($flag->show_as_field || $flag->shows_in_entity_links($view_mode))) {
957
      // Flag is not configured to output on entity view, so skip it to save on
958
      // calls to access checks.
959
      continue;
960
    }
961

    
962
    $entity_id = $flag->get_entity_id($entity);
963
    // For a new, unsaved entity, make a dummy entity ID so that the flag
964
    // handler can remember the entity. This allows access to the flag to be
965
    // correctly handled in node and comment preview.
966
    if (is_null($entity_id)) {
967
      $entity_id = 'new';
968
    }
969
    $flag->remember_entity($entity_id, $entity);
970

    
971
    if (!$flag->access($entity_id) && (!$flag->is_flagged($entity_id) || !$flag->access($entity_id, 'flag'))) {
972
      // User has no permission to use this flag or flag does not apply to this
973
      // entity. The link is not skipped if the user has "flag" access but
974
      // not "unflag" access (this way the unflag denied message is shown).
975
      continue;
976
    }
977

    
978
    // We're good to go. Output the flag in the appropriate manner(s).
979

    
980
    // The old-style entity links output.
981
    if ($flag->shows_in_entity_links($view_mode)) {
982
      // The flag links are actually fully rendered theme functions.
983
      // The HTML attribute is set to TRUE to allow whatever the themer desires.
984
      $links['flag-' . $flag->name] = array(
985
        'title' => $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id),
986
        'html' => TRUE,
987
      );
988
    }
989

    
990
    // The pseudofield output.
991
    if ($flag->show_as_field) {
992
      $entity->content['flag_' . $flag->name] = array(
993
        '#markup' => $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id, array('needs_wrapping_element' => TRUE)),
994
      );
995
    }
996
  }
997

    
998
  // If any links were made, add them to the entity's links array.
999
  if (isset($links)) {
1000
    $entity->content['links']['flag'] = array(
1001
      '#theme' => 'links',
1002
      '#links' => $links,
1003
      '#attributes' => array('class' => array('links', 'inline')),
1004
    );
1005
  }
1006
}
1007

    
1008
/**
1009
 * Implements hook_node_insert().
1010
 */
1011
function flag_node_insert($node) {
1012
  flag_node_save($node);
1013
}
1014

    
1015
/**
1016
 * Implements hook_node_update().
1017
 */
1018
function flag_node_update($node) {
1019
  flag_node_save($node);
1020
}
1021

    
1022
/**
1023
 * Shared saving routine between flag_node_insert() and flag_node_update().
1024
 */
1025
function flag_node_save($node) {
1026
  // Response to the flag checkboxes added to the form in flag_form_alter().
1027
  $remembered = FALSE;
1028
  if (isset($node->flag)) {
1029
    foreach ($node->flag as $name => $state) {
1030
      $flag = flag_get_flag($name);
1031
      // Flagging may trigger actions. We want actions to get the current
1032
      // node, not a stale database-loaded one:
1033
      if (!$remembered) {
1034
        $flag->remember_entity($node->nid, $node);
1035
        // Actions may modify a node, and we don't want to overwrite this
1036
        // modification:
1037
        $remembered = TRUE;
1038
      }
1039

    
1040
      $action = $state ? 'flag' : 'unflag';
1041
      // Pass TRUE for $skip_permission_check so that flags that have been
1042
      // passed through as hidden form values are saved.
1043
      $flag->flag($action, $node->nid, NULL, TRUE);
1044
    }
1045
  }
1046
}
1047

    
1048
/**
1049
 * Implements hook_entity_delete().
1050
 */
1051
function flag_entity_delete($entity, $type) {
1052
  // Node and user flags handle things through the entity type delete hooks.
1053
  // @todo: make this configurable in the flag type definition?
1054
  if ($type == 'node' || $type == 'user') {
1055
    return;
1056
  }
1057

    
1058
  list($id) = entity_extract_ids($type, $entity);
1059
  _flag_entity_delete($type, $id);
1060
}
1061

    
1062
/**
1063
 * Implements hook_node_delete().
1064
 */
1065
function flag_node_delete($node) {
1066
  foreach (flag_get_flags('node') as $flag) {
1067
    // If the flag is being tracked by translation set and the node is part
1068
    // of a translation set, don't delete the flagging record.
1069
    // Instead, data will be updated in hook_node_translation_change(), below.
1070
    if (!$flag->i18n || empty($node->tnid)) {
1071
      _flag_entity_delete('node', $node->nid, $flag->fid);
1072
    }
1073
  }
1074
}
1075

    
1076
/**
1077
 * Implements hook_node_translation_change().
1078
 *
1079
 * (Hook provided by translation_helpers module.)
1080
 */
1081
function flag_node_translation_change($node) {
1082
  if (isset($node->translation_change)) {
1083
    // If there is only one node remaining, track by nid rather than tnid.
1084
    // Otherwise, use the new tnid.
1085
    $entity_id = $node->translation_change['new_tnid'] == 0 ? $node->translation_change['remaining_nid'] : $node->translation_change['new_tnid'];
1086
    foreach (flag_get_flags('node') as $flag) {
1087
      if ($flag->i18n) {
1088
        db_update('flagging')->fields(array('entity_id' => $entity_id))
1089
          ->condition('fid', $flag->fid)
1090
          ->condition('entity_id', $node->translation_change['old_tnid'])
1091
          ->execute();
1092
        db_update('flag_counts')->fields(array('entity_id' => $entity_id))
1093
          ->condition('fid', $flag->fid)
1094
          ->condition('entity_id', $node->translation_change['old_tnid'])
1095
          ->execute();
1096
      }
1097
    }
1098
  }
1099
}
1100

    
1101
/**
1102
 * Deletes flagging records for the entity.
1103
 *
1104
 * @param $entity_type
1105
 *   The type of the entity being deleted; e.g. 'node' or 'comment'.
1106
 * @param $entity_id
1107
 *   The ID of the entity being deleted.
1108
 * @param $fid
1109
 *   The flag id
1110
 */
1111
function _flag_entity_delete($entity_type, $entity_id, $fid = NULL) {
1112
  $query_content = db_delete('flagging')
1113
    ->condition('entity_type', $entity_type)
1114
    ->condition('entity_id', $entity_id);
1115
  $query_counts = db_delete('flag_counts')
1116
    ->condition('entity_type', $entity_type)
1117
    ->condition('entity_id', $entity_id);
1118
  if (isset($fid)) {
1119
    $query_content->condition('fid', $fid);
1120
    $query_counts->condition('fid', $fid);
1121
  }
1122
  $query_content->execute();
1123
  $query_counts->execute();
1124
}
1125

    
1126
/**
1127
 * Implements hook_user_login().
1128
 */
1129
function flag_user_login(&$edit, &$account) {
1130
  // Migrate anonymous flags to this user's account.
1131
  if (module_exists('session_api') && ($sid = flag_get_sid(0))) {
1132
    // Get a list of flagging IDs that will be moved over.
1133
    $duplicate_flaggings = array();
1134
    $flaggings = db_select('flagging', 'fc')
1135
      ->fields('fc', array('flagging_id', 'fid', 'entity_id'))
1136
      ->condition('uid', 0)
1137
      ->condition('sid', $sid)
1138
      ->execute()
1139
      ->fetchAllAssoc('flagging_id', PDO::FETCH_ASSOC);
1140

    
1141
    // Convert anonymous flaggings to their authenticated account.
1142
    foreach ($flaggings as $flagging_id => $flagging) {
1143
      // Each update is wrapped in a try block to prevent unique key errors.
1144
      // Any duplicate object that was flagged as anonoymous is deleted in the
1145
      // subsequent db_delete() call.
1146
      try {
1147
        db_update('flagging')
1148
          ->fields(array(
1149
            'uid' => $account->uid,
1150
            'sid' => 0,
1151
          ))
1152
          ->condition('flagging_id', $flagging_id)
1153
          ->execute();
1154
      }
1155
      catch (Exception $e) {
1156
        $duplicate_flaggings[$flagging_id] = $flagging;
1157
      }
1158
    }
1159

    
1160
    // Delete any remaining flags this user had as an anonymous user. We use the
1161
    // proper unflag action here to make sure the count gets decremented again
1162
    // and so that other modules can clean up their tables if needed.
1163
    $anonymous_user = drupal_anonymous_user();
1164
    foreach ($duplicate_flaggings as $flagging_id => $flagging) {
1165
      $flag = flag_get_flag(NULL, $flagging['fid']);
1166
      $flag->flag('unflag', $flagging['entity_id'], $anonymous_user, TRUE);
1167
    }
1168

    
1169
    // Clean up anonymous cookies.
1170
    FlagCookieStorage::drop();
1171
  }
1172
}
1173

    
1174
/**
1175
 * Implements hook_user_cancel().
1176
 */
1177
function flag_user_cancel($edit, $account, $method) {
1178
  flag_user_account_removal($account);
1179
}
1180

    
1181
/**
1182
 * Implements hook_user_delete().
1183
 */
1184
function flag_user_delete($account) {
1185
  flag_user_account_removal($account);
1186
}
1187

    
1188
/**
1189
 * Shared helper for user account cancellation or deletion.
1190
 */
1191
function flag_user_account_removal($account) {
1192
  // Remove flags by this user.
1193
  $query = db_select('flagging', 'fc');
1194
  $query->leftJoin('flag_counts', 'c', 'fc.entity_id = c.entity_id AND fc.entity_type = c.entity_type AND fc.fid = c.fid');
1195
  $result = $query
1196
    ->fields('fc', array('fid', 'entity_id'))
1197
    ->fields('c', array('count'))
1198
    ->condition('fc.uid', $account->uid)
1199
    ->execute();
1200

    
1201
  foreach ($result as $flag_data) {
1202
    // Only decrement the flag count table if it's greater than 1.
1203
    if ($flag_data->count > 0) {
1204
      $flag_data->count--;
1205
      db_update('flag_counts')
1206
        ->fields(array(
1207
          'count' => $flag_data->count,
1208
        ))
1209
        ->condition('fid', $flag_data->fid)
1210
        ->condition('entity_id', $flag_data->entity_id)
1211
        ->execute();
1212
    }
1213
    elseif ($flag_data->count == 0) {
1214
      db_delete('flag_counts')
1215
        ->condition('fid', $flag_data->fid)
1216
        ->condition('entity_id', $flag_data->entity_id)
1217
        ->execute();
1218
    }
1219
  }
1220
  db_delete('flagging')
1221
    ->condition('uid', $account->uid)
1222
    ->execute();
1223

    
1224
  // Remove flags that have been done to this user.
1225
  _flag_entity_delete('user', $account->uid);
1226
}
1227

    
1228
/**
1229
 * Implements hook_user_view().
1230
 */
1231
function flag_user_view($account, $view_mode) {
1232
  $flags = flag_get_flags('user');
1233
  $flag_items = array();
1234
  foreach ($flags as $flag) {
1235
    if (!$flag->access($account->uid)) {
1236
      // User has no permission to use this flag.
1237
      continue;
1238
    }
1239
    if (!$flag->show_on_profile) {
1240
      // Flag not set to appear on profile.
1241
      continue;
1242
    }
1243
    $flag_items[$flag->name] = array(
1244
      '#type' => 'user_profile_item',
1245
      '#title' => $flag->get_title($account->uid),
1246
      '#markup' => $flag->theme($flag->is_flagged($account->uid) ? 'unflag' : 'flag', $account->uid),
1247
      '#attributes' => array('class' => array('flag-profile-' . $flag->name)),
1248
    );
1249
  }
1250
  if (!empty($flag_items)) {
1251
    $account->content['flags'] = $flag_items;
1252
    $account->content['flags'] += array(
1253
      '#type' => 'user_profile_category',
1254
      '#title' => t('Actions'),
1255
      '#attributes' => array('class' => array('flag-profile')),
1256
    );
1257
  }
1258
}
1259

    
1260
/**
1261
 * Implements hook_session_api_cleanup().
1262
 *
1263
 * Clear out anonymous user flaggings during Session API cleanup.
1264
 */
1265
function flag_session_api_cleanup($arg = 'run') {
1266
  // Session API 1.1 version:
1267
  if ($arg == 'run') {
1268
    $query = db_select('flagging', 'fc');
1269
    $query->leftJoin('session_api', 's', 'fc.sid = s.sid');
1270
    $result = $query
1271
      ->fields('fc', array('sid'))
1272
      ->condition('fc.sid', 0, '<>')
1273
      ->isNull('s.sid')
1274
      ->execute();
1275
    foreach ($result as $row) {
1276
      db_delete('flagging')
1277
        ->condition('sid', $row->sid)
1278
        ->execute();
1279
    }
1280
  }
1281
  // Session API 1.2+ version.
1282
  elseif (is_array($arg)) {
1283
    $outdated_sids = $arg;
1284
    db_delete('flagging')->condition('sid', $outdated_sids, 'IN')->execute();
1285
  }
1286
}
1287

    
1288
/**
1289
 * Implements hook_field_attach_delete_bundle().
1290
 *
1291
 * Delete any flags' applicability to the deleted bundle.
1292
 */
1293
function flag_field_attach_delete_bundle($entity_type, $bundle, $instances) {
1294
  // This query can't use db_delete() because that doesn't support a
1295
  // subquery: see http://drupal.org/node/1267508.
1296
  db_query("DELETE FROM {flag_types} WHERE type = :bundle AND fid IN (SELECT fid FROM {flag} WHERE entity_type = :entity_type)", array(
1297
    ':bundle' => $bundle,
1298
    ':entity_type' => $entity_type,
1299
  ));
1300
}
1301

    
1302
/**
1303
 * Flags or unflags an item.
1304
 *
1305
 * @param $action
1306
 *   Either 'flag' or 'unflag'.
1307
 * @param $flag_name
1308
 *   The name of the flag to use.
1309
 * @param $entity_id
1310
 *   The ID of the item to flag or unflag.
1311
 * @param $account
1312
 *   (optional) The user on whose behalf to flag. Omit for the current user.
1313
 * @param permissions_check
1314
 *   (optional) A boolean indicating whether to skip permissions.
1315
 *
1316
 * @return
1317
 *   FALSE if some error occured (e.g., user has no permission, flag isn't
1318
 *   applicable to the item, etc.), TRUE otherwise.
1319
 */
1320
function flag($action, $flag_name, $entity_id, $account = NULL, $permissions_check = FALSE) {
1321
  if (!($flag = flag_get_flag($flag_name))) {
1322
    // Flag does not exist.
1323
    return FALSE;
1324
  }
1325
  return $flag->flag($action, $entity_id, $account, $permissions_check);
1326
}
1327

    
1328
/**
1329
 * Implements hook_flag_flag().
1330
 */
1331
function flag_flag_flag($flag, $entity_id, $account, $flagging) {
1332
  if (module_exists('trigger')) {
1333
    flag_flag_trigger('flag', $flag, $entity_id, $account, $flagging);
1334
  }
1335
}
1336

    
1337
/**
1338
 * Implements hook_flag_unflag().
1339
 */
1340
function flag_flag_unflag($flag, $entity_id, $account, $flagging) {
1341
  if (module_exists('trigger')) {
1342
    flag_flag_trigger('unflag', $flag, $entity_id, $account, $flagging);
1343
  }
1344
}
1345

    
1346
/**
1347
 * Trigger actions if any are available. Helper for hook_flag_(un)flag().
1348
 *
1349
 * @param $op
1350
 *  The operation being performed: one of 'flag' or 'unflag'.
1351
 * @param $flag
1352
 *  The flag object.
1353
 * @param $entity_id
1354
 *  The id of the entity the flag is on.
1355
 * @param $account
1356
 *  The user account performing the action.
1357
 * @param $flagging_id
1358
 *  The flagging entity.
1359
 */
1360
function flag_flag_trigger($action, $flag, $entity_id, $account, $flagging) {
1361
  $context['hook'] = 'flag';
1362
  $context['account'] = $account;
1363
  $context['flag'] = $flag;
1364
  $context['op'] = $action;
1365
  // We add to the $context all the objects we know about:
1366
  $context = array_merge($flag->get_relevant_action_objects($entity_id), $context);
1367
  // The primary object the actions work on.
1368
  $object = $flag->fetch_entity($entity_id);
1369

    
1370
  // Generic "all flags" actions.
1371
  foreach (trigger_get_assigned_actions('flag_' . $action) as $aid => $action_info) {
1372
    // The 'if ($aid)' is a safeguard against
1373
    // http://drupal.org/node/271460#comment-886564
1374
    if ($aid) {
1375
      actions_do($aid, $object, $context);
1376
    }
1377
  }
1378
  // Actions specifically for this flag.
1379
  foreach (trigger_get_assigned_actions('flag_' . $action . '_' . $flag->name) as $aid => $action_info) {
1380
    if ($aid) {
1381
      actions_do($aid, $object, $context);
1382
    }
1383
  }
1384
}
1385

    
1386
/**
1387
 * Implements hook_flag_access().
1388
 */
1389
function flag_flag_access($flag, $entity_id, $action, $account) {
1390
  // Do nothing if there is no restriction by authorship.
1391
  if (empty($flag->access_author)) {
1392
    return;
1393
  }
1394

    
1395
  // Restrict access by authorship. It's important that TRUE is never returned
1396
  // here, otherwise we'd grant permission even if other modules denied access.
1397
  if ($flag->entity_type == 'node') {
1398
    // For non-existent nodes (such as on the node add form), assume that the
1399
    // current user is creating the content.
1400
    if (empty($entity_id) || !($node = $flag->fetch_entity($entity_id))) {
1401
      return $flag->access_author == 'others' ? FALSE : NULL;
1402
    }
1403

    
1404
    if ($flag->access_author == 'own' && $node->uid != $account->uid) {
1405
      return FALSE;
1406
    }
1407
    elseif ($flag->access_author == 'others' && $node->uid == $account->uid) {
1408
      return FALSE;
1409
    }
1410
  }
1411

    
1412
  // Restrict access by comment authorship.
1413
  if ($flag->entity_type == 'comment') {
1414
    // For non-existent comments (such as on the comment add form), assume that
1415
    // the current user is creating the content.
1416
    if (empty($entity_id) || !($comment = $flag->fetch_entity($entity_id)) || $entity_id == 'new') {
1417
      return $flag->access_author == 'comment_others' ? FALSE : NULL;
1418
    }
1419

    
1420
    $node = node_load($comment->nid);
1421
    if ($flag->access_author == 'node_own' && $node->uid != $account->uid) {
1422
      return FALSE;
1423
    }
1424
    elseif ($flag->access_author == 'node_others' && $node->uid == $account->uid) {
1425
      return FALSE;
1426
    }
1427
    elseif ($flag->access_author == 'comment_own' && $comment->uid != $account->uid) {
1428
      return FALSE;
1429
    }
1430
    elseif ($flag->access_author == 'comment_others' && $comment->uid == $account->uid) {
1431
      return FALSE;
1432
    }
1433
  }
1434
}
1435

    
1436
/**
1437
 * Implements hook_flag_access_multiple().
1438
 */
1439
function flag_flag_access_multiple($flag, $entity_ids, $account) {
1440
  $access = array();
1441

    
1442
  // Do nothing if there is no restriction by authorship.
1443
  if (empty($flag->access_author)) {
1444
    return $access;
1445
  }
1446

    
1447
  if ($flag->entity_type == 'node') {
1448
    // Restrict access by authorship. This is similar to flag_flag_access()
1449
    // above, but returns an array of 'nid' => $access values. Similarly, we
1450
    // should never return TRUE in any of these access values, only FALSE if we
1451
    // want to deny access, or use the current access value provided by Flag.
1452
    $result = db_select('node', 'n')
1453
      ->fields('n', array('nid', 'uid'))
1454
      ->condition('nid', array_keys($entity_ids), 'IN')
1455
      ->condition('type', $flag->types, 'IN')
1456
      ->execute();
1457
    foreach ($result as $row) {
1458
      if ($flag->access_author == 'own') {
1459
        $access[$row->nid] = $row->uid != $account->uid ? FALSE : NULL;
1460
      }
1461
      elseif ($flag->access_author == 'others') {
1462
        $access[$row->nid] = $row->uid == $account->uid ? FALSE : NULL;
1463
      }
1464
    }
1465
  }
1466

    
1467
  if ($flag->entity_type == 'comment') {
1468
    // Restrict access by comment ownership.
1469
    $query = db_select('comment', 'c');
1470
    $query->leftJoin('node', 'n', 'c.nid = n.nid');
1471
    $query
1472
      ->fields('c', array('cid', 'nid', 'uid'))
1473
      ->condition('c.cid', $entity_ids, 'IN');
1474
    $query->addField('c', 'uid', 'comment_uid');
1475
    $result = $query->execute();
1476

    
1477
    foreach ($result as $row) {
1478
      if ($flag->access_author == 'node_own') {
1479
        $access[$row->cid] = $row->node_uid != $account->uid ? FALSE : NULL;
1480
      }
1481
      elseif ($flag->access_author == 'node_others') {
1482
        $access[$row->cid] = $row->node_uid == $account->uid ? FALSE : NULL;
1483
      }
1484
      elseif ($flag->access_author == 'comment_own') {
1485
        $access[$row->cid] = $row->comment_uid != $account->uid ? FALSE : NULL;
1486
      }
1487
      elseif ($flag->access_author == 'comment_others') {
1488
        $access[$row->cid] = $row->comment_uid == $account->uid ? FALSE : NULL;
1489
      }
1490
    }
1491
  }
1492

    
1493
  // Always return an array (even if empty) of accesses.
1494
  return $access;
1495
}
1496

    
1497
/**
1498
 * Implements hook_theme().
1499
 */
1500
function flag_theme() {
1501
  $path = drupal_get_path('module', 'flag') . '/theme';
1502

    
1503
  return array(
1504
    'flag' => array(
1505
      'variables' => array(
1506
        'flag' => NULL,
1507
        'action' => NULL,
1508
        'entity_id' => NULL,
1509
        'after_flagging' => FALSE,
1510
        'needs_wrapping_element' => FALSE,
1511
        'errors' => array(),
1512
      ),
1513
      'template' => 'flag',
1514
      'pattern' => 'flag__',
1515
      'path' => $path,
1516
    ),
1517
    'flag_tokens_browser' => array(
1518
      'variables' => array(
1519
        'types' => array('all'),
1520
        'global_types' => TRUE,
1521
      ),
1522
      'file' => 'flag.tokens.inc',
1523
    ),
1524
    'flag_admin_listing' => array(
1525
      'render element' => 'form',
1526
      'file' => 'includes/flag.admin.inc',
1527
    ),
1528
    'flag_admin_listing_disabled' => array(
1529
      'variables' => array(
1530
        'flags' => NULL,
1531
        'default_flags' => NULL,
1532
      ),
1533
      'file' => 'includes/flag.admin.inc',
1534
    ),
1535
    'flag_admin_page' => array(
1536
      'variables' => array(
1537
        'flags' => NULL,
1538
        'default_flags' => NULL,
1539
        'flag_admin_listing' => NULL,
1540
      ),
1541
      'file' => 'includes/flag.admin.inc',
1542
    ),
1543
    'flag_form_roles' => array(
1544
      'render element' => 'element',
1545
      'file' => 'includes/flag.admin.inc',
1546
    ),
1547
  );
1548
}
1549

    
1550
/**
1551
 * A preprocess function for our theme('flag'). It generates the
1552
 * variables needed there.
1553
 *
1554
 * The $variables array initially contains the following arguments:
1555
 * - $flag
1556
 * - $action
1557
 * - $entity_id
1558
 * - $after_flagging
1559
 * - $errors
1560
 * - $needs_wrapping_element
1561
 *
1562
 * See 'flag.tpl.php' for their documentation.
1563
 */
1564
function template_preprocess_flag(&$variables) {
1565
  global $user;
1566
  $initialized = &drupal_static(__FUNCTION__, array());
1567

    
1568
  // Some typing shotcuts:
1569
  $flag =& $variables['flag'];
1570
  $action = $variables['action'];
1571
  $entity_id = $variables['entity_id'];
1572
  $errors = implode('<br />', $variables['errors']);
1573
  $flag_name_css = str_replace('_', '-', $flag->name);
1574

    
1575
  // Generate the link URL.
1576
  $link_type = $flag->get_link_type();
1577
  $link = module_invoke($link_type['module'], 'flag_link', $flag, $action, $entity_id);
1578
  if (isset($link['title']) && empty($link['html'])) {
1579
    $link['title'] = check_plain($link['title']);
1580
  }
1581

    
1582
  // Replace the link with the access denied text if unable to flag.
1583
  if ($action == 'unflag' && !$flag->access($entity_id, 'unflag')) {
1584
    $link['title'] = $flag->get_label('unflag_denied_text', $entity_id);
1585
    unset($link['href']);
1586
  }
1587

    
1588
  // Anonymous users always need the JavaScript to maintain their flag state.
1589
  if ($user->uid == 0) {
1590
    $link_type['uses standard js'] = TRUE;
1591
  }
1592

    
1593
  // Load the JavaScript/CSS, if the link type requires it.
1594
  if (!isset($initialized[$link_type['name']])) {
1595
    if ($link_type['uses standard css']) {
1596
      drupal_add_css(drupal_get_path('module', 'flag') . '/theme/flag.css');
1597
    }
1598
    if ($link_type['uses standard js']) {
1599
      drupal_add_js(drupal_get_path('module', 'flag') . '/theme/flag.js');
1600
    }
1601
    $initialized[$link_type['name']] = TRUE;
1602
  }
1603

    
1604
  $variables['link'] = $link;
1605
  $variables['link_href'] = isset($link['href']) ? check_url(url($link['href'], $link)) : FALSE;
1606
  $variables['link_text'] = isset($link['title']) ? $link['title'] : $flag->get_label($action . '_short', $entity_id);
1607
  $variables['link_title'] = isset($link['attributes']['title']) ? check_plain($link['attributes']['title']) : check_plain(strip_tags($flag->get_label($action . '_long', $entity_id)));
1608
  $variables['status'] = ($action == 'flag' ? 'unflagged' : 'flagged');
1609
  $variables['flag_name_css'] = $flag_name_css;
1610

    
1611
  $variables['flag_wrapper_classes_array'] = array();
1612
  $variables['flag_wrapper_classes_array'][] = 'flag-wrapper';
1613
  $variables['flag_wrapper_classes_array'][] = 'flag-' . $flag_name_css;
1614
  $variables['flag_wrapper_classes_array'][] = 'flag-' . $flag_name_css . '-' . $entity_id;
1615

    
1616
  $variables['flag_classes_array'] = array();
1617
  $variables['flag_classes_array'][] = 'flag';
1618
  if (isset($link['href'])) {
1619
    $variables['flag_classes_array'][] = $variables['action'] . '-action';
1620
    $variables['flag_classes_array'][] = 'flag-link-' . $flag->link_type;
1621
  }
1622
  else {
1623
    $variables['flag_classes_array'][] = $variables['action'] . '-disabled';
1624
  }
1625
  if (isset($link['attributes']['class'])) {
1626
    $link['attributes']['class'] = is_string($link['attributes']['class']) ? array_filter(explode(' ', $link['attributes']['class'])) : $link['attributes']['class'];
1627
    $variables['flag_classes_array'] = array_merge($variables['flag_classes_array'], $link['attributes']['class']);
1628
  }
1629
  $variables['message_classes_array'] = array();
1630
  if ($variables['after_flagging']) {
1631
    $variables['message_classes_array'][] = 'flag-message';
1632
    if ($errors) {
1633
      $variables['message_classes_array'][] = 'flag-failure-message';
1634
      $variables['message_text'] = $errors;
1635
    }
1636
    else {
1637
      $inverse_action = ($action == 'flag' ? 'unflag' : 'flag');
1638
      $variables['message_classes_array'][] = 'flag-success-message';
1639
      $variables['message_classes_array'][] = 'flag-' . $variables['status'] . '-message';
1640
      $variables['message_text'] = $flag->get_label($inverse_action . '_message', $entity_id);
1641
      $variables['flag_classes_array'][] = $variables['status'];
1642
      // By default we make our JS code remove, after a few seconds, only
1643
      // success messages.
1644
      $variables['message_classes_array'][] = 'flag-auto-remove';
1645
    }
1646
  }
1647
  else {
1648
    $variables['message_text'] = '';
1649
  }
1650
}
1651

    
1652
/**
1653
 * Theme processor for flag.tpl.php.
1654
 *
1655
 * @param array &$variables
1656
 *  An array of variables for the template. See 'flag.tpl.php' for their
1657
 *  documentation.
1658
 */
1659
function template_process_flag(&$variables) {
1660
  // Convert class arrays to strings.
1661
  $variables['flag_wrapper_classes'] = implode(' ', $variables['flag_wrapper_classes_array']);
1662
  $variables['flag_classes'] = implode(' ', $variables['flag_classes_array']);
1663
  $variables['message_classes'] = implode(' ', $variables['message_classes_array']);
1664
}
1665

    
1666
/**
1667
 * Return an array of flag names keyed by fid.
1668
 */
1669
function _flag_get_flag_names() {
1670
  $flags = flag_get_flags();
1671
  $flag_names = array();
1672
  foreach ($flags as $flag) {
1673
    $flag_names[$flag->fid] = $flag->name;
1674
  }
1675
  return $flag_names;
1676
}
1677

    
1678
/**
1679
 * Return an array of flag link types suitable for a select list or radios.
1680
 */
1681
function _flag_link_type_options() {
1682
  $options = array();
1683
  $types = flag_get_link_types();
1684
  foreach ($types as $type_name => $type) {
1685
    $options[$type_name] = $type['title'];
1686
  }
1687
  return $options;
1688
}
1689

    
1690
/**
1691
 * Return an array of flag link type descriptions.
1692
 */
1693
function _flag_link_type_descriptions() {
1694
  $options = array();
1695
  $types = flag_get_link_types();
1696
  foreach ($types as $type_name => $type) {
1697
    $options[$type_name] = $type['description'];
1698
  }
1699
  return $options;
1700
}
1701

    
1702
// ---------------------------------------------------------------------------
1703
// Non-Views public API
1704

    
1705
/**
1706
 * Gets the count of flaggings for the given flag.
1707
 *
1708
 * For example, if you have an 'endorse' flag, this method will tell you how
1709
 * many endorsements have been made, rather than how many things have been
1710
 * endorsed.
1711
 *
1712
 * When called during a flagging or unflagging (such as from a hook
1713
 * implementation or from Rules), the flagging or unflagging that is in the
1714
 * process of being performed:
1715
 *  - will be included during a flagging operation
1716
 *  - will STILL be included during an unflagging operation. That is, the count
1717
 *    will not yet have been decreased.
1718
 * This is because this queries the {flagging} table, which only has its record
1719
 * deleted at the very end of the unflagging process.
1720
 *
1721
 * @param $flag
1722
 *   The flag.
1723
 * @param $entity_type
1724
 *   The entity type. For example, 'node'.
1725
 *
1726
 * @return int
1727
 *   The number of flaggings for the flag.
1728
 */
1729
function flag_get_entity_flag_counts($flag, $entity_type) {
1730
  $counts = &drupal_static(__FUNCTION__);
1731

    
1732
  // We check to see if the flag count is already in the cache,
1733
  // if it's not, run the query.
1734
  if (!isset($counts[$flag->name][$entity_type])) {
1735
    $counts[$flag->name][$entity_type] = array();
1736
    $result = db_select('flagging', 'f')
1737
      ->fields('f', array('fid'))
1738
      ->condition('fid', $flag->fid)
1739
      ->condition('entity_type', $entity_type)
1740
      ->countQuery()
1741
      ->execute()
1742
      ->fetchField();
1743
    $counts[$flag->name][$entity_type] = $result;
1744
  }
1745

    
1746
  return $counts[$flag->name][$entity_type];
1747
}
1748

    
1749
/**
1750
 * Gets the count of the flaggings made by a user with a flag.
1751
 *
1752
 * For example, with a 'bookmarks' flag, this returns the number of bookmarks
1753
 * a user has created.
1754
 *
1755
 * When called during a flagging or unflagging (such as from a hook
1756
 * implementation or from Rules), the flagging or unflagging that is in the
1757
 * process of being performed:
1758
 *  - will be included during a flagging operation
1759
 *  - will STILL be included during an unflagging operation. That is, the count
1760
 *    will not yet have been decreased.
1761
 * This is because this queries the {flagging} table, which only has its record
1762
 * deleted at the very end of the unflagging process.
1763
 *
1764
 * However, it should be noted that this method does not serves global flags.
1765
 *
1766
 * @param $flag
1767
 *   The flag.
1768
 * @param $user
1769
 *   The user object.
1770
 *
1771
 * @return int
1772
 *   The number of flaggings for the given flag and user.
1773
 */
1774
function flag_get_user_flag_counts($flag, $user) {
1775
  $counts = &drupal_static(__FUNCTION__);
1776

    
1777
  // We check to see if the flag count is already in the cache,
1778
  // if it's not, run the query.
1779
  if (!isset($counts[$flag->name][$user->uid])) {
1780
    $counts[$flag->name][$user->uid] = array();
1781
    $result = db_select('flagging', 'f')
1782
      ->fields('f', array('fid'))
1783
      ->condition('fid', $flag->fid)
1784
      ->condition('uid', $user->uid)
1785
      ->countQuery()
1786
      ->execute()
1787
      ->fetchField();
1788
    $counts[$flag->name][$user->uid] = $result;
1789
  }
1790

    
1791
  return $counts[$flag->name][$user->uid];
1792
}
1793

    
1794
/**
1795
 * Gets flag counts for all flags on an entity.
1796
 *
1797
 * Provides a count of all the flaggings for a single entity. Instead
1798
 * of a single response, this method returns an array of counts keyed by
1799
 * the flag ID:
1800
 *
1801
 * @code
1802
 * array(
1803
 *   my_flag => 42
1804
 *   another_flag => 57
1805
 * );
1806
 * @endcode
1807
 *
1808
 * When called during a flagging or unflagging (such as from a hook
1809
 * implementation or from Rules), the count this returns takes into account the
1810
 * the flagging or unflagging that is in the process of being performed.
1811
 *
1812
 * @param $entity_type
1813
 *   The entity type (usually 'node').
1814
 * @param $entity_id
1815
 *   The entity ID (usually the node ID).
1816
 *
1817
 * @return array
1818
 *   An array giving the counts of all flaggings on the entity. The flag IDs
1819
 *   are the keys and the counts for each flag the values. Note that flags
1820
 *   that have no flaggings are not included in the array.
1821
 */
1822
function flag_get_counts($entity_type, $entity_id) {
1823
  $counts = &drupal_static(__FUNCTION__);
1824

    
1825
  if (!isset($counts[$entity_type][$entity_id])) {
1826
    $counts[$entity_type][$entity_id] = array();
1827
    $query = db_select('flag', 'f');
1828
    $query->leftJoin('flag_counts', 'fc', 'f.fid = fc.fid');
1829
    $result = $query
1830
      ->fields('f', array('name'))
1831
      ->fields('fc', array('count'))
1832
      ->condition('fc.entity_type', $entity_type)
1833
      ->condition('fc.entity_id', $entity_id)
1834
      ->execute();
1835
    foreach ($result as $row) {
1836
      $counts[$entity_type][$entity_id][$row->name] = $row->count;
1837
    }
1838
  }
1839

    
1840
  return $counts[$entity_type][$entity_id];
1841
}
1842

    
1843
/**
1844
 * Gets the count of entities flagged by the given flag.
1845
 *
1846
 * For example, with a 'report abuse' flag, this returns the number of
1847
 * entities that have been reported, not the total number of reports. In other
1848
 * words, an entity that has been reported multiple times will only be counted
1849
 * once.
1850
 *
1851
 * When called during a flagging or unflagging (such as from a hook
1852
 * implementation or from Rules), the count this returns takes into account the
1853
 * the flagging or unflagging that is in the process of being performed.
1854
 *
1855
 * @param $flag_name
1856
 *   The flag name for which to retrieve a flag count.
1857
 * @param $reset
1858
 *   (optional) Reset the internal cache and execute the SQL query another time.
1859
 *
1860
 * @return int
1861
 *   The number of entities that are flagged with the flag.
1862
 */
1863
function flag_get_flag_counts($flag_name, $reset = FALSE) {
1864
  $counts = &drupal_static(__FUNCTION__);
1865

    
1866
  if ($reset) {
1867
    $counts = array();
1868
  }
1869
  if (!isset($counts[$flag_name])) {
1870
    $flag = flag_get_flag($flag_name);
1871
    $counts[$flag_name] = db_select('flag_counts', 'fc')
1872
      ->fields('fc', array('fid'))
1873
      ->condition('fid', $flag->fid)
1874
      ->countQuery()
1875
      ->execute()
1876
      ->fetchField();
1877
  }
1878

    
1879
  return $counts[$flag_name];
1880
}
1881

    
1882
/**
1883
 * Load a single flag either by name or by flag ID.
1884
 *
1885
 * @param $name
1886
 *  (optional) The flag name.
1887
 * @param $fid
1888
 *  (optional) The the flag id.
1889
 *
1890
 * @return
1891
 *  The flag object, or FALSE if no matching flag was found.
1892
 */
1893
function flag_get_flag($name = NULL, $fid = NULL) {
1894
  $flags = flag_get_flags();
1895
  if (isset($name)) {
1896
    if (isset($flags[$name])) {
1897
      return $flags[$name];
1898
    }
1899
  }
1900
  elseif (isset($fid)) {
1901
    foreach ($flags as $flag) {
1902
      if ($flag->fid == $fid) {
1903
        return $flag;
1904
      }
1905
    }
1906
  }
1907
  return FALSE;
1908
}
1909

    
1910
/**
1911
 * List all flags available.
1912
 *
1913
 * If all the parameters are omitted, a list of all flags will be returned.
1914
 *
1915
 * @param $entity_type
1916
 *   (optional) The type of entity for which to load the flags. Usually 'node'.
1917
 * @param $content_subtype
1918
 *   (optional) The node type for which to load the flags.
1919
 * @param $account
1920
 *   (optional) The user accont to filter available flags. If not set, all
1921
 *   flags for will this node will be returned.
1922
 *
1923
 * @return
1924
 *   An array of flag objects, keyed by the flag names.
1925
 */
1926
function flag_get_flags($entity_type = NULL, $content_subtype = NULL, $account = NULL) {
1927
  $flags = &drupal_static(__FUNCTION__);
1928

    
1929
  // Retrieve a list of all flags, regardless of the parameters.
1930
  if (!isset($flags)) {
1931
    $flags = array();
1932

    
1933
    // Database flags.
1934
    $query = db_select('flag', 'f');
1935
    $query->leftJoin('flag_types', 'fn', 'fn.fid = f.fid');
1936
    $result = $query
1937
      ->fields('f', array(
1938
        'fid',
1939
        'entity_type',
1940
        'name',
1941
        'title',
1942
        'global',
1943
        'options',
1944
      ))
1945
      ->fields('fn', array('type'))
1946
      ->execute();
1947
    foreach ($result as $row) {
1948
      if (!isset($flags[$row->name])) {
1949
        $flags[$row->name] = flag_flag::factory_by_row($row);
1950
      }
1951
      else {
1952
        $flags[$row->name]->types[] = $row->type;
1953
      }
1954
    }
1955

    
1956
    // Add code-based flags provided by modules.
1957
    $default_flags = flag_get_default_flags();
1958
    foreach ($default_flags as $name => $default_flag) {
1959
      // Insert new enabled flags into the database to give them an FID.
1960
      if ($default_flag->status && !isset($flags[$name])) {
1961
        $default_flag->save();
1962
        $flags[$name] = $default_flag;
1963
      }
1964

    
1965
      if (isset($flags[$name])) {
1966
        // Ensure overridden flags are associated with their parent module.
1967
        $flags[$name]->module = $default_flag->module;
1968

    
1969
        // Update the flag with any properties that are "locked" by the code
1970
        // version.
1971
        if (isset($default_flag->locked)) {
1972
          $flags[$name]->locked = $default_flag->locked;
1973
          foreach ($default_flag->locked as $property) {
1974
            $flags[$name]->$property = $default_flag->$property;
1975
          }
1976
        }
1977
      }
1978
    }
1979

    
1980
    // Sort the list of flags by weight.
1981
    uasort($flags, '_flag_compare_weight');
1982

    
1983
    foreach ($flags as $flag) {
1984
      // Allow modules implementing hook_flag_alter(&$flag) to modify each flag.
1985
      drupal_alter('flag', $flag);
1986
    }
1987
  }
1988

    
1989
  // Make a variable copy to filter types and account.
1990
  $filtered_flags = $flags;
1991

    
1992
  // Filter out flags based on type and subtype.
1993
  if (isset($entity_type) || isset($content_subtype)) {
1994
    foreach ($filtered_flags as $name => $flag) {
1995
      if (!$flag->access_entity_enabled($entity_type, $content_subtype)) {
1996
        unset($filtered_flags[$name]);
1997
      }
1998
    }
1999
  }
2000

    
2001
  // Filter out flags based on account permissions.
2002
  if (isset($account) && $account->uid != 1) {
2003
    foreach ($filtered_flags as $name => $flag) {
2004
      // We test against the 'flag' action, which is the minimum permission to
2005
      // use a flag.
2006
      if (!$flag->user_access('flag', $account)) {
2007
        unset($filtered_flags[$name]);
2008
      }
2009
    }
2010
  }
2011

    
2012
  return $filtered_flags;
2013
}
2014

    
2015
/**
2016
 * Comparison function for uasort().
2017
 */
2018
function _flag_compare_weight($flag1, $flag2) {
2019
  if ($flag1->weight == $flag2->weight) {
2020
    return 0;
2021
  }
2022
  return $flag1->weight < $flag2->weight ? -1 : 1;
2023
}
2024

    
2025
/**
2026
 * Retrieve a list of flags defined by modules.
2027
 *
2028
 * @param $include_disabled
2029
 *   (optional) Unless specified, only enabled flags will be returned.
2030
 *
2031
 * @return
2032
 *   An array of flag prototypes, not usable for flagging. Use flag_get_flags()
2033
 *   if needing to perform a flagging with any enabled flag.
2034
 */
2035
function flag_get_default_flags($include_disabled = FALSE) {
2036
  $default_flags = array();
2037
  $flag_status = variable_get('flag_default_flag_status', array());
2038

    
2039
  $default_flags_info = array();
2040
  foreach (module_implements('flag_default_flags') as $module) {
2041
    $function = $module . '_flag_default_flags';
2042
    foreach ($function() as $flag_name => $flag_info) {
2043
      // Backward compatibility: old exported default flags have their names
2044
      // in $flag_info instead, so we use the + operator to not overwrite it.
2045
      $default_flags_info[$flag_name] = $flag_info + array(
2046
        'name' => $flag_name,
2047
        'module' => $module,
2048
      );
2049
    }
2050
  }
2051

    
2052
  // Allow modules to alter definitions using hook_flag_default_flags_alter().
2053
  drupal_alter('flag_default_flags', $default_flags_info);
2054

    
2055
  foreach ($default_flags_info as $flag_info) {
2056
    $flag = flag_flag::factory_by_array($flag_info);
2057

    
2058
    // Disable flags that are not at the current API version.
2059
    if (!$flag->is_compatible()) {
2060
      $flag->status = FALSE;
2061
    }
2062

    
2063
    // Add flags that have been enabled.
2064
    if ((!isset($flag_status[$flag->name]) && (!isset($flag->status) || $flag->status)) || !empty($flag_status[$flag->name])) {
2065
      $flag->status = TRUE;
2066
      $default_flags[$flag->name] = $flag;
2067
    }
2068
    // Add flags that have been disabled.
2069
    elseif ($include_disabled) {
2070
      $flag->status = FALSE;
2071
      $default_flags[$flag->name] = $flag;
2072
    }
2073
  }
2074

    
2075
  return $default_flags;
2076
}
2077

    
2078
/**
2079
 * Get all flagged entities in a flag.
2080
 *
2081
 * @param $flag_name
2082
 *   The flag name for which to retrieve flagged entites.
2083
 *
2084
 * @return
2085
 *   An array of flagging data, keyed by the flagging ID.
2086
 */
2087
function flag_get_flag_flagging_data($flag_name) {
2088
  $flag = flag_get_flag($flag_name);
2089
  $result = db_select('flagging', 'fc')
2090
    ->fields('fc')
2091
    ->condition('fid', $flag->fid)
2092
    ->execute();
2093
  return $result->fetchAllAssoc('flagging_id');
2094
}
2095

    
2096
/**
2097
 * Find what a user has flagged, either a single entity or on the entire site.
2098
 *
2099
 * When called during a flagging or unflagging (such as from a hook
2100
 * implementation or from Rules), the flagging or unflagging that is in the
2101
 * process of being performed:
2102
 *  - will be included during a flagging operation
2103
 *  - will STILL be included during an unflagging operation. That is, the count
2104
 *    will not yet have been decreased.
2105
 * This is because this queries the {flagging} table, which only has its record
2106
 * deleted at the very end of the unflagging process.
2107
 *
2108
 * @param $entity_type
2109
 *   The type of entity that will be retrieved. Usually 'node'.
2110
 * @param $entity_id
2111
 *   (optional) The entity ID to check for flagging. If none given, all
2112
 *   entities flagged by this user will be returned.
2113
 * @param $uid
2114
 *   (optional) The user ID whose flags we're checking. If none given, the
2115
 *   current user will be used.
2116
 * @param $sid
2117
 *   (optional) The user SID (provided by Session API) whose flags we're
2118
 *   checking. If none given, the current user will be used. The SID is 0 for
2119
 *   logged in users.
2120
 *
2121
 * @return
2122
 *   If returning a single item's flags (that is, when $entity_id isn't NULL),
2123
 *   an array of the structure
2124
 *   [flag_name] => (
2125
 *     flagging_id => [flagging_id],
2126
 *     uid => [uid],
2127
 *     entity_id => [entity_id],
2128
 *     timestamp => [timestamp],
2129
 *     ...)
2130
 *
2131
 *   If returning all items' flags, an array of arrays for each flag:
2132
 *   [flag_name] => [entity_id] => Object from above.
2133
 */
2134
function flag_get_user_flags($entity_type, $entity_id = NULL, $uid = NULL, $sid = NULL) {
2135
  $flagged_content = &drupal_static(__FUNCTION__);
2136

    
2137
  $uid = !isset($uid) ? $GLOBALS['user']->uid : $uid;
2138
  $sid = !isset($sid) ? flag_get_sid($uid) : $sid;
2139

    
2140
  if (isset($entity_id)) {
2141
    if (!isset($flagged_content[$uid][$sid][$entity_type][$entity_id])) {
2142
      $flag_names = _flag_get_flag_names();
2143
      $flagged_content[$uid][$sid][$entity_type][$entity_id] = array();
2144
      $result = db_select('flagging', 'fc')
2145
        ->fields('fc')
2146
        ->condition('entity_type', $entity_type)
2147
        ->condition('entity_id', $entity_id)
2148
        ->condition(db_or()
2149
          ->condition('uid', $uid)
2150
          ->condition('uid', 0)
2151
        )
2152
        ->condition('sid', $sid)
2153
        ->execute();
2154

    
2155
      foreach ($result as $flagging_data) {
2156
        $flagged_content[$uid][$sid][$entity_type][$entity_id][$flag_names[$flagging_data->fid]] = $flagging_data;
2157
      }
2158
    }
2159
    return $flagged_content[$uid][$sid][$entity_type][$entity_id];
2160
  }
2161

    
2162
  else {
2163
    if (!isset($flagged_content[$uid][$sid][$entity_type]['all'])) {
2164
      $flag_names = _flag_get_flag_names();
2165
      $flagged_content[$uid][$sid][$entity_type]['all'] = array();
2166
      $result = db_select('flagging', 'fc')
2167
        ->fields('fc')
2168
        ->condition('entity_type', $entity_type)
2169
        ->condition(db_or()
2170
          ->condition('uid', $uid)
2171
          ->condition('uid', 0)
2172
        )
2173
        ->condition('sid', $sid)
2174
        ->execute();
2175
      foreach ($result as $flagging_data) {
2176
        $flagged_content[$uid][$sid][$entity_type]['all'][$flag_names[$flagging_data->fid]][$flagging_data->entity_id] = $flagging_data;
2177
      }
2178
    }
2179
    return $flagged_content[$uid][$sid][$entity_type]['all'];
2180
  }
2181

    
2182
}
2183

    
2184
/**
2185
 * Return a list of users who have flagged an entity.
2186
 *
2187
 * When called during a flagging or unflagging (such as from a hook
2188
 * implementation or from Rules), the flagging or unflagging that is in the
2189
 * process of being performed:
2190
 *  - will be included during a flagging operation
2191
 *  - will STILL be included during an unflagging operation. That is, the count
2192
 *    will not yet have been decreased.
2193
 * This is because this queries the {flagging} table, which only has its record
2194
 * deleted at the very end of the unflagging process.
2195
 *
2196
 * @param $entity_type
2197
 *   The type of entity that will be retrieved. Usually 'node'.
2198
 * @param $entity_id
2199
 *   The entity ID to check for flagging.
2200
 * @param $flag_name
2201
 *   (optional) The name of a flag if wanting a list specific to a single flag.
2202
 *
2203
 * @return
2204
 *   A nested array of flagging records (i.e. rows from the {flagging} table,
2205
 *   rather than complete Flagging entities). The structure depends on the
2206
 *   presence of the $flag_name parameter:
2207
 *    - if $flag_name is omitted, the array is keyed first by the user ID of
2208
 *      the users that flagged the entity, then by flag name. Each value is
2209
 *      then the flagging record.
2210
 *    - if $flag_name is given, the array is keyed only by user ID. Each value
2211
 *      is the flagging record.
2212
 *   If no flags were found an empty array is returned.
2213
 */
2214
function flag_get_entity_flags($entity_type, $entity_id, $flag_name = NULL) {
2215
  $entity_flags = &drupal_static(__FUNCTION__, array());
2216

    
2217
  if (!isset($entity_flags[$entity_type][$entity_id])) {
2218
    $flag_names = _flag_get_flag_names();
2219
    $result = db_select('flagging', 'fc')
2220
      ->fields('fc')
2221
      ->condition('entity_type', $entity_type)
2222
      ->condition('entity_id', $entity_id)
2223
      ->orderBy('timestamp', 'DESC')
2224
      ->execute();
2225
    $entity_flags[$entity_type][$entity_id] = array();
2226
    foreach ($result as $flagging_data) {
2227
      // Build a list of flaggings for all flags by user.
2228
      $entity_flags[$entity_type][$entity_id]['users'][$flagging_data->uid][$flag_names[$flagging_data->fid]] = $flagging_data;
2229
      // Build a list of flaggings for each individual flag.
2230
      $entity_flags[$entity_type][$entity_id]['flags'][$flag_names[$flagging_data->fid]][$flagging_data->uid] = $flagging_data;
2231
    }
2232
  }
2233
  if (empty($entity_flags[$entity_type][$entity_id])) {
2234
    return array();
2235
  }
2236
  if (isset($flag_name)) {
2237
    if (isset($entity_flags[$entity_type][$entity_id]['flags'][$flag_name])) {
2238
      return $entity_flags[$entity_type][$entity_id]['flags'][$flag_name];
2239
    }
2240
    return array();
2241
  }
2242
  return $entity_flags[$entity_type][$entity_id]['users'];
2243
}
2244

    
2245
/**
2246
 * A utility function for outputting a flag link.
2247
 *
2248
 * You should call this function from your template when you want to put the
2249
 * link on the page yourself. For example, you could call this function from
2250
 * your theme preprocessor for node.tpl.php:
2251
 * @code
2252
 * $variables['my_flag_link'] = flag_create_link('bookmarks', $node->nid);
2253
 * @endcode
2254
 *
2255
 * @param $flag_name
2256
 *   The "machine readable" name of the flag; e.g. 'bookmarks'.
2257
 * @param $entity_id
2258
 *   The entity ID to check for flagging, for example a node ID.
2259
 * @param $variables
2260
 *  An array of further variables to pass to theme('flag'). For the full list
2261
 *  of parameters, see flag.tpl.php. Of particular interest:
2262
 *  - after_flagging: Set to TRUE if this flag link is being displayed as the
2263
 *    result of a flagging action.
2264
 *  - errors: An array of error messages.
2265
 *
2266
 * @return
2267
 *   The HTML for the themed flag link.
2268
 */
2269
function flag_create_link($flag_name, $entity_id, $variables = array()) {
2270
  $flag = flag_get_flag($flag_name);
2271
  if (!$flag) {
2272
    // Flag does not exist.
2273
    return;
2274
  }
2275
  if (!$flag->access($entity_id) && (!$flag->is_flagged($entity_id) || !$flag->access($entity_id, 'flag'))) {
2276
    // User has no permission to use this flag.
2277
    return;
2278
  }
2279
  return $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id, $variables);
2280
}
2281

    
2282
/**
2283
 * Trim a flag to a certain size.
2284
 *
2285
 * @param $fid
2286
 *   The flag object.
2287
 * @param $account
2288
 *   The user object on behalf the trimming will occur.
2289
 * @param $cutoff_size
2290
 *   The number of flaggings allowed. Any flaggings beyond that will be trimmed.
2291
 * @param $trim_newest
2292
 *   An optional boolean indicating whether to trim the newest flags.
2293
 * @param $permissions_check
2294
 *   (optional) A boolean indicating whether to skip permissions.
2295
 *   This will trim the flag if $permissions_check is TRUE even if the user
2296
 *   doesn't have the permission to flag/unflag.
2297
 */
2298
function flag_trim_flag($flag, $account, $cutoff_size, $trim_newest, $permissions_check = FALSE) {
2299
  $query = db_select('flagging', 'fc')
2300
    ->fields('fc')
2301
    ->condition('fid', $flag->fid)
2302
    ->condition(db_or()->condition('uid', $account->uid)->condition('uid', 0))
2303
    // Account for session ID (in the case of anonymous users).
2304
    ->condition('sid', flag_get_sid($account->uid));
2305
  // If $trim_newest is TRUE, then, we should order by 'ASC' as we should trim
2306
  // the newest flags.
2307
  if ($trim_newest) {
2308
    $query->orderBy('timestamp', 'ASC');
2309
  }
2310
  else {
2311
    $query->orderBy('timestamp', 'DESC');
2312
  }
2313

    
2314
  // Execute the query.
2315
  $result = $query->execute();
2316

    
2317
  $i = 1;
2318
  foreach ($result as $row) {
2319
    if ($i++ > $cutoff_size) {
2320
      flag('unflag', $flag->name, $row->entity_id, $account, $permissions_check);
2321
    }
2322
  }
2323
}
2324

    
2325
/**
2326
 * Remove all flagged entities from a flag.
2327
 *
2328
 * @param $flag
2329
 *   The flag object.
2330
 * @param $entity_id
2331
 *   (optional) The entity ID on which all flaggings will be removed. If left
2332
 *   empty, this will remove all of this flag's entities.
2333
 */
2334
function flag_reset_flag($flag, $entity_id = NULL) {
2335
  $query = db_select('flagging', 'fc')
2336
    ->fields('fc')
2337
    ->condition('fid', $flag->fid);
2338

    
2339
  if ($entity_id) {
2340
    $query->condition('entity_id', $entity_id);
2341
  }
2342

    
2343
  $result = $query->execute()->fetchAllAssoc('flagging_id', PDO::FETCH_ASSOC);
2344
  $rows = array();
2345
  foreach ($result as $row) {
2346
    $rows[] = $row;
2347
  }
2348
  module_invoke_all('flag_reset', $flag, $entity_id, $rows);
2349

    
2350
  $query = db_delete('flagging')->condition('fid', $flag->fid);
2351
  // Update the flag_counts table.
2352
  $count_query = db_delete('flag_counts')->condition('fid', $flag->fid);
2353
  if ($entity_id) {
2354
    $query->condition('entity_id', $entity_id);
2355
    $count_query->condition('entity_id', $entity_id);
2356
  }
2357
  $count_query->execute();
2358
  return $query->execute();
2359
}
2360

    
2361
/**
2362
 * Return an array of link types provided by modules.
2363
 *
2364
 * @return
2365
 *  An array of link types as defined by hook_flag_link_type_info(). These are
2366
 *  keyed by the type name, and each value is an array of properties. In
2367
 *  addition to those defined in hook_flag_link_type_info(), the following
2368
 *  properties are set:
2369
 *  - 'module': The providing module.
2370
 *  - 'name': The machine name of the type.
2371
 *
2372
 * @see hook_flag_link_type_info()
2373
 * @see hook_flag_link_type_info_alter()
2374
 */
2375
function flag_get_link_types() {
2376
  $link_types = &drupal_static(__FUNCTION__);
2377

    
2378
  if (!isset($link_types)) {
2379
    if ($cache = cache_get('flag_link_type_info')) {
2380
      $link_types = $cache->data;
2381
    }
2382
    // In some rare edge cases cache_get() can return an empty result. If it
2383
    // does, we make sure to fetch the link types again.
2384
    if (empty($link_types)) {
2385
      $link_types = array();
2386
      foreach (module_implements('flag_link_type_info') as $module) {
2387
        $module_types = module_invoke($module, 'flag_link_type_info');
2388
        foreach ($module_types as $type_name => $info) {
2389
          $link_types[$type_name] = $info + array(
2390
            'module' => $module,
2391
            'name' => $type_name,
2392
            'title' => '',
2393
            'description' => '',
2394
            'options' => array(),
2395
            'uses standard js' => TRUE,
2396
            'uses standard css' => TRUE,
2397
            'provides form' => FALSE,
2398
          );
2399
        }
2400
      }
2401
      drupal_alter('flag_link_type_info', $link_types);
2402

    
2403
      cache_set('flag_link_type_info', $link_types);
2404
    }
2405
  }
2406

    
2407
  return $link_types;
2408
}
2409

    
2410
/**
2411
 * Get a private token used to protect links from spoofing - CSRF.
2412
 */
2413
function flag_get_token($entity_id) {
2414
  // Anonymous users get a less secure token, since it must be the same for all
2415
  // anonymous users on the entire site to work with page caching.
2416
  return ($GLOBALS['user']->uid) ? drupal_get_token($entity_id) : md5(drupal_get_private_key() . $entity_id);
2417
}
2418

    
2419
/**
2420
 * Check to see if a token value matches the specified node.
2421
 */
2422
function flag_check_token($token, $entity_id) {
2423
  return flag_get_token($entity_id) == $token;
2424
}
2425

    
2426
/**
2427
 * Set the Session ID for a user. Utilizes the Session API module.
2428
 *
2429
 * Creates a Session ID for an anonymous user and returns it. It will always
2430
 * return 0 for registered users.
2431
 *
2432
 * @param int $uid
2433
 *   (optional) The user ID to create a session ID for. Defaults to the
2434
 *   current user.
2435
 * @param bool $create
2436
 *   (optional) Determines whether a session should be created if it doesn't
2437
 *   exist yet. Defaults to TRUE.
2438
 *
2439
 * @return
2440
 *   The session ID, if a session was created. If not, the return value is 0.
2441
 *
2442
 * @see flag_get_sid()
2443
 */
2444
function flag_set_sid($uid = NULL, $create = TRUE) {
2445
  $sids = &drupal_static(__FUNCTION__, array());
2446

    
2447
  if (!isset($uid)) {
2448
    $uid = $GLOBALS['user']->uid;
2449
  }
2450

    
2451
  // Set the sid if none has been set yet. If the caller specified to create an
2452
  // sid and we have an invalid one (-1), create it.
2453
  if (!isset($sids[$uid]) || ($sids[$uid] == -1 && $create)) {
2454
    if (module_exists('session_api') && session_api_available() && $uid == 0) {
2455
      // This returns one of the following:
2456
      // - -1. This indicates that no session exists and none was created.
2457
      // - A positive integer with the Session ID when it does exist.
2458
      $sids[$uid] = session_api_get_sid($create);
2459
    }
2460
    else {
2461
      $sids[$uid] = 0;
2462
    }
2463
  }
2464

    
2465
  // Keep the -1 case internal and let the outside world only distinguish two
2466
  // cases: (1) there is an SID; (2) there is no SID (-> 0).
2467
  return $sids[$uid] == -1 ? 0 : $sids[$uid];
2468
}
2469

    
2470
/**
2471
 * Get the Session ID for a user. Utilizes the Session API module.
2472
 *
2473
 * Gets the Session ID for an anonymous user. It will always return 0 for
2474
 * registered users.
2475
 *
2476
 * @param int $uid
2477
 *   (optional) The user ID to return the session ID for. Defaults to the
2478
 *   current user.
2479
 * @param bool $create
2480
 *   (optional) Determines whether a session should be created if it doesn't
2481
 *   exist yet. Defaults to FALSE.
2482
 *
2483
 * @return
2484
 *   The session ID, if the session exists. If not, the return value is 0.
2485
 *
2486
 * @see flag_set_sid()
2487
 */
2488
function flag_get_sid($uid = NULL, $create = FALSE) {
2489
  return flag_set_sid($uid, $create);
2490
}
2491

    
2492
// ---------------------------------------------------------------------------
2493
// Drupal Core operations
2494

    
2495
/**
2496
 * Implements hook_node_operations().
2497
 *
2498
 * Add additional options on the admin/build/node page.
2499
 */
2500
function flag_node_operations() {
2501
  global $user;
2502

    
2503
  $flags = flag_get_flags('node', NULL, $user);
2504
  $operations = array();
2505

    
2506
  foreach ($flags as $flag) {
2507
    $operations['flag_' . $flag->name] = array(
2508
      'label' => $flag->get_label('flag_short'),
2509
      'callback' => 'flag_nodes',
2510
      'callback arguments' => array('flag', $flag->name),
2511
      'behavior' => array(),
2512
    );
2513
    $operations['unflag_' . $flag->name] = array(
2514
      'label' => $flag->get_label('unflag_short'),
2515
      'callback' => 'flag_nodes',
2516
      'callback arguments' => array('unflag', $flag->name),
2517
      'behavior' => array(),
2518
    );
2519
  }
2520
  return $operations;
2521
}
2522

    
2523
/**
2524
 * Callback function for hook_node_operations().
2525
 */
2526
function flag_nodes($nodes, $action, $flag_name) {
2527
  $performed = FALSE;
2528
  foreach ($nodes as $nid) {
2529
    $performed |= flag($action, $flag_name, $nid);
2530
  }
2531
  if ($performed) {
2532
    drupal_set_message(t('The update has been performed.'));
2533
  }
2534
}
2535

    
2536
/**
2537
 * Implements hook_user_operations().
2538
 */
2539
function flag_user_operations() {
2540
  global $user;
2541

    
2542
  $flags = flag_get_flags('user', NULL, $user);
2543
  $operations = array();
2544

    
2545
  foreach ($flags as $flag) {
2546
    $operations['flag_' . $flag->name] = array(
2547
      'label' => $flag->get_label('flag_short'),
2548
      'callback' => 'flag_users',
2549
      'callback arguments' => array('flag', $flag->name),
2550
    );
2551
    $operations['unflag_' . $flag->name] = array(
2552
      'label' => $flag->get_label('unflag_short'),
2553
      'callback' => 'flag_users',
2554
      'callback arguments' => array('unflag', $flag->name),
2555
    );
2556
  }
2557
  return $operations;
2558
}
2559
/**
2560
 * Callback function for hook_user_operations().
2561
 */
2562
function flag_users($users, $action, $flag_name) {
2563
  foreach ($users as $uid) {
2564
    flag($action, $flag_name, $uid);
2565
  }
2566
}
2567

    
2568
// ---------------------------------------------------------------------------
2569
// Contrib integration hooks
2570

    
2571
/**
2572
 * Implements hook_views_api().
2573
 */
2574
function flag_views_api() {
2575
  return array(
2576
    'api' => 3.0,
2577
    'path' => drupal_get_path('module', 'flag') . '/includes/views',
2578
  );
2579
}
2580

    
2581
/**
2582
 * Implements hook_features_api().
2583
 */
2584
function flag_features_api() {
2585
  return array(
2586
    'flag' => array(
2587
      'name' => t('Flag'),
2588
      'feature_source' => TRUE,
2589
      'default_hook' => 'flag_default_flags',
2590
      'file' => drupal_get_path('module', 'flag') . '/includes/flag.features.inc',
2591
    ),
2592
  );
2593
}
2594

    
2595
/**
2596
 * Implements hook_ctools_plugin_directory().
2597
 */
2598
function flag_ctools_plugin_directory($module, $plugin) {
2599
  if ($module == 'ctools' && !empty($plugin)) {
2600
    return "plugins/$plugin";
2601
  }
2602
}
2603

    
2604
/**
2605
 * Implements hook_field_attach_rename_bundle().
2606
 */
2607
function flag_field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) {
2608
  $flags = flag_get_flags($entity_type);
2609
  foreach ($flags as $flag) {
2610
    foreach ($flag->types as $key => $type) {
2611
      if ($type == $bundle_old) {
2612
        $flag->types[$key] = $bundle_new;
2613
      }
2614
    }
2615
    $flag->save();
2616
  }
2617
}
2618

    
2619
// ---------------------------------------------------------------------------
2620
// Entity Metadata callbacks
2621

    
2622
/**
2623
 * Getter callback that returns whether the given entity is flagged.
2624
 */
2625
function flag_properties_get_flagging_boolean($entity, array $options, $name, $entity_type, $property_info) {
2626
  list($entity_id,) = entity_extract_ids($entity_type, $entity);
2627

    
2628
  $flagging_data = flag_get_user_flags($entity_type, $entity_id);
2629
  return isset($flagging_data[$property_info['flag_name']]);
2630
}
2631

    
2632
/**
2633
 * Getter callback that returns entities the given user flagged.
2634
 */
2635
function flag_properties_get_flagged_entities($entity, array $options, $name, $entity_type, $property_info) {
2636
  // Need the entity type the flag applies to.
2637
  $flag_entity_type = $property_info['flag_entity_type'];
2638

    
2639
  $flagging_data = flag_get_user_flags($flag_entity_type, NULL, $entity->uid);
2640

    
2641
  $flag_name = $property_info['flag_name'];
2642
  if (isset($flagging_data[$flag_name])) {
2643
    return array_keys($flagging_data[$flag_name]);
2644
  }
2645
  return array();
2646
}
2647

    
2648
/**
2649
 * Getter callback that returns users who flagged the given entity.
2650
 */
2651
function flag_properties_get_flagging_users($entity, array $options, $name, $entity_type, $property_info) {
2652
  list($entity_id,) = entity_extract_ids($entity_type, $entity);
2653

    
2654
  $flagging_data = flag_get_entity_flags($entity_type, $entity_id, $property_info['flag_name']);
2655

    
2656
  return array_keys($flagging_data);
2657
}
2658

    
2659
/**
2660
 * Getter callback that returns the SID of the user that is being retrieved.
2661
 *
2662
 * Callback for hook_entity_property_info_alter().
2663
 *
2664
 * @param stdobj $entity
2665
 *  The entity object representing a user for which we are getting inforamtion for.
2666
 *
2667
 * @param array $options
2668
 *  Options reguarding the nature of the entity. Language, etc.
2669
 *
2670
 * @param string $name
2671
 *  The name of the property we are running this callback for.
2672
 *
2673
 * @param string $entity_type
2674
 *  The type that the stdobj $entity is supposed to be.
2675
 *
2676
 * @param $property_info
2677
 *  The ifnromatin that represents the property we are providing a result for.
2678
 *
2679
 * @return an integer representing the user's sid field from the session_api table
2680
 *
2681
 * @ingroup callbacks
2682
 */
2683
function flag_properties_get_user_sid($entity, array $options, $name, $entity_type, $property_info) {
2684
  $sid = flag_get_sid($entity->uid, FALSE);
2685
  return $sid;
2686
}