Projet

Général

Profil

Paste
Télécharger (82,9 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / flag / flag.module @ 76df55b7

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
  // Add bundle info but bypass flag_get_flags() as we cannot use it here, as
41
  // it calls entity_get_info().
42
  $result = db_query("SELECT name, title FROM {flag}");
43
  $flag_names = $result->fetchAllKeyed();
44
  foreach ($flag_names as $flag_name => $flag_title) {
45
    $return['flagging']['bundles'][$flag_name] = array(
46
      'label' => $flag_title,
47
      'admin' => array(
48
        'path' => FLAG_ADMIN_PATH . '/manage/%flag',
49
        'real path' => FLAG_ADMIN_PATH . '/manage/' . $flag_name,
50
        'bundle argument' => FLAG_ADMIN_PATH_START + 1,
51
        'access arguments' => array('administer flags'),
52
      ),
53
    );
54
  }
55

    
56
  return $return;
57
}
58

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

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

    
91
  if (!isset($values['flag_name'])) {
92
    if (isset($values['fid'])) {
93
      // Add flag_name, determined from fid.
94
      $flag = flag_get_flag(NULL, $values['fid']);
95
      $values['flag_name'] = $flag->name;
96
    }
97
  }
98

    
99
  // Apply the given values.
100
  foreach ($values as $key => $value) {
101
    $flagging->$key = $value;
102
  }
103

    
104
  return $flagging;
105
}
106

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

    
128
  if (!$flag) {
129
    throw new Exception('Flag not found for flagging entity.');
130
  }
131

    
132
  // Fill in properties that may be omitted.
133
  $flagging->fid = $flag->fid;
134
  $flagging->flag_name = $flag->name;
135

    
136
  if (!empty($flagging->uid)) {
137
    $account = user_load($flagging->uid);
138
  }
139
  else {
140
    $account = NULL;
141
  }
142

    
143
  $result = $flag->flag('flag', $flagging->entity_id, $account, FALSE, $flagging);
144

    
145
  if (!$result) {
146
    throw new Exception('Flag action not allowed for given flagging entity properties.');
147
  }
148
}
149

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

    
152
// @todo: When renaming a flag: Call field_attach_rename_bundle().
153

    
154
// @todo: When creating a flag: Call field_attach_create_bundle().
155

    
156
// @todo: When deleting a flag: Call field_attach_delete_bundle().
157

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

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

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

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

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

    
196
/**
197
 * Implements hook_query_TAG_alter() for flagging_flag_names tag.
198
 *
199
 * @see flag_entity_query_alter()
200
 */
201
function flag_query_flagging_flag_names_alter(QueryAlterableInterface $query) {
202
  // Get value and operator for bundle condition from meta data.
203
  $value = $query->getMetaData('flag_name_value');
204
  $operator = $query->getMetaData('flag_name_operator');
205
  // Join [flag] and [flagging] tables by [fid] and
206
  // apply bundle condition on [flag].[name] field.
207
  $query->join('flag', 'f', 'flagging.fid = f.fid');
208
  $query->condition('f.name', $value, $operator);
209
}
210

    
211
/**
212
 * Implements hook_menu().
213
 */
214
function flag_menu() {
215
  $items[FLAG_ADMIN_PATH] = array(
216
    'title' => 'Flags',
217
    'page callback' => 'flag_admin_page',
218
    'access callback' => 'user_access',
219
    'access arguments' => array('administer flags'),
220
    'description' => 'Configure flags for marking content with arbitrary information (such as <em>offensive</em> or <em>bookmarked</em>).',
221
    'file' => 'includes/flag.admin.inc',
222
  );
223
  $items[FLAG_ADMIN_PATH . '/list'] = array(
224
    'title' => 'List',
225
    'type' => MENU_DEFAULT_LOCAL_TASK,
226
    'weight' => -10,
227
  );
228
  $items[FLAG_ADMIN_PATH . '/add'] = array(
229
    'title' => 'Add flag',
230
    'page callback' => 'flag_add_page',
231
    'access callback' => 'user_access',
232
    'access arguments' => array('administer flags'),
233
    'file' => 'includes/flag.admin.inc',
234
    'type' => MENU_LOCAL_ACTION,
235
    'weight' => 1,
236
  );
237
  $items[FLAG_ADMIN_PATH . '/import'] = array(
238
    'title' => 'Import',
239
    'page callback' => 'drupal_get_form',
240
    'page arguments' => array('flag_import_form'),
241
    'access arguments' => array('use flag import'),
242
    'file' => 'includes/flag.export.inc',
243
    'type' => MENU_LOCAL_ACTION,
244
    'weight' => 2,
245
  );
246
  $items[FLAG_ADMIN_PATH . '/export'] = array(
247
    'title' => 'Export',
248
    'page callback' => 'drupal_get_form',
249
    'page arguments' => array('flag_export_form'),
250
    'access arguments' => array('administer flags'),
251
    'file' => 'includes/flag.export.inc',
252
    'type' => MENU_LOCAL_ACTION,
253
    'weight' => 3,
254
  );
255

    
256
  $items[FLAG_ADMIN_PATH . '/manage/%flag'] = array(
257
    // Allow for disabled flags.
258
    'load arguments' => array(TRUE),
259
    'page callback' => 'drupal_get_form',
260
    'page arguments' => array('flag_form', FLAG_ADMIN_PATH_START + 1),
261
    'access callback' => 'user_access',
262
    'access arguments' => array('administer flags'),
263
    'file' => 'includes/flag.admin.inc',
264
    'type' => MENU_LOCAL_TASK,
265
    // Make the flag title the default title for descendant menu items.
266
    'title callback' => '_flag_menu_title',
267
    'title arguments' => array(FLAG_ADMIN_PATH_START + 1),
268
  );
269
  $items[FLAG_ADMIN_PATH . '/manage/%flag/edit'] = array(
270
    // Allow for disabled flags.
271
    'load arguments' => array(TRUE),
272
    'title' => 'Edit flag',
273
    'type' => MENU_DEFAULT_LOCAL_TASK,
274
    'weight' => -10,
275
  );
276
  $items[FLAG_ADMIN_PATH . '/manage/%flag/export'] = array(
277
    'title' => 'Export',
278
    'page callback' => 'drupal_get_form',
279
    'page arguments' => array('flag_export_form', FLAG_ADMIN_PATH_START + 1),
280
    'access arguments' => array('administer flags'),
281
    'file' => 'includes/flag.export.inc',
282
    'type' => MENU_LOCAL_TASK,
283
    'weight' => 20,
284
  );
285
  $items[FLAG_ADMIN_PATH . '/manage/%flag/delete'] = array(
286
    'title' => 'Delete flag',
287
    'page callback' => 'drupal_get_form',
288
    'page arguments' => array('flag_delete_confirm', FLAG_ADMIN_PATH_START + 1),
289
    'access callback' => 'user_access',
290
    'access arguments' => array('administer flags'),
291
    'file' => 'includes/flag.admin.inc',
292
    'type' => MENU_CALLBACK,
293
  );
294
  $items[FLAG_ADMIN_PATH . '/manage/%flag/update'] = array(
295
    // Allow for disabled flags.
296
    'load arguments' => array(TRUE),
297
    'title' => 'Update',
298
    'page callback' => 'flag_update_page',
299
    'page arguments' => array(FLAG_ADMIN_PATH_START + 1),
300
    'access arguments' => array('administer flags'),
301
    'file' => 'includes/flag.export.inc',
302
    'type' => MENU_CALLBACK,
303
  );
304

    
305
  $items['flag/%/%flag/%'] = array(
306
    'title' => 'Flag',
307
    'page callback' => 'flag_page',
308
    'page arguments' => array(1, 2, 3),
309
    'access callback' => 'user_access',
310
    'access arguments' => array('access content'),
311
    'file' => 'includes/flag.pages.inc',
312
    'type' => MENU_CALLBACK,
313
  );
314
  $items['flag/confirm/%/%flag/%'] = array(
315
    'title' => 'Flag confirm',
316
    'page callback' => 'drupal_get_form',
317
    'page arguments' => array('flag_confirm', 2, 3, 4),
318
    'access callback' => 'user_access',
319
    'access arguments' => array('access content'),
320
    'file' => 'includes/flag.pages.inc',
321
    'type' => MENU_CALLBACK,
322
  );
323

    
324
  return $items;
325
}
326

    
327
/**
328
 * Implements hook_admin_menu_map().
329
 */
330
function flag_admin_menu_map() {
331
  if (!user_access('administer flags')) {
332
    return;
333
  }
334

    
335
  $map = array();
336
  $map[FLAG_ADMIN_PATH . '/manage/%flag'] = array(
337
    'parent' => FLAG_ADMIN_PATH,
338
    'arguments' => array(
339
      array(
340
        '%flag' => array_keys(flag_get_flags()),
341
      ),
342
    ),
343
  );
344

    
345
  return $map;
346
}
347

    
348
/**
349
 * Menu loader for '%flag' arguments.
350
 *
351
 * @param $flag_name
352
 *   The machine name of the flag.
353
 * @param $include_disabled
354
 *   (optional) Whether to return a disabled flag too. Normally only enabled
355
 *   flags are returned. Some menu items operate on disabled flags and in this
356
 *   case you need to turn on this switch by doing:
357
 *   @code
358
 *   'load arguments' => array(TRUE)
359
 *   @endcode
360
 *   in your hook_menu().
361
 *
362
 * @return
363
 *   Either the flag object, or FALSE if none was found.
364
 */
365
function flag_load($flag_name, $include_disabled = FALSE) {
366
  if (($flag = flag_get_flag($flag_name))) {
367
    return $flag;
368
  }
369
  else {
370
    // No enabled flag was found. Search among the disabled ones.
371
    if ($include_disabled) {
372
      $default_flags = flag_get_default_flags(TRUE);
373
      if (isset($default_flags[$flag_name])) {
374
        return $default_flags[$flag_name];
375
      }
376
    }
377
  }
378
  // A menu loader has to return FALSE (not NULL) when no object is found.
379
  return FALSE;
380
}
381

    
382
/**
383
 * Menu title callback.
384
 */
385
function _flag_menu_title($flag) {
386
  // The following conditional it to handle a D7 bug (@todo: link).
387
  return $flag ? $flag->get_title() : '';
388
}
389

    
390
/**
391
 * Implements hook_help().
392
 */
393
function flag_help($path, $arg) {
394
  switch ($path) {
395
    case FLAG_ADMIN_PATH:
396
      $output = '<p>' . t('This page lists all the <em>flags</em> that are currently defined on this system.') . '</p>';
397
      return $output;
398

    
399
    case FLAG_ADMIN_PATH . '/add':
400
      $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>';
401
      return $output;
402

    
403
    case FLAG_ADMIN_PATH . '/manage/%/fields':
404
      // Get the existing link types that provide a flagging form.
405
      $link_types = flag_get_link_types();
406
      $form_link_types = array();
407
      foreach (flag_get_link_types() as $link_type) {
408
        if ($link_type['provides form']) {
409
          $form_link_types[] = '<em>' . $link_type['title'] . '</em>';
410
        }
411
      }
412

    
413
      // Get the flag for which we're managing fields.
414
      $flag = menu_get_object('flag', FLAG_ADMIN_PATH_START + 1);
415

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

    
420
      // Three cases:
421
      if ($flag->link_type == 'form') {
422
        // Case 1: the current link type is the flagging form. Don't tell the
423
        // user anything extra, all is fine.
424
      }
425
      elseif ($link_types[$flag->link_type]['provides form']) {
426
        // Case 2: the current link type shows the form for creation of the
427
        // flagging, but it not the flagging form. Tell the user they can't edit
428
        // existing flagging fields.
429
        $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(
430
          '!flagging-form-url' => 'http://drupal.org/project/flagging_form',
431
        ));
432
        if (!module_exists('flagging_form')) {
433
          $output .= ' <span class="warning">'
434
            . t("You do not currently have this module enabled.")
435
            . '</span>';
436
        }
437
        $output .= '</p>';
438
      }
439
      else {
440
        // Case 3: the current link type does not allow access to the flagging
441
        // form. Tell the user they should change it.
442
        $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(
443
          '!form-link-type-url' => url('admin/structure/flags/manage/' . $flag->name, array('fragment' => 'edit-link-type')),
444
          // The list of labels from link types. These are all defined in code
445
          // in hook_flag_link_type_info() and therefore safe to output raw.
446
          '!link-types-list' => implode(', ', $form_link_types),
447
        )) . '</p>';
448
        $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(
449
          '!flagging-form-url' => 'http://drupal.org/project/flagging_form',
450
        ));
451
        if (!module_exists('flagging_form')) {
452
          $output .= ' <span class="warning">'
453
            . t("You do not currently have this module enabled.")
454
            . '</span>';
455
        }
456
        $output .= '</p>';
457
      }
458

    
459
      return $output;
460
  }
461
}
462

    
463
/**
464
 * Implements hook_init().
465
 */
466
function flag_init() {
467
  module_load_include('inc', 'flag', 'includes/flag.actions');
468
}
469

    
470
/**
471
 * Implements hook_hook_info().
472
 */
473
function flag_hook_info() {
474
  $hooks['flag_type_info'] = array(
475
    'group' => 'flag',
476
  );
477
  $hooks['flag_type_info_alter'] = array(
478
    'group' => 'flag',
479
  );
480
  $hooks['flag_link_type_info'] = array(
481
    'group' => 'flag',
482
  );
483
  $hooks['flag_link_type_info_alter'] = array(
484
    'group' => 'flag',
485
  );
486
  return $hooks;
487
}
488

    
489
/**
490
 * Get a flag type definition.
491
 *
492
 * @param $entity_type
493
 *   (optional) The entity type to get the definition for, or NULL to return
494
 *   all flag types.
495
 *
496
 * @return
497
 *   The flag type definition array.
498
 *
499
 * @see hook_flag_type_info()
500
 */
501
function flag_fetch_definition($entity_type = NULL) {
502
  $definitions = &drupal_static(__FUNCTION__);
503
  if (!isset($definitions)) {
504
    if ($cache = cache_get('flag_type_info')) {
505
      $definitions = $cache->data;
506
    }
507
    else {
508
      $definitions = module_invoke_all('flag_type_info');
509
      drupal_alter('flag_type_info', $definitions);
510

    
511
      cache_set('flag_type_info', $definitions);
512
    }
513
  }
514

    
515
  if (isset($entity_type)) {
516
    if (isset($definitions[$entity_type])) {
517
      return $definitions[$entity_type];
518
    }
519
  }
520
  else {
521
    return $definitions;
522
  }
523
}
524

    
525
/**
526
 * Returns all flag types defined on the system.
527
 *
528
 * @return
529
 *   An array of flag type names.
530
 */
531
function flag_get_types() {
532
  $types = &drupal_static(__FUNCTION__);
533
  if (!isset($types)) {
534
    $types = array_keys(flag_fetch_definition());
535
  }
536
  return $types;
537
}
538

    
539
/**
540
 * Instantiates a new flag handler.
541
 *
542
 * A flag handler is more commonly know as "a flag". A factory method usually
543
 * populates this empty flag with settings loaded from the database.
544
 *
545
 * @param $entity_type
546
 *  The entity type to create a flag handler for. This may be FALSE if the
547
 *  entity type property could not be found in the flag configuration data.
548
 *
549
 * @return
550
 *  A flag handler object. This may be the special class flag_broken is there is
551
 *  a problem with the flag.
552
 */
553
function flag_create_handler($entity_type) {
554
  $definition = flag_fetch_definition($entity_type);
555
  if (isset($definition) && class_exists($definition['handler'])) {
556
    $handler = new $definition['handler']();
557
  }
558
  else {
559
    $handler = new flag_broken();
560
  }
561
  $handler->entity_type = $entity_type;
562
  $handler->construct();
563
  return $handler;
564
}
565

    
566
/**
567
 * Implements hook_permission().
568
 */
569
function flag_permission() {
570
  $permissions = array(
571
    'administer flags' => array(
572
      'title' => t('Administer flags'),
573
      'description' => t('Create and edit site-wide flags.'),
574
    ),
575
    'use flag import' => array(
576
      'title' => t('Use flag importer'),
577
      'description' => t('Access the flag import functionality.'),
578
      'restrict access' => TRUE,
579
    ),
580
  );
581

    
582
  // Reset static cache to ensure all flag permissions are available.
583
  drupal_static_reset('flag_get_flags');
584
  $flags = flag_get_flags();
585
  // Provide flag and unflag permissions for each flag.
586
  foreach ($flags as $flag_name => $flag) {
587
    $permissions += $flag->get_permissions();
588
  }
589

    
590
  return $permissions;
591
}
592

    
593
/**
594
 * Implements hook_form_FORM_ID_alter(): user_admin_permissions.
595
 *
596
 * Disable permission on the permissions form that don't make sense for
597
 * anonymous users when Session API module is not enabled.
598
 */
599
function flag_form_user_admin_permissions_alter(&$form, &$form_state, $form_id) {
600
  if (!module_exists('session_api')) {
601
    $flags = flag_get_flags();
602
    // Disable flag and unflag permission checkboxes for anonymous users.
603
    foreach ($flags as $flag_name => $flag) {
604
      $form['checkboxes'][DRUPAL_ANONYMOUS_RID]["flag $flag_name"]['#disabled'] = TRUE;
605
      $form['checkboxes'][DRUPAL_ANONYMOUS_RID]["unflag $flag_name"]['#disabled'] = TRUE;
606
    }
607
  }
608
}
609

    
610
/**
611
 * Implements hook_flag_link().
612
 */
613
function flag_flag_link($flag, $action, $entity_id) {
614
  $token = flag_get_token($entity_id);
615
  return array(
616
    'href' => 'flag/' . ($flag->link_type == 'confirm' ? 'confirm/' : '') . "$action/$flag->name/$entity_id",
617
    'query' => drupal_get_destination() + ($flag->link_type == 'confirm' ? array() : array('token' => $token)),
618
  );
619
}
620

    
621
/**
622
 * Implements hook_field_extra_fields().
623
 */
624
function flag_field_extra_fields() {
625
  $extra = array();
626

    
627
  $flags = flag_get_flags();
628
  foreach ($flags as $name => $flag) {
629
    // Skip flags that aren't on entities.
630
    if (!($flag instanceof flag_entity)) {
631
      continue;
632
    }
633

    
634
    $applicable_bundles = $flag->types;
635
    // If the list of bundles is empty, it indicates all bundles apply.
636
    if (empty($applicable_bundles)) {
637
      $entity_info = entity_get_info($flag->entity_type);
638
      $applicable_bundles = array_keys($entity_info['bundles']);
639
    }
640

    
641
    foreach ($applicable_bundles as $bundle_name) {
642
      if ($flag->show_on_form) {
643
        $extra[$flag->entity_type][$bundle_name]['form']['flag'] = array(
644
          'label' => t('Flags'),
645
          'description' => t('Checkboxes for toggling flags'),
646
          'weight' => 10,
647
        );
648
      }
649

    
650
      if ($flag->show_as_field) {
651
        $extra[$flag->entity_type][$bundle_name]['display']['flag_' . $name] = array(
652
          // It would be nicer to use % as the placeholder, but the label is
653
          // run through check_plain() by field_ui_display_overview_form()
654
          // (arguably incorrectly; see http://drupal.org/node/1991292).
655
          'label' => t('Flag: @title', array(
656
            '@title' => $flag->title,
657
          )),
658
          'description' => t('Individual flag link'),
659
          'weight' => 10,
660
        );
661
      }
662
    }
663
  }
664

    
665
  return $extra;
666
}
667

    
668
/**
669
 * Implements hook_form_FORM_ID_alter(): node_type_form.
670
 */
671
function flag_form_node_type_form_alter(&$form, &$form_state, $form_id) {
672
  global $user;
673
  $flags = flag_get_flags('node', $form['#node_type']->type, $user);
674
  foreach ($flags as $flag) {
675
    if ($flag->show_on_form) {
676
      // To be able to process node tokens in flag labels, we create a fake
677
      // node and store it in the flag's cache for replace_tokens() to find,
678
      // with a fake ID.
679
      $flag->remember_entity('fake', (object) array(
680
        'nid' => NULL,
681
        'type' => $form['#node_type']->type,
682
        'title' => '',
683
      ));
684
      $var = 'flag_' . $flag->name . '_default';
685
      $form['workflow']['flag'][$var] = array(
686
        '#type' => 'checkbox',
687
        '#title' => $flag->get_label('flag_short', 'fake'),
688
        '#default_value' => variable_get($var . '_' . $form['#node_type']->type, 0),
689
        '#return_value' => 1,
690
      );
691
    }
692
  }
693

    
694
  if (isset($form['workflow']['flag'])) {
695
    $form['workflow']['flag'] += array(
696
      '#type' => 'item',
697
      '#title' => t('Default flags'),
698
      '#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))),
699
      // Make the spacing a bit more compact:
700
      '#prefix' => '<div class="form-checkboxes">',
701
      '#suffix' => '</div>',
702
    );
703
  }
704
}
705

    
706
/**
707
 * Implements hook_field_attach_form().
708
 *
709
 * Handles the 'show_on_form' flag option.
710
 *
711
 * Warning: will not work on entity types that are not fieldable, as this relies
712
 * on a field module hook.
713
 *
714
 * @see flag_field_attach_submit()
715
 */
716
function flag_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
717
  list($id) = entity_extract_ids($entity_type, $entity);
718
  // Some modules are being stupid here. Commerce!
719
  if (empty($id)) {
720
    $id = NULL;
721
  }
722

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

    
727
  // Get all possible flags for this entity type.
728
  $flags = flag_get_flags($entity_type);
729

    
730
  // Filter out flags which need to be included on the node form.
731
  $flags_in_form = 0;
732
  $flags_visible = 0;
733
  foreach ($flags as $flag) {
734
    if (!$flag->show_on_form) {
735
      continue;
736
    }
737

    
738
    // Get the flag status.
739
    if ($is_existing_entity) {
740
      $flag_status = $flag->is_flagged($id);
741
    }
742
    else {
743
      // We don't have per-bundle defaults on general entities yet: default
744
      // status is just unflagged.
745
      $flag_status = FALSE;
746
      // Apply the per-bundle defaults for nodes.
747
      if ($entity_type == 'node') {
748
        $node_type = $entity->type;
749
        $flag_status = variable_get('flag_' . $flag->name . '_default_' . $node_type, 0);
750
      }
751

    
752
      // For a new, unsaved entity, make a dummy entity ID so that the flag
753
      // handler can remember the entity. This allows access to the flag to be
754
      // correctly handled in node and comment preview.
755
      $id = 'new';
756
      $flag->remember_entity($id, $entity);
757
    }
758

    
759
    // If the flag is not global and the user doesn't have access, skip it.
760
    // Global flags have their value set even if the user doesn't have access
761
    // to it, similar to the way "published" and "promote" keep the default
762
    // values even if the user doesn't have "administer nodes" permission.
763
    // Furthermore, a global flag is set to its default value on new nodes
764
    // even if the user creating the node doesn't have access to the flag.
765
    global $user;
766
    $access = $flag->access($id, $flag_status ? 'unflag' : 'flag');
767
    if (!$access && !$flag->global) {
768
      continue;
769
    }
770

    
771
    $form['flag'][$flag->name] = array(
772
      '#type' => 'checkbox',
773
      '#title' => $flag->get_label('flag_short', $id),
774
      '#description' => $flag->get_label('flag_long', $id),
775
      '#default_value' => $flag_status,
776
      '#return_value' => 1,
777
      // Used by our drupalSetSummary() on vertical tabs.
778
      '#attributes' => array('title' => $flag->get_title()),
779
    );
780

    
781
    // If the user does not have access to the flag, set as a value.
782
    if (!$access) {
783
      $form['flag'][$flag->name]['#type'] = 'value';
784
      $form['flag'][$flag->name]['#value'] = $flag_status;
785
    }
786
    else {
787
      $flags_visible++;
788
    }
789
    $flags_in_form++;
790
  }
791

    
792
  if ($flags_in_form) {
793
    $form['flag'] += array(
794
      '#weight' => 1,
795
      '#tree' => TRUE,
796
    );
797
  }
798
  if ($flags_visible) {
799
    $form['flag'] += array(
800
      '#type' => 'fieldset',
801
      '#title' => t('Flags'),
802
      '#collapsible' => TRUE,
803
    );
804

    
805
    if ($entity_type == 'node') {
806
      // Turn the fieldset into a vertical tab.
807
      $form['flag'] += array(
808
        '#group' => 'additional_settings',
809
        '#attributes' => array('class' => array('flag-fieldset')),
810
        '#attached' => array(
811
          'js' => array(
812
            'vertical-tabs' => drupal_get_path('module', 'flag') . '/theme/flag-admin.js',
813
          ),
814
        ),
815
      );
816
    }
817
  }
818
}
819

    
820
/**
821
 * Implements hook_field_attach_submit().
822
 *
823
 * @see flag_field_attach_form()
824
 */
825
function flag_field_attach_submit($entity_type, $entity, $form, &$form_state) {
826
  // This is invoked for each flag_field_attach_form(), but possibly more than
827
  // once for a particular form in the case that a form is showing multiple
828
  // entities (field collection, inline entity form). Hence we can't simply
829
  // assume our submitted form values are in $form_state['values']['flag'].
830
  if (isset($form['flag'])) {
831
    $parents = $form['flag']['#parents'];
832
    $flag_values = drupal_array_get_nested_value($form_state['values'], $parents);
833

    
834
    // Put the form values in the entity so flag_field_attach_save() can find
835
    // them. We can't call flag() here as new entities have no id yet.
836
    $entity->flag = $flag_values;
837
  }
838
}
839

    
840
/**
841
 * Implements hook_field_attach_insert().
842
 */
843
function flag_field_attach_insert($entity_type, $entity) {
844
  if (isset($entity->flag)) {
845
    flag_field_attach_save($entity_type, $entity);
846
  }
847
}
848

    
849
/**
850
 * Implements hook_field_attach_update().
851
 */
852
function flag_field_attach_update($entity_type, $entity) {
853
  if (isset($entity->flag)) {
854
    flag_field_attach_save($entity_type, $entity);
855
  }
856
}
857

    
858
/**
859
 * Shared saving routine between flag_field_attach_insert/update().
860
 *
861
 * @see flag_field_attach_form()
862
 */
863
function flag_field_attach_save($entity_type, $entity) {
864
  list($id) = entity_extract_ids($entity_type, $entity);
865
  // Get the flag values we stashed in the entity in flag_field_attach_submit().
866
  foreach ($entity->flag as $flag_name => $state) {
867
    flag($state ? 'flag' : 'unflag', $flag_name, $id);
868
  }
869
}
870

    
871
/*
872
 * Implements hook_contextual_links_view_alter().
873
 */
874
function flag_contextual_links_view_alter(&$element, $items) {
875
  if (isset($element['#element']['#entity_type'])) {
876
    $entity_type = $element['#element']['#entity_type'];
877

    
878
    // Get the entity out of the element. This requires a bit of legwork.
879
    if (isset($element['#element']['#entity'])) {
880
      // EntityAPI entities will all have the entity in the same place.
881
      $entity = $element['#element']['#entity'];
882
    }
883
    elseif (isset($element['#element']['#' . $entity_type])) {
884
      // Node module at least puts it here.
885
      $entity = $element['#element']['#' . $entity_type];
886
    }
887
    else {
888
      // Give up.
889
      return;
890
    }
891

    
892
    // Get all possible flags for this entity type.
893
    $flags = flag_get_flags($entity_type);
894

    
895
    foreach ($flags as $name => $flag) {
896
      if (!$flag->show_contextual_link) {
897
        continue;
898
      }
899

    
900
      list($entity_id) = entity_extract_ids($entity_type, $entity);
901
      if (!$flag->access($entity_id) && (!$flag->is_flagged($entity_id) || !$flag->access($entity_id, 'flag'))) {
902
        // User has no permission to use this flag or flag does not apply to
903
        // this object. The link is not skipped if the user has "flag" access
904
        // but not "unflag" access (this way the unflag denied message is
905
        // shown).
906
        continue;
907
      }
908

    
909
      $element['#links']['flag-' . $name] = array(
910
        'title' => $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id),
911
        'html' => TRUE,
912
      );
913
    }
914
  }
915
}
916

    
917
/**
918
 * Implements hook_entity_view().
919
 *
920
 * Handles the 'show_in_links' and 'show_as_field' flag options.
921
 *
922
 * Note this is broken for taxonomy terms for version of Drupal core < 7.17.
923
 */
924
function flag_entity_view($entity, $type, $view_mode, $langcode) {
925
  // Get all possible flags for this entity type.
926
  $flags = flag_get_flags($type);
927
  foreach ($flags as $flag) {
928
    // Check if the flag outputs on entity view.
929
    if (!($flag->show_as_field || $flag->shows_in_entity_links($view_mode))) {
930
      // Flag is not configured to output on entity view, so skip it to save on
931
      // calls to access checks.
932
      continue;
933
    }
934

    
935
    $entity_id = $flag->get_entity_id($entity);
936
    // For a new, unsaved entity, make a dummy entity ID so that the flag
937
    // handler can remember the entity. This allows access to the flag to be
938
    // correctly handled in node and comment preview.
939
    if (is_null($entity_id)) {
940
      $entity_id = 'new';
941
    }
942
    $flag->remember_entity($entity_id, $entity);
943

    
944
    if (!$flag->access($entity_id) && (!$flag->is_flagged($entity_id) || !$flag->access($entity_id, 'flag'))) {
945
      // User has no permission to use this flag or flag does not apply to this
946
      // entity. The link is not skipped if the user has "flag" access but
947
      // not "unflag" access (this way the unflag denied message is shown).
948
      continue;
949
    }
950

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

    
953
    // The old-style entity links output.
954
    if ($flag->shows_in_entity_links($view_mode)) {
955
      // The flag links are actually fully rendered theme functions.
956
      // The HTML attribute is set to TRUE to allow whatever the themer desires.
957
      $links['flag-' . $flag->name] = array(
958
        'title' => $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id),
959
        'html' => TRUE,
960
      );
961
    }
962

    
963
    // The pseudofield output.
964
    if ($flag->show_as_field) {
965
      $entity->content['flag_' . $flag->name] = array(
966
        '#markup' => $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id, array('needs_wrapping_element' => TRUE)),
967
      );
968
    }
969
  }
970

    
971
  // If any links were made, add them to the entity's links array.
972
  if (isset($links)) {
973
    $entity->content['links']['flag'] = array(
974
      '#theme' => 'links',
975
      '#links' => $links,
976
      '#attributes' => array('class' => array('links', 'inline')),
977
    );
978
  }
979
}
980

    
981
/**
982
 * Implements hook_node_insert().
983
 */
984
function flag_node_insert($node) {
985
  flag_node_save($node);
986
}
987

    
988
/**
989
 * Implements hook_node_update().
990
 */
991
function flag_node_update($node) {
992
  flag_node_save($node);
993
}
994

    
995
/**
996
 * Shared saving routine between flag_node_insert() and flag_node_update().
997
 */
998
function flag_node_save($node) {
999
  // Response to the flag checkboxes added to the form in flag_form_alter().
1000
  $remembered = FALSE;
1001
  if (isset($node->flag)) {
1002
    foreach ($node->flag as $name => $state) {
1003
      $flag = flag_get_flag($name);
1004
      // Flagging may trigger actions. We want actions to get the current
1005
      // node, not a stale database-loaded one:
1006
      if (!$remembered) {
1007
        $flag->remember_entity($node->nid, $node);
1008
        // Actions may modify a node, and we don't want to overwrite this
1009
        // modification:
1010
        $remembered = TRUE;
1011
      }
1012

    
1013
      $action = $state ? 'flag' : 'unflag';
1014
      // Pass TRUE for $skip_permission_check so that flags that have been
1015
      // passed through as hidden form values are saved.
1016
      $flag->flag($action, $node->nid, NULL, TRUE);
1017
    }
1018
  }
1019
}
1020

    
1021
/**
1022
 * Implements hook_entity_delete().
1023
 */
1024
function flag_entity_delete($entity, $type) {
1025
  // Node and user flags handle things through the entity type delete hooks.
1026
  // @todo: make this configurable in the flag type definition?
1027
  if ($type == 'node' || $type == 'user') {
1028
    return;
1029
  }
1030

    
1031
  list($id) = entity_extract_ids($type, $entity);
1032
  _flag_entity_delete($type, $id);
1033
}
1034

    
1035
/**
1036
 * Implements hook_node_delete().
1037
 */
1038
function flag_node_delete($node) {
1039
  foreach (flag_get_flags('node') as $flag) {
1040
    // If the flag is being tracked by translation set and the node is part
1041
    // of a translation set, don't delete the flagging record.
1042
    // Instead, data will be updated in hook_node_translation_change(), below.
1043
    if (!$flag->i18n || empty($node->tnid)) {
1044
      _flag_entity_delete('node', $node->nid, $flag->fid);
1045
    }
1046
  }
1047
}
1048

    
1049
/**
1050
 * Implements hook_node_translation_change().
1051
 *
1052
 * (Hook provided by translation_helpers module.)
1053
 */
1054
function flag_node_translation_change($node) {
1055
  if (isset($node->translation_change)) {
1056
    // If there is only one node remaining, track by nid rather than tnid.
1057
    // Otherwise, use the new tnid.
1058
    $entity_id = $node->translation_change['new_tnid'] == 0 ? $node->translation_change['remaining_nid'] : $node->translation_change['new_tnid'];
1059
    foreach (flag_get_flags('node') as $flag) {
1060
      if ($flag->i18n) {
1061
        db_update('flagging')->fields(array('entity_id' => $entity_id))
1062
          ->condition('fid', $flag->fid)
1063
          ->condition('entity_id', $node->translation_change['old_tnid'])
1064
          ->execute();
1065
        db_update('flag_counts')->fields(array('entity_id' => $entity_id))
1066
          ->condition('fid', $flag->fid)
1067
          ->condition('entity_id', $node->translation_change['old_tnid'])
1068
          ->execute();
1069
      }
1070
    }
1071
  }
1072
}
1073

    
1074
/**
1075
 * Deletes flagging records for the entity.
1076
 *
1077
 * @param $entity_type
1078
 *   The type of the entity being deleted; e.g. 'node' or 'comment'.
1079
 * @param $entity_id
1080
 *   The ID of the entity being deleted.
1081
 * @param $fid
1082
 *   The flag id
1083
 */
1084
function _flag_entity_delete($entity_type, $entity_id, $fid = NULL) {
1085
  $query_content = db_delete('flagging')
1086
    ->condition('entity_type', $entity_type)
1087
    ->condition('entity_id', $entity_id);
1088
  $query_counts = db_delete('flag_counts')
1089
    ->condition('entity_type', $entity_type)
1090
    ->condition('entity_id', $entity_id);
1091
  if (isset($fid)) {
1092
    $query_content->condition('fid', $fid);
1093
    $query_counts->condition('fid', $fid);
1094
  }
1095
  $query_content->execute();
1096
  $query_counts->execute();
1097
}
1098

    
1099
/**
1100
 * Implements hook_user_login().
1101
 */
1102
function flag_user_login(&$edit, &$account) {
1103
  // Migrate anonymous flags to this user's account.
1104
  if (module_exists('session_api') && ($sid = flag_get_sid(0))) {
1105
    // Get a list of flagging IDs that will be moved over.
1106
    $duplicate_flaggings = array();
1107
    $flaggings = db_select('flagging', 'fc')
1108
      ->fields('fc', array('flagging_id', 'fid', 'entity_id'))
1109
      ->condition('uid', 0)
1110
      ->condition('sid', $sid)
1111
      ->execute()
1112
      ->fetchAllAssoc('flagging_id', PDO::FETCH_ASSOC);
1113

    
1114
    // Convert anonymous flaggings to their authenticated account.
1115
    foreach ($flaggings as $flagging_id => $flagging) {
1116
      // Each update is wrapped in a try block to prevent unique key errors.
1117
      // Any duplicate object that was flagged as anonoymous is deleted in the
1118
      // subsequent db_delete() call.
1119
      try {
1120
        db_update('flagging')
1121
          ->fields(array(
1122
            'uid' => $account->uid,
1123
            'sid' => 0,
1124
          ))
1125
          ->condition('flagging_id', $flagging_id)
1126
          ->execute();
1127
      }
1128
      catch (Exception $e) {
1129
        $duplicate_flaggings[$flagging_id] = $flagging;
1130
      }
1131
    }
1132

    
1133
    // Delete any remaining flags this user had as an anonymous user. We use the
1134
    // proper unflag action here to make sure the count gets decremented again
1135
    // and so that other modules can clean up their tables if needed.
1136
    $anonymous_user = drupal_anonymous_user();
1137
    foreach ($duplicate_flaggings as $flagging_id => $flagging) {
1138
      $flag = flag_get_flag(NULL, $flagging['fid']);
1139
      $flag->flag('unflag', $flagging['entity_id'], $anonymous_user, TRUE);
1140
    }
1141

    
1142
    // Clean up anonymous cookies.
1143
    FlagCookieStorage::drop();
1144
  }
1145
}
1146

    
1147
/**
1148
 * Implements hook_user_cancel().
1149
 */
1150
function flag_user_cancel($edit, $account, $method) {
1151
  flag_user_account_removal($account);
1152
}
1153

    
1154
/**
1155
 * Implements hook_user_delete().
1156
 */
1157
function flag_user_delete($account) {
1158
  flag_user_account_removal($account);
1159
}
1160

    
1161
/**
1162
 * Shared helper for user account cancellation or deletion.
1163
 */
1164
function flag_user_account_removal($account) {
1165
  // Remove flags by this user.
1166
  $query = db_select('flagging', 'fc');
1167
  $query->leftJoin('flag_counts', 'c', 'fc.entity_id = c.entity_id AND fc.entity_type = c.entity_type AND fc.fid = c.fid');
1168
  $result = $query
1169
    ->fields('fc', array('fid', 'entity_id'))
1170
    ->fields('c', array('count'))
1171
    ->condition('fc.uid', $account->uid)
1172
    ->execute();
1173

    
1174
  foreach ($result as $flag_data) {
1175
    // Only decrement the flag count table if it's greater than 1.
1176
    if ($flag_data->count > 0) {
1177
      $flag_data->count--;
1178
      db_update('flag_counts')
1179
        ->fields(array(
1180
          'count' => $flag_data->count,
1181
        ))
1182
        ->condition('fid', $flag_data->fid)
1183
        ->condition('entity_id', $flag_data->entity_id)
1184
        ->execute();
1185
    }
1186
    elseif ($flag_data->count == 0) {
1187
      db_delete('flag_counts')
1188
        ->condition('fid', $flag_data->fid)
1189
        ->condition('entity_id', $flag_data->entity_id)
1190
        ->execute();
1191
    }
1192
  }
1193
  db_delete('flagging')
1194
    ->condition('uid', $account->uid)
1195
    ->execute();
1196

    
1197
  // Remove flags that have been done to this user.
1198
  _flag_entity_delete('user', $account->uid);
1199
}
1200

    
1201
/**
1202
 * Implements hook_user_view().
1203
 */
1204
function flag_user_view($account, $view_mode) {
1205
  $flags = flag_get_flags('user');
1206
  $flag_items = array();
1207
  foreach ($flags as $flag) {
1208
    if (!$flag->access($account->uid)) {
1209
      // User has no permission to use this flag.
1210
      continue;
1211
    }
1212
    if (!$flag->show_on_profile) {
1213
      // Flag not set to appear on profile.
1214
      continue;
1215
    }
1216
    $flag_items[$flag->name] = array(
1217
      '#type' => 'user_profile_item',
1218
      '#title' => $flag->get_title($account->uid),
1219
      '#markup' => $flag->theme($flag->is_flagged($account->uid) ? 'unflag' : 'flag', $account->uid),
1220
      '#attributes' => array('class' => array('flag-profile-' . $flag->name)),
1221
    );
1222
  }
1223
  if (!empty($flag_items)) {
1224
    $account->content['flags'] = $flag_items;
1225
    $account->content['flags'] += array(
1226
      '#type' => 'user_profile_category',
1227
      '#title' => t('Actions'),
1228
      '#attributes' => array('class' => array('flag-profile')),
1229
    );
1230
  }
1231
}
1232

    
1233
/**
1234
 * Implements hook_session_api_cleanup().
1235
 *
1236
 * Clear out anonymous user flaggings during Session API cleanup.
1237
 */
1238
function flag_session_api_cleanup($arg = 'run') {
1239
  // Session API 1.1 version:
1240
  if ($arg == 'run') {
1241
    $query = db_select('flagging', 'fc');
1242
    $query->leftJoin('session_api', 's', 'fc.sid = s.sid');
1243
    $result = $query
1244
      ->fields('fc', array('sid'))
1245
      ->condition('fc.sid', 0, '<>')
1246
      ->isNull('s.sid')
1247
      ->execute();
1248
    foreach ($result as $row) {
1249
      db_delete('flagging')
1250
        ->condition('sid', $row->sid)
1251
        ->execute();
1252
    }
1253
  }
1254
  // Session API 1.2+ version.
1255
  elseif (is_array($arg)) {
1256
    $outdated_sids = $arg;
1257
    db_delete('flagging')->condition('sid', $outdated_sids, 'IN')->execute();
1258
  }
1259
}
1260

    
1261
/**
1262
 * Implements hook_field_attach_delete_bundle().
1263
 *
1264
 * Delete any flags' applicability to the deleted bundle.
1265
 */
1266
function flag_field_attach_delete_bundle($entity_type, $bundle, $instances) {
1267
  // This query can't use db_delete() because that doesn't support a
1268
  // subquery: see http://drupal.org/node/1267508.
1269
  db_query("DELETE FROM {flag_types} WHERE type = :bundle AND fid IN (SELECT fid FROM {flag} WHERE entity_type = :entity_type)", array(
1270
    ':bundle' => $bundle,
1271
    ':entity_type' => $entity_type,
1272
  ));
1273
}
1274

    
1275
/**
1276
 * Flags or unflags an item.
1277
 *
1278
 * @param $action
1279
 *   Either 'flag' or 'unflag'.
1280
 * @param $flag_name
1281
 *   The name of the flag to use.
1282
 * @param $entity_id
1283
 *   The ID of the item to flag or unflag.
1284
 * @param $account
1285
 *   (optional) The user on whose behalf to flag. Omit for the current user.
1286
 * @param permissions_check
1287
 *   (optional) A boolean indicating whether to skip permissions.
1288
 *
1289
 * @return
1290
 *   FALSE if some error occured (e.g., user has no permission, flag isn't
1291
 *   applicable to the item, etc.), TRUE otherwise.
1292
 */
1293
function flag($action, $flag_name, $entity_id, $account = NULL, $permissions_check = FALSE) {
1294
  if (!($flag = flag_get_flag($flag_name))) {
1295
    // Flag does not exist.
1296
    return FALSE;
1297
  }
1298
  return $flag->flag($action, $entity_id, $account, $permissions_check);
1299
}
1300

    
1301
/**
1302
 * Implements hook_flag_flag().
1303
 */
1304
function flag_flag_flag($flag, $entity_id, $account, $flagging) {
1305
  if (module_exists('trigger')) {
1306
    flag_flag_trigger('flag', $flag, $entity_id, $account, $flagging);
1307
  }
1308
}
1309

    
1310
/**
1311
 * Implements hook_flag_unflag().
1312
 */
1313
function flag_flag_unflag($flag, $entity_id, $account, $flagging) {
1314
  if (module_exists('trigger')) {
1315
    flag_flag_trigger('unflag', $flag, $entity_id, $account, $flagging);
1316
  }
1317
}
1318

    
1319
/**
1320
 * Trigger actions if any are available. Helper for hook_flag_(un)flag().
1321
 *
1322
 * @param $op
1323
 *  The operation being performed: one of 'flag' or 'unflag'.
1324
 * @param $flag
1325
 *  The flag object.
1326
 * @param $entity_id
1327
 *  The id of the entity the flag is on.
1328
 * @param $account
1329
 *  The user account performing the action.
1330
 * @param $flagging_id
1331
 *  The flagging entity.
1332
 */
1333
function flag_flag_trigger($action, $flag, $entity_id, $account, $flagging) {
1334
  $context['hook'] = 'flag';
1335
  $context['account'] = $account;
1336
  $context['flag'] = $flag;
1337
  $context['op'] = $action;
1338
  // We add to the $context all the objects we know about:
1339
  $context = array_merge($flag->get_relevant_action_objects($entity_id), $context);
1340
  // The primary object the actions work on.
1341
  $object = $flag->fetch_entity($entity_id);
1342

    
1343
  // Generic "all flags" actions.
1344
  foreach (trigger_get_assigned_actions('flag_' . $action) as $aid => $action_info) {
1345
    // The 'if ($aid)' is a safeguard against
1346
    // http://drupal.org/node/271460#comment-886564
1347
    if ($aid) {
1348
      actions_do($aid, $object, $context);
1349
    }
1350
  }
1351
  // Actions specifically for this flag.
1352
  foreach (trigger_get_assigned_actions('flag_' . $action . '_' . $flag->name) as $aid => $action_info) {
1353
    if ($aid) {
1354
      actions_do($aid, $object, $context);
1355
    }
1356
  }
1357
}
1358

    
1359
/**
1360
 * Implements hook_flag_access().
1361
 */
1362
function flag_flag_access($flag, $entity_id, $action, $account) {
1363
  // Do nothing if there is no restriction by authorship.
1364
  if (empty($flag->access_author)) {
1365
    return;
1366
  }
1367

    
1368
  // Restrict access by authorship. It's important that TRUE is never returned
1369
  // here, otherwise we'd grant permission even if other modules denied access.
1370
  if ($flag->entity_type == 'node') {
1371
    // For non-existent nodes (such as on the node add form), assume that the
1372
    // current user is creating the content.
1373
    if (empty($entity_id) || !($node = $flag->fetch_entity($entity_id))) {
1374
      return $flag->access_author == 'others' ? FALSE : NULL;
1375
    }
1376

    
1377
    if ($flag->access_author == 'own' && $node->uid != $account->uid) {
1378
      return FALSE;
1379
    }
1380
    elseif ($flag->access_author == 'others' && $node->uid == $account->uid) {
1381
      return FALSE;
1382
    }
1383
  }
1384

    
1385
  // Restrict access by comment authorship.
1386
  if ($flag->entity_type == 'comment') {
1387
    // For non-existent comments (such as on the comment add form), assume that
1388
    // the current user is creating the content.
1389
    if (empty($entity_id) || !($comment = $flag->fetch_entity($entity_id))) {
1390
      return $flag->access_author == 'comment_others' ? FALSE : NULL;
1391
    }
1392

    
1393
    $node = node_load($comment->nid);
1394
    if ($flag->access_author == 'node_own' && $node->uid != $account->uid) {
1395
      return FALSE;
1396
    }
1397
    elseif ($flag->access_author == 'node_others' && $node->uid == $account->uid) {
1398
      return FALSE;
1399
    }
1400
    elseif ($flag->access_author == 'comment_own' && $comment->uid != $account->uid) {
1401
      return FALSE;
1402
    }
1403
    elseif ($flag->access_author == 'comment_others' && $comment->uid == $account->uid) {
1404
      return FALSE;
1405
    }
1406
  }
1407
}
1408

    
1409
/**
1410
 * Implements hook_flag_access_multiple().
1411
 */
1412
function flag_flag_access_multiple($flag, $entity_ids, $account) {
1413
  $access = array();
1414

    
1415
  // Do nothing if there is no restriction by authorship.
1416
  if (empty($flag->access_author)) {
1417
    return $access;
1418
  }
1419

    
1420
  if ($flag->entity_type == 'node') {
1421
    // Restrict access by authorship. This is similar to flag_flag_access()
1422
    // above, but returns an array of 'nid' => $access values. Similarly, we
1423
    // should never return TRUE in any of these access values, only FALSE if we
1424
    // want to deny access, or use the current access value provided by Flag.
1425
    $result = db_select('node', 'n')
1426
      ->fields('n', array('nid', 'uid'))
1427
      ->condition('nid', array_keys($entity_ids), 'IN')
1428
      ->condition('type', $flag->types, 'IN')
1429
      ->execute();
1430
    foreach ($result as $row) {
1431
      if ($flag->access_author == 'own') {
1432
        $access[$row->nid] = $row->uid != $account->uid ? FALSE : NULL;
1433
      }
1434
      elseif ($flag->access_author == 'others') {
1435
        $access[$row->nid] = $row->uid == $account->uid ? FALSE : NULL;
1436
      }
1437
    }
1438
  }
1439

    
1440
  if ($flag->entity_type == 'comment') {
1441
    // Restrict access by comment ownership.
1442
    $query = db_select('comment', 'c');
1443
    $query->leftJoin('node', 'n', 'c.nid = n.nid');
1444
    $query
1445
      ->fields('c', array('cid', 'nid', 'uid'))
1446
      ->condition('c.cid', $entity_ids, 'IN');
1447
    $query->addField('c', 'uid', 'comment_uid');
1448
    $result = $query->execute();
1449

    
1450
    foreach ($result as $row) {
1451
      if ($flag->access_author == 'node_own') {
1452
        $access[$row->cid] = $row->node_uid != $account->uid ? FALSE : NULL;
1453
      }
1454
      elseif ($flag->access_author == 'node_others') {
1455
        $access[$row->cid] = $row->node_uid == $account->uid ? FALSE : NULL;
1456
      }
1457
      elseif ($flag->access_author == 'comment_own') {
1458
        $access[$row->cid] = $row->comment_uid != $account->uid ? FALSE : NULL;
1459
      }
1460
      elseif ($flag->access_author == 'comment_others') {
1461
        $access[$row->cid] = $row->comment_uid == $account->uid ? FALSE : NULL;
1462
      }
1463
    }
1464
  }
1465

    
1466
  // Always return an array (even if empty) of accesses.
1467
  return $access;
1468
}
1469

    
1470
/**
1471
 * Implements hook_theme().
1472
 */
1473
function flag_theme() {
1474
  $path = drupal_get_path('module', 'flag') . '/theme';
1475

    
1476
  return array(
1477
    'flag' => array(
1478
      'variables' => array(
1479
        'flag' => NULL,
1480
        'action' => NULL,
1481
        'entity_id' => NULL,
1482
        'after_flagging' => FALSE,
1483
        'needs_wrapping_element' => FALSE,
1484
        'errors' => array(),
1485
      ),
1486
      'template' => 'flag',
1487
      'pattern' => 'flag__',
1488
      'path' => $path,
1489
    ),
1490
    'flag_tokens_browser' => array(
1491
      'variables' => array(
1492
        'types' => array('all'),
1493
        'global_types' => TRUE,
1494
      ),
1495
      'file' => 'flag.tokens.inc',
1496
    ),
1497
    'flag_admin_listing' => array(
1498
      'render element' => 'form',
1499
      'file' => 'includes/flag.admin.inc',
1500
    ),
1501
    'flag_admin_listing_disabled' => array(
1502
      'variables' => array(
1503
        'flags' => NULL,
1504
        'default_flags' => NULL,
1505
      ),
1506
      'file' => 'includes/flag.admin.inc',
1507
    ),
1508
    'flag_admin_page' => array(
1509
      'variables' => array(
1510
        'flags' => NULL,
1511
        'default_flags' => NULL,
1512
        'flag_admin_listing' => NULL,
1513
      ),
1514
      'file' => 'includes/flag.admin.inc',
1515
    ),
1516
    'flag_form_roles' => array(
1517
      'render element' => 'element',
1518
      'file' => 'includes/flag.admin.inc',
1519
    ),
1520
  );
1521
}
1522

    
1523
/**
1524
 * A preprocess function for our theme('flag'). It generates the
1525
 * variables needed there.
1526
 *
1527
 * The $variables array initially contains the following arguments:
1528
 * - $flag
1529
 * - $action
1530
 * - $entity_id
1531
 * - $after_flagging
1532
 * - $errors
1533
 * - $needs_wrapping_element
1534
 *
1535
 * See 'flag.tpl.php' for their documentation.
1536
 */
1537
function template_preprocess_flag(&$variables) {
1538
  global $user;
1539
  $initialized = &drupal_static(__FUNCTION__, array());
1540

    
1541
  // Some typing shotcuts:
1542
  $flag =& $variables['flag'];
1543
  $action = $variables['action'];
1544
  $entity_id = $variables['entity_id'];
1545
  $errors = implode('<br />', $variables['errors']);
1546
  $flag_name_css = str_replace('_', '-', $flag->name);
1547

    
1548
  // Generate the link URL.
1549
  $link_type = $flag->get_link_type();
1550
  $link = module_invoke($link_type['module'], 'flag_link', $flag, $action, $entity_id);
1551
  if (isset($link['title']) && empty($link['html'])) {
1552
    $link['title'] = check_plain($link['title']);
1553
  }
1554

    
1555
  // Replace the link with the access denied text if unable to flag.
1556
  if ($action == 'unflag' && !$flag->access($entity_id, 'unflag')) {
1557
    $link['title'] = $flag->get_label('unflag_denied_text', $entity_id);
1558
    unset($link['href']);
1559
  }
1560

    
1561
  // Anonymous users always need the JavaScript to maintain their flag state.
1562
  if ($user->uid == 0) {
1563
    $link_type['uses standard js'] = TRUE;
1564
  }
1565

    
1566
  // Load the JavaScript/CSS, if the link type requires it.
1567
  if (!isset($initialized[$link_type['name']])) {
1568
    if ($link_type['uses standard css']) {
1569
      drupal_add_css(drupal_get_path('module', 'flag') . '/theme/flag.css');
1570
    }
1571
    if ($link_type['uses standard js']) {
1572
      drupal_add_js(drupal_get_path('module', 'flag') . '/theme/flag.js');
1573
    }
1574
    $initialized[$link_type['name']] = TRUE;
1575
  }
1576

    
1577
  $variables['link'] = $link;
1578
  $variables['link_href'] = isset($link['href']) ? check_url(url($link['href'], $link)) : FALSE;
1579
  $variables['link_text'] = isset($link['title']) ? $link['title'] : $flag->get_label($action . '_short', $entity_id);
1580
  $variables['link_title'] = isset($link['attributes']['title']) ? check_plain($link['attributes']['title']) : check_plain(strip_tags($flag->get_label($action . '_long', $entity_id)));
1581
  $variables['status'] = ($action == 'flag' ? 'unflagged' : 'flagged');
1582
  $variables['flag_name_css'] = $flag_name_css;
1583

    
1584
  $variables['flag_wrapper_classes_array'] = array();
1585
  $variables['flag_wrapper_classes_array'][] = 'flag-wrapper';
1586
  $variables['flag_wrapper_classes_array'][] = 'flag-' . $flag_name_css;
1587
  $variables['flag_wrapper_classes_array'][] = 'flag-' . $flag_name_css . '-' . $entity_id;
1588

    
1589
  $variables['flag_classes_array'] = array();
1590
  $variables['flag_classes_array'][] = 'flag';
1591
  if (isset($link['href'])) {
1592
    $variables['flag_classes_array'][] = $variables['action'] . '-action';
1593
    $variables['flag_classes_array'][] = 'flag-link-' . $flag->link_type;
1594
  }
1595
  else {
1596
    $variables['flag_classes_array'][] = $variables['action'] . '-disabled';
1597
  }
1598
  if (isset($link['attributes']['class'])) {
1599
    $link['attributes']['class'] = is_string($link['attributes']['class']) ? array_filter(explode(' ', $link['attributes']['class'])) : $link['attributes']['class'];
1600
    $variables['flag_classes_array'] = array_merge($variables['flag_classes_array'], $link['attributes']['class']);
1601
  }
1602
  $variables['message_classes_array'] = array();
1603
  if ($variables['after_flagging']) {
1604
    $variables['message_classes_array'][] = 'flag-message';
1605
    if ($errors) {
1606
      $variables['message_classes_array'][] = 'flag-failure-message';
1607
      $variables['message_text'] = $errors;
1608
    }
1609
    else {
1610
      $inverse_action = ($action == 'flag' ? 'unflag' : 'flag');
1611
      $variables['message_classes_array'][] = 'flag-success-message';
1612
      $variables['message_classes_array'][] = 'flag-' . $variables['status'] . '-message';
1613
      $variables['message_text'] = $flag->get_label($inverse_action . '_message', $entity_id);
1614
      $variables['flag_classes_array'][] = $variables['status'];
1615
      // By default we make our JS code remove, after a few seconds, only
1616
      // success messages.
1617
      $variables['message_classes_array'][] = 'flag-auto-remove';
1618
    }
1619
  }
1620
  else {
1621
    $variables['message_text'] = '';
1622
  }
1623
}
1624

    
1625
/**
1626
 * Theme processor for flag.tpl.php.
1627
 *
1628
 * @param array &$variables
1629
 *  An array of variables for the template. See 'flag.tpl.php' for their
1630
 *  documentation.
1631
 */
1632
function template_process_flag(&$variables) {
1633
  // Convert class arrays to strings.
1634
  $variables['flag_wrapper_classes'] = implode(' ', $variables['flag_wrapper_classes_array']);
1635
  $variables['flag_classes'] = implode(' ', $variables['flag_classes_array']);
1636
  $variables['message_classes'] = implode(' ', $variables['message_classes_array']);
1637
}
1638

    
1639
/**
1640
 * Return an array of flag names keyed by fid.
1641
 */
1642
function _flag_get_flag_names() {
1643
  $flags = flag_get_flags();
1644
  $flag_names = array();
1645
  foreach ($flags as $flag) {
1646
    $flag_names[$flag->fid] = $flag->name;
1647
  }
1648
  return $flag_names;
1649
}
1650

    
1651
/**
1652
 * Return an array of flag link types suitable for a select list or radios.
1653
 */
1654
function _flag_link_type_options() {
1655
  $options = array();
1656
  $types = flag_get_link_types();
1657
  foreach ($types as $type_name => $type) {
1658
    $options[$type_name] = $type['title'];
1659
  }
1660
  return $options;
1661
}
1662

    
1663
/**
1664
 * Return an array of flag link type descriptions.
1665
 */
1666
function _flag_link_type_descriptions() {
1667
  $options = array();
1668
  $types = flag_get_link_types();
1669
  foreach ($types as $type_name => $type) {
1670
    $options[$type_name] = $type['description'];
1671
  }
1672
  return $options;
1673
}
1674

    
1675
// ---------------------------------------------------------------------------
1676
// Non-Views public API
1677

    
1678
/**
1679
 * Get the count of flags for a particular entity type.
1680
 *
1681
 * When called during a flagging or unflagging (such as from a hook
1682
 * implementation or from Rules), the flagging or unflagging that is in the
1683
 * process of being performed:
1684
 *  - will be included during a flagging operation
1685
 *  - will STILL be included during an unflagging operation. That is, the count
1686
 *    will not yet have been decreased.
1687
 * This is because this queries the {flagging} table, which only has its record
1688
 * deleted at the very end of the unflagging process.
1689
 *
1690
 * @param $flag
1691
 *   The flag.
1692
 * @param $entity_type
1693
 *   The entity type. For example, 'node'.
1694
 *
1695
 * @return
1696
 *   The flag count with the flag name and entity type as the array key.
1697
 */
1698
function flag_get_entity_flag_counts($flag, $entity_type) {
1699
  $counts = &drupal_static(__FUNCTION__);
1700

    
1701
  // We check to see if the flag count is already in the cache,
1702
  // if it's not, run the query.
1703
  if (!isset($counts[$flag->name][$entity_type])) {
1704
    $counts[$flag->name][$entity_type] = array();
1705
    $result = db_select('flagging', 'f')
1706
      ->fields('f', array('fid'))
1707
      ->condition('fid', $flag->fid)
1708
      ->condition('entity_type', $entity_type)
1709
      ->countQuery()
1710
      ->execute()
1711
      ->fetchField();
1712
    $counts[$flag->name][$entity_type] = $result;
1713
  }
1714

    
1715
  return $counts[$flag->name][$entity_type];
1716
}
1717

    
1718
/**
1719
 * Get the user's flag count.
1720
 *
1721
 * When called during a flagging or unflagging (such as from a hook
1722
 * implementation or from Rules), the flagging or unflagging that is in the
1723
 * process of being performed:
1724
 *  - will be included during a flagging operation
1725
 *  - will STILL be included during an unflagging operation. That is, the count
1726
 *    will not yet have been decreased.
1727
 * This is because this queries the {flagging} table, which only has its record
1728
 * deleted at the very end of the unflagging process.
1729
 *
1730
 * @param $flag
1731
 *   The flag.
1732
 * @param $user
1733
 *   The user object.
1734
 *
1735
 * @return
1736
 *   The flag count with the flag name and the uid as the array key.
1737
 */
1738
function flag_get_user_flag_counts($flag, $user) {
1739
  $counts = &drupal_static(__FUNCTION__);
1740

    
1741
  // We check to see if the flag count is already in the cache,
1742
  // if it's not, run the query.
1743
  if (!isset($counts[$flag->name][$user->uid])) {
1744
    $counts[$flag->name][$user->uid] = array();
1745
    $result = db_select('flagging', 'f')
1746
      ->fields('f', array('fid'))
1747
      ->condition('fid', $flag->fid)
1748
      ->condition('uid', $user->uid)
1749
      ->countQuery()
1750
      ->execute()
1751
      ->fetchField();
1752
    $counts[$flag->name][$user->uid] = $result;
1753
  }
1754

    
1755
  return $counts[$flag->name][$user->uid];
1756
}
1757

    
1758
/**
1759
 * Get flag counts for all flags on a node.
1760
 *
1761
 * When called during a flagging or unflagging (such as from a hook
1762
 * implementation or from Rules), the count this returns takes into account the
1763
 * the flagging or unflagging that is in the process of being performed.
1764
 *
1765
 * @param $entity_type
1766
 *   The entity type (usually 'node').
1767
 * @param $entity_id
1768
 *   The entity ID (usually the node ID).
1769
 *
1770
 * @return
1771
 *   The flag count with the flag names as array keys.
1772
 */
1773
function flag_get_counts($entity_type, $entity_id) {
1774
  $counts = &drupal_static(__FUNCTION__);
1775

    
1776
  if (!isset($counts[$entity_type][$entity_id])) {
1777
    $counts[$entity_type][$entity_id] = array();
1778
    $query = db_select('flag', 'f');
1779
    $query->leftJoin('flag_counts', 'fc', 'f.fid = fc.fid');
1780
    $result = $query
1781
      ->fields('f', array('name'))
1782
      ->fields('fc', array('count'))
1783
      ->condition('fc.entity_type', $entity_type)
1784
      ->condition('fc.entity_id', $entity_id)
1785
      ->execute();
1786
    foreach ($result as $row) {
1787
      $counts[$entity_type][$entity_id][$row->name] = $row->count;
1788
    }
1789
  }
1790

    
1791
  return $counts[$entity_type][$entity_id];
1792
}
1793

    
1794
/**
1795
 * Get the total count of items flagged within a flag.
1796
 *
1797
 * When called during a flagging or unflagging (such as from a hook
1798
 * implementation or from Rules), the count this returns takes into account the
1799
 * the flagging or unflagging that is in the process of being performed.
1800
 *
1801
 * @param $flag_name
1802
 *   The flag name for which to retrieve a flag count.
1803
 * @param $reset
1804
 *   (optional) Reset the internal cache and execute the SQL query another time.
1805
 */
1806
function flag_get_flag_counts($flag_name, $reset = FALSE) {
1807
  $counts = &drupal_static(__FUNCTION__);
1808

    
1809
  if ($reset) {
1810
    $counts = array();
1811
  }
1812
  if (!isset($counts[$flag_name])) {
1813
    $flag = flag_get_flag($flag_name);
1814
    $counts[$flag_name] = db_select('flag_counts', 'fc')
1815
      ->fields('fc', array('fid'))
1816
      ->condition('fid', $flag->fid)
1817
      ->countQuery()
1818
      ->execute()
1819
      ->fetchField();
1820
  }
1821

    
1822
  return $counts[$flag_name];
1823
}
1824

    
1825
/**
1826
 * Load a single flag either by name or by flag ID.
1827
 *
1828
 * @param $name
1829
 *  (optional) The flag name.
1830
 * @param $fid
1831
 *  (optional) The the flag id.
1832
 *
1833
 * @return
1834
 *  The flag object, or FALSE if no matching flag was found.
1835
 */
1836
function flag_get_flag($name = NULL, $fid = NULL) {
1837
  $flags = flag_get_flags();
1838
  if (isset($name)) {
1839
    if (isset($flags[$name])) {
1840
      return $flags[$name];
1841
    }
1842
  }
1843
  elseif (isset($fid)) {
1844
    foreach ($flags as $flag) {
1845
      if ($flag->fid == $fid) {
1846
        return $flag;
1847
      }
1848
    }
1849
  }
1850
  return FALSE;
1851
}
1852

    
1853
/**
1854
 * List all flags available.
1855
 *
1856
 * If node type or account are entered, a list of all possible flags will be
1857
 * returned.
1858
 *
1859
 * @param $entity_type
1860
 *   (optional) The type of entity for which to load the flags. Usually 'node'.
1861
 * @param $content_subtype
1862
 *   (optional) The node type for which to load the flags.
1863
 * @param $account
1864
 *   (optional) The user accont to filter available flags. If not set, all
1865
 *   flags for will this node will be returned.
1866
 *
1867
 * @return
1868
 *   An array of the structure [fid] = flag_object.
1869
 */
1870
function flag_get_flags($entity_type = NULL, $content_subtype = NULL, $account = NULL) {
1871
  $flags = &drupal_static(__FUNCTION__);
1872

    
1873
  // Retrieve a list of all flags, regardless of the parameters.
1874
  if (!isset($flags)) {
1875
    $flags = array();
1876

    
1877
    // Database flags.
1878
    $query = db_select('flag', 'f');
1879
    $query->leftJoin('flag_types', 'fn', 'fn.fid = f.fid');
1880
    $result = $query
1881
      ->fields('f', array(
1882
        'fid',
1883
        'entity_type',
1884
        'name',
1885
        'title',
1886
        'global',
1887
        'options',
1888
      ))
1889
      ->fields('fn', array('type'))
1890
      ->execute();
1891
    foreach ($result as $row) {
1892
      if (!isset($flags[$row->name])) {
1893
        $flags[$row->name] = flag_flag::factory_by_row($row);
1894
      }
1895
      else {
1896
        $flags[$row->name]->types[] = $row->type;
1897
      }
1898
    }
1899

    
1900
    // Add code-based flags provided by modules.
1901
    $default_flags = flag_get_default_flags();
1902
    foreach ($default_flags as $name => $default_flag) {
1903
      // Insert new enabled flags into the database to give them an FID.
1904
      if ($default_flag->status && !isset($flags[$name])) {
1905
        $default_flag->save();
1906
        $flags[$name] = $default_flag;
1907
      }
1908

    
1909
      if (isset($flags[$name])) {
1910
        // Ensure overridden flags are associated with their parent module.
1911
        $flags[$name]->module = $default_flag->module;
1912

    
1913
        // Update the flag with any properties that are "locked" by the code
1914
        // version.
1915
        if (isset($default_flag->locked)) {
1916
          $flags[$name]->locked = $default_flag->locked;
1917
          foreach ($default_flag->locked as $property) {
1918
            $flags[$name]->$property = $default_flag->$property;
1919
          }
1920
        }
1921
      }
1922
    }
1923

    
1924
    // Sort the list of flags by weight.
1925
    uasort($flags, '_flag_compare_weight');
1926

    
1927
    foreach ($flags as $flag) {
1928
      // Allow modules implementing hook_flag_alter(&$flag) to modify each flag.
1929
      drupal_alter('flag', $flag);
1930
    }
1931
  }
1932

    
1933
  // Make a variable copy to filter types and account.
1934
  $filtered_flags = $flags;
1935

    
1936
  // Filter out flags based on type and subtype.
1937
  if (isset($entity_type) || isset($content_subtype)) {
1938
    foreach ($filtered_flags as $name => $flag) {
1939
      if (!$flag->access_entity_enabled($entity_type, $content_subtype)) {
1940
        unset($filtered_flags[$name]);
1941
      }
1942
    }
1943
  }
1944

    
1945
  // Filter out flags based on account permissions.
1946
  if (isset($account) && $account->uid != 1) {
1947
    foreach ($filtered_flags as $name => $flag) {
1948
      // We test against the 'flag' action, which is the minimum permission to
1949
      // use a flag.
1950
      if (!$flag->user_access('flag', $account)) {
1951
        unset($filtered_flags[$name]);
1952
      }
1953
    }
1954
  }
1955

    
1956
  return $filtered_flags;
1957
}
1958

    
1959
/**
1960
 * Comparison function for uasort().
1961
 */
1962
function _flag_compare_weight($flag1, $flag2) {
1963
  if ($flag1->weight == $flag2->weight) {
1964
    return 0;
1965
  }
1966
  return $flag1->weight < $flag2->weight ? -1 : 1;
1967
}
1968

    
1969
/**
1970
 * Retrieve a list of flags defined by modules.
1971
 *
1972
 * @param $include_disabled
1973
 *   (optional) Unless specified, only enabled flags will be returned.
1974
 *
1975
 * @return
1976
 *   An array of flag prototypes, not usable for flagging. Use flag_get_flags()
1977
 *   if needing to perform a flagging with any enabled flag.
1978
 */
1979
function flag_get_default_flags($include_disabled = FALSE) {
1980
  $default_flags = array();
1981
  $flag_status = variable_get('flag_default_flag_status', array());
1982

    
1983
  $default_flags_info = array();
1984
  foreach (module_implements('flag_default_flags') as $module) {
1985
    $function = $module . '_flag_default_flags';
1986
    foreach ($function() as $flag_name => $flag_info) {
1987
      // Backward compatibility: old exported default flags have their names
1988
      // in $flag_info instead, so we use the + operator to not overwrite it.
1989
      $default_flags_info[$flag_name] = $flag_info + array(
1990
        'name' => $flag_name,
1991
        'module' => $module,
1992
      );
1993
    }
1994
  }
1995

    
1996
  // Allow modules to alter definitions using hook_flag_default_flags_alter().
1997
  drupal_alter('flag_default_flags', $default_flags_info);
1998

    
1999
  foreach ($default_flags_info as $flag_info) {
2000
    $flag = flag_flag::factory_by_array($flag_info);
2001

    
2002
    // Disable flags that are not at the current API version.
2003
    if (!$flag->is_compatible()) {
2004
      $flag->status = FALSE;
2005
    }
2006

    
2007
    // Add flags that have been enabled.
2008
    if ((!isset($flag_status[$flag->name]) && (!isset($flag->status) || $flag->status)) || !empty($flag_status[$flag->name])) {
2009
      $flag->status = TRUE;
2010
      $default_flags[$flag->name] = $flag;
2011
    }
2012
    // Add flags that have been disabled.
2013
    elseif ($include_disabled) {
2014
      $flag->status = FALSE;
2015
      $default_flags[$flag->name] = $flag;
2016
    }
2017
  }
2018

    
2019
  return $default_flags;
2020
}
2021

    
2022
/**
2023
 * Get all flagged entities in a flag.
2024
 *
2025
 * @param $flag_name
2026
 *   The flag name for which to retrieve flagged entites.
2027
 *
2028
 * @return
2029
 *   An array of flagging data, keyed by the flagging ID.
2030
 */
2031
function flag_get_flag_flagging_data($flag_name) {
2032
  $flag = flag_get_flag($flag_name);
2033
  $result = db_select('flagging', 'fc')
2034
    ->fields('fc')
2035
    ->condition('fid', $flag->fid)
2036
    ->execute();
2037
  return $result->fetchAllAssoc('flagging_id');
2038
}
2039

    
2040
/**
2041
 * Find what a user has flagged, either a single entity or on the entire site.
2042
 *
2043
 * When called during a flagging or unflagging (such as from a hook
2044
 * implementation or from Rules), the flagging or unflagging that is in the
2045
 * process of being performed:
2046
 *  - will be included during a flagging operation
2047
 *  - will STILL be included during an unflagging operation. That is, the count
2048
 *    will not yet have been decreased.
2049
 * This is because this queries the {flagging} table, which only has its record
2050
 * deleted at the very end of the unflagging process.
2051
 *
2052
 * @param $entity_type
2053
 *   The type of entity that will be retrieved. Usually 'node'.
2054
 * @param $entity_id
2055
 *   (optional) The entity ID to check for flagging. If none given, all
2056
 *   entities flagged by this user will be returned.
2057
 * @param $uid
2058
 *   (optional) The user ID whose flags we're checking. If none given, the
2059
 *   current user will be used.
2060
 * @param $sid
2061
 *   (optional) The user SID (provided by Session API) whose flags we're
2062
 *   checking. If none given, the current user will be used. The SID is 0 for
2063
 *   logged in users.
2064
 *
2065
 * @return
2066
 *   If returning a single item's flags (that is, when $entity_id isn't NULL),
2067
 *   an array of the structure
2068
 *   [flag_name] => (
2069
 *     flagging_id => [flagging_id],
2070
 *     uid => [uid],
2071
 *     entity_id => [entity_id],
2072
 *     timestamp => [timestamp],
2073
 *     ...)
2074
 *
2075
 *   If returning all items' flags, an array of arrays for each flag:
2076
 *   [flag_name] => [entity_id] => Object from above.
2077
 */
2078
function flag_get_user_flags($entity_type, $entity_id = NULL, $uid = NULL, $sid = NULL) {
2079
  $flagged_content = &drupal_static(__FUNCTION__);
2080

    
2081
  $uid = !isset($uid) ? $GLOBALS['user']->uid : $uid;
2082
  $sid = !isset($sid) ? flag_get_sid($uid) : $sid;
2083

    
2084
  if (isset($entity_id)) {
2085
    if (!isset($flagged_content[$uid][$sid][$entity_type][$entity_id])) {
2086
      $flag_names = _flag_get_flag_names();
2087
      $flagged_content[$uid][$sid][$entity_type][$entity_id] = array();
2088
      $result = db_select('flagging', 'fc')
2089
        ->fields('fc')
2090
        ->condition('entity_type', $entity_type)
2091
        ->condition('entity_id', $entity_id)
2092
        ->condition(db_or()
2093
          ->condition('uid', $uid)
2094
          ->condition('uid', 0)
2095
        )
2096
        ->condition('sid', $sid)
2097
        ->execute();
2098

    
2099
      foreach ($result as $flagging_data) {
2100
        $flagged_content[$uid][$sid][$entity_type][$entity_id][$flag_names[$flagging_data->fid]] = $flagging_data;
2101
      }
2102
    }
2103
    return $flagged_content[$uid][$sid][$entity_type][$entity_id];
2104
  }
2105

    
2106
  else {
2107
    if (!isset($flagged_content[$uid][$sid][$entity_type]['all'])) {
2108
      $flag_names = _flag_get_flag_names();
2109
      $flagged_content[$uid][$sid][$entity_type]['all'] = array();
2110
      $result = db_select('flagging', 'fc')
2111
        ->fields('fc')
2112
        ->condition('entity_type', $entity_type)
2113
        ->condition(db_or()
2114
          ->condition('uid', $uid)
2115
          ->condition('uid', 0)
2116
        )
2117
        ->condition('sid', $sid)
2118
        ->execute();
2119
      foreach ($result as $flagging_data) {
2120
        $flagged_content[$uid][$sid][$entity_type]['all'][$flag_names[$flagging_data->fid]][$flagging_data->entity_id] = $flagging_data;
2121
      }
2122
    }
2123
    return $flagged_content[$uid][$sid][$entity_type]['all'];
2124
  }
2125

    
2126
}
2127

    
2128
/**
2129
 * Return a list of users who have flagged an entity.
2130
 *
2131
 * When called during a flagging or unflagging (such as from a hook
2132
 * implementation or from Rules), the flagging or unflagging that is in the
2133
 * process of being performed:
2134
 *  - will be included during a flagging operation
2135
 *  - will STILL be included during an unflagging operation. That is, the count
2136
 *    will not yet have been decreased.
2137
 * This is because this queries the {flagging} table, which only has its record
2138
 * deleted at the very end of the unflagging process.
2139
 *
2140
 * @param $entity_type
2141
 *   The type of entity that will be retrieved. Usually 'node'.
2142
 * @param $entity_id
2143
 *   The entity ID to check for flagging.
2144
 * @param $flag_name
2145
 *   (optional) The name of a flag if wanting a list specific to a single flag.
2146
 *
2147
 * @return
2148
 *   A nested array of flagging records (i.e. rows from the {flagging} table,
2149
 *   rather than complete Flagging entities). The structure depends on the
2150
 *   presence of the $flag_name parameter:
2151
 *    - if $flag_name is omitted, the array is keyed first by the user ID of
2152
 *      the users that flagged the entity, then by flag name. Each value is
2153
 *      then the flagging record.
2154
 *    - if $flag_name is given, the array is keyed only by user ID. Each value
2155
 *      is the flagging record.
2156
 *   If no flags were found an empty array is returned.
2157
 */
2158
function flag_get_entity_flags($entity_type, $entity_id, $flag_name = NULL) {
2159
  $entity_flags = &drupal_static(__FUNCTION__, array());
2160

    
2161
  if (!isset($entity_flags[$entity_type][$entity_id])) {
2162
    $flag_names = _flag_get_flag_names();
2163
    $result = db_select('flagging', 'fc')
2164
      ->fields('fc')
2165
      ->condition('entity_type', $entity_type)
2166
      ->condition('entity_id', $entity_id)
2167
      ->orderBy('timestamp', 'DESC')
2168
      ->execute();
2169
    $entity_flags[$entity_type][$entity_id] = array();
2170
    foreach ($result as $flagging_data) {
2171
      // Build a list of flaggings for all flags by user.
2172
      $entity_flags[$entity_type][$entity_id]['users'][$flagging_data->uid][$flag_names[$flagging_data->fid]] = $flagging_data;
2173
      // Build a list of flaggings for each individual flag.
2174
      $entity_flags[$entity_type][$entity_id]['flags'][$flag_names[$flagging_data->fid]][$flagging_data->uid] = $flagging_data;
2175
    }
2176
  }
2177
  if (empty($entity_flags[$entity_type][$entity_id])) {
2178
    return array();
2179
  }
2180
  if (isset($flag_name)) {
2181
    if (isset($entity_flags[$entity_type][$entity_id]['flags'][$flag_name])) {
2182
      return $entity_flags[$entity_type][$entity_id]['flags'][$flag_name];
2183
    }
2184
    return array();
2185
  }
2186
  return $entity_flags[$entity_type][$entity_id]['users'];
2187
}
2188

    
2189
/**
2190
 * A utility function for outputting a flag link.
2191
 *
2192
 * You should call this function from your template when you want to put the
2193
 * link on the page yourself. For example, you could call this function from
2194
 * your theme preprocessor for node.tpl.php:
2195
 * @code
2196
 * $variables['my_flag_link'] = flag_create_link('bookmarks', $node->nid);
2197
 * @endcode
2198
 *
2199
 * @param $flag_name
2200
 *   The "machine readable" name of the flag; e.g. 'bookmarks'.
2201
 * @param $entity_id
2202
 *   The entity ID to check for flagging, for example a node ID.
2203
 * @param $variables
2204
 *  An array of further variables to pass to theme('flag'). For the full list
2205
 *  of parameters, see flag.tpl.php. Of particular interest:
2206
 *  - after_flagging: Set to TRUE if this flag link is being displayed as the
2207
 *    result of a flagging action.
2208
 *  - errors: An array of error messages.
2209
 *
2210
 * @return
2211
 *   The HTML for the themed flag link.
2212
 */
2213
function flag_create_link($flag_name, $entity_id, $variables = array()) {
2214
  $flag = flag_get_flag($flag_name);
2215
  if (!$flag) {
2216
    // Flag does not exist.
2217
    return;
2218
  }
2219
  if (!$flag->access($entity_id) && (!$flag->is_flagged($entity_id) || !$flag->access($entity_id, 'flag'))) {
2220
    // User has no permission to use this flag.
2221
    return;
2222
  }
2223
  return $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id, $variables);
2224
}
2225

    
2226
/**
2227
 * Trim a flag to a certain size.
2228
 *
2229
 * @param $fid
2230
 *   The flag object.
2231
 * @param $account
2232
 *   The user object on behalf the trimming will occur.
2233
 * @param $cutoff_size
2234
 *   The number of flaggings allowed. Any flaggings beyond that will be trimmed.
2235
 * @param $trim_newest
2236
 *   An optional boolean indicating whether to trim the newest flags.
2237
 * @param $permissions_check
2238
 *   (optional) A boolean indicating whether to skip permissions.
2239
 *   This will trim the flag if $permissions_check is TRUE even if the user
2240
 *   doesn't have the permission to flag/unflag.
2241
 */
2242
function flag_trim_flag($flag, $account, $cutoff_size, $trim_newest, $permissions_check = FALSE) {
2243
  $query = db_select('flagging', 'fc')
2244
    ->fields('fc')
2245
    ->condition('fid', $flag->fid)
2246
    ->condition(db_or()->condition('uid', $account->uid)->condition('uid', 0))
2247
    // Account for session ID (in the case of anonymous users).
2248
    ->condition('sid', flag_get_sid($account->uid));
2249
  // If $trim_newest is TRUE, then, we should order by 'ASC' as we should trim
2250
  // the newest flags.
2251
  if ($trim_newest) {
2252
    $query->orderBy('timestamp', 'ASC');
2253
  }
2254
  else {
2255
    $query->orderBy('timestamp', 'DESC');
2256
  }
2257

    
2258
  // Execute the query.
2259
  $result = $query->execute();
2260

    
2261
  $i = 1;
2262
  foreach ($result as $row) {
2263
    if ($i++ > $cutoff_size) {
2264
      flag('unflag', $flag->name, $row->entity_id, $account, $permissions_check);
2265
    }
2266
  }
2267
}
2268

    
2269
/**
2270
 * Remove all flagged entities from a flag.
2271
 *
2272
 * @param $flag
2273
 *   The flag object.
2274
 * @param $entity_id
2275
 *   (optional) The entity ID on which all flaggings will be removed. If left
2276
 *   empty, this will remove all of this flag's entities.
2277
 */
2278
function flag_reset_flag($flag, $entity_id = NULL) {
2279
  $query = db_select('flagging', 'fc')
2280
    ->fields('fc')
2281
    ->condition('fid', $flag->fid);
2282

    
2283
  if ($entity_id) {
2284
    $query->condition('entity_id', $entity_id);
2285
  }
2286

    
2287
  $result = $query->execute()->fetchAllAssoc('flagging_id', PDO::FETCH_ASSOC);
2288
  $rows = array();
2289
  foreach ($result as $row) {
2290
    $rows[] = $row;
2291
  }
2292
  module_invoke_all('flag_reset', $flag, $entity_id, $rows);
2293

    
2294
  $query = db_delete('flagging')->condition('fid', $flag->fid);
2295
  // Update the flag_counts table.
2296
  $count_query = db_delete('flag_counts')->condition('fid', $flag->fid);
2297
  if ($entity_id) {
2298
    $query->condition('entity_id', $entity_id);
2299
    $count_query->condition('entity_id', $entity_id);
2300
  }
2301
  $count_query->execute();
2302
  return $query->execute();
2303
}
2304

    
2305
/**
2306
 * Return an array of link types provided by modules.
2307
 *
2308
 * @return
2309
 *  An array of link types as defined by hook_flag_link_type_info(). These are
2310
 *  keyed by the type name, and each value is an array of properties. In
2311
 *  addition to those defined in hook_flag_link_type_info(), the following
2312
 *  properties are set:
2313
 *  - 'module': The providing module.
2314
 *  - 'name': The machine name of the type.
2315
 *
2316
 * @see hook_flag_link_type_info()
2317
 * @see hook_flag_link_type_info_alter()
2318
 */
2319
function flag_get_link_types() {
2320
  $link_types = &drupal_static(__FUNCTION__);
2321

    
2322
  if (!isset($link_types)) {
2323
    if ($cache = cache_get('flag_link_type_info')) {
2324
      $link_types = $cache->data;
2325
    }
2326
    // In some rare edge cases cache_get() can return an empty result. If it
2327
    // does, we make sure to fetch the link types again.
2328
    if (empty($link_types)) {
2329
      $link_types = array();
2330
      foreach (module_implements('flag_link_type_info') as $module) {
2331
        $module_types = module_invoke($module, 'flag_link_type_info');
2332
        foreach ($module_types as $type_name => $info) {
2333
          $link_types[$type_name] = $info + array(
2334
            'module' => $module,
2335
            'name' => $type_name,
2336
            'title' => '',
2337
            'description' => '',
2338
            'options' => array(),
2339
            'uses standard js' => TRUE,
2340
            'uses standard css' => TRUE,
2341
            'provides form' => FALSE,
2342
          );
2343
        }
2344
      }
2345
      drupal_alter('flag_link_type_info', $link_types);
2346

    
2347
      cache_set('flag_link_type_info', $link_types);
2348
    }
2349
  }
2350

    
2351
  return $link_types;
2352
}
2353

    
2354
/**
2355
 * Get a private token used to protect links from spoofing - CSRF.
2356
 */
2357
function flag_get_token($entity_id) {
2358
  // Anonymous users get a less secure token, since it must be the same for all
2359
  // anonymous users on the entire site to work with page caching.
2360
  return ($GLOBALS['user']->uid) ? drupal_get_token($entity_id) : md5(drupal_get_private_key() . $entity_id);
2361
}
2362

    
2363
/**
2364
 * Check to see if a token value matches the specified node.
2365
 */
2366
function flag_check_token($token, $entity_id) {
2367
  return flag_get_token($entity_id) == $token;
2368
}
2369

    
2370
/**
2371
 * Set the Session ID for a user. Utilizes the Session API module.
2372
 *
2373
 * Creates a Session ID for an anonymous user and returns it. It will always
2374
 * return 0 for registered users.
2375
 *
2376
 * @param int $uid
2377
 *   (optional) The user ID to create a session ID for. Defaults to the
2378
 *   current user.
2379
 * @param bool $create
2380
 *   (optional) Determines whether a session should be created if it doesn't
2381
 *   exist yet. Defaults to TRUE.
2382
 *
2383
 * @return
2384
 *   The session ID, if a session was created. If not, the return value is 0.
2385
 *
2386
 * @see flag_get_sid()
2387
 */
2388
function flag_set_sid($uid = NULL, $create = TRUE) {
2389
  $sids = &drupal_static(__FUNCTION__, array());
2390

    
2391
  if (!isset($uid)) {
2392
    $uid = $GLOBALS['user']->uid;
2393
  }
2394

    
2395
  // Set the sid if none has been set yet. If the caller specified to create an
2396
  // sid and we have an invalid one (-1), create it.
2397
  if (!isset($sids[$uid]) || ($sids[$uid] == -1 && $create)) {
2398
    if (module_exists('session_api') && session_api_available() && $uid == 0) {
2399
      // This returns one of the following:
2400
      // - -1. This indicates that no session exists and none was created.
2401
      // - A positive integer with the Session ID when it does exist.
2402
      $sids[$uid] = session_api_get_sid($create);
2403
    }
2404
    else {
2405
      $sids[$uid] = 0;
2406
    }
2407
  }
2408

    
2409
  // Keep the -1 case internal and let the outside world only distinguish two
2410
  // cases: (1) there is an SID; (2) there is no SID (-> 0).
2411
  return $sids[$uid] == -1 ? 0 : $sids[$uid];
2412
}
2413

    
2414
/**
2415
 * Get the Session ID for a user. Utilizes the Session API module.
2416
 *
2417
 * Gets the Session ID for an anonymous user. It will always return 0 for
2418
 * registered users.
2419
 *
2420
 * @param int $uid
2421
 *   (optional) The user ID to return the session ID for. Defaults to the
2422
 *   current user.
2423
 * @param bool $create
2424
 *   (optional) Determines whether a session should be created if it doesn't
2425
 *   exist yet. Defaults to FALSE.
2426
 *
2427
 * @return
2428
 *   The session ID, if the session exists. If not, the return value is 0.
2429
 *
2430
 * @see flag_set_sid()
2431
 */
2432
function flag_get_sid($uid = NULL, $create = FALSE) {
2433
  return flag_set_sid($uid, $create);
2434
}
2435

    
2436
// ---------------------------------------------------------------------------
2437
// Drupal Core operations
2438

    
2439
/**
2440
 * Implements hook_node_operations().
2441
 *
2442
 * Add additional options on the admin/build/node page.
2443
 */
2444
function flag_node_operations() {
2445
  global $user;
2446

    
2447
  $flags = flag_get_flags('node', NULL, $user);
2448
  $operations = array();
2449

    
2450
  foreach ($flags as $flag) {
2451
    $operations['flag_' . $flag->name] = array(
2452
      'label' => $flag->get_label('flag_short'),
2453
      'callback' => 'flag_nodes',
2454
      'callback arguments' => array('flag', $flag->name),
2455
      'behavior' => array(),
2456
    );
2457
    $operations['unflag_' . $flag->name] = array(
2458
      'label' => $flag->get_label('unflag_short'),
2459
      'callback' => 'flag_nodes',
2460
      'callback arguments' => array('unflag', $flag->name),
2461
      'behavior' => array(),
2462
    );
2463
  }
2464
  return $operations;
2465
}
2466

    
2467
/**
2468
 * Callback function for hook_node_operations().
2469
 */
2470
function flag_nodes($nodes, $action, $flag_name) {
2471
  $performed = FALSE;
2472
  foreach ($nodes as $nid) {
2473
    $performed |= flag($action, $flag_name, $nid);
2474
  }
2475
  if ($performed) {
2476
    drupal_set_message(t('The update has been performed.'));
2477
  }
2478
}
2479

    
2480
/**
2481
 * Implements hook_user_operations().
2482
 */
2483
function flag_user_operations() {
2484
  global $user;
2485

    
2486
  $flags = flag_get_flags('user', NULL, $user);
2487
  $operations = array();
2488

    
2489
  foreach ($flags as $flag) {
2490
    $operations['flag_' . $flag->name] = array(
2491
      'label' => $flag->get_label('flag_short'),
2492
      'callback' => 'flag_users',
2493
      'callback arguments' => array('flag', $flag->name),
2494
    );
2495
    $operations['unflag_' . $flag->name] = array(
2496
      'label' => $flag->get_label('unflag_short'),
2497
      'callback' => 'flag_users',
2498
      'callback arguments' => array('unflag', $flag->name),
2499
    );
2500
  }
2501
  return $operations;
2502
}
2503
/**
2504
 * Callback function for hook_user_operations().
2505
 */
2506
function flag_users($users, $action, $flag_name) {
2507
  foreach ($users as $uid) {
2508
    flag($action, $flag_name, $uid);
2509
  }
2510
}
2511

    
2512
// ---------------------------------------------------------------------------
2513
// Contrib integration hooks
2514

    
2515
/**
2516
 * Implements hook_views_api().
2517
 */
2518
function flag_views_api() {
2519
  return array(
2520
    'api' => 3.0,
2521
    'path' => drupal_get_path('module', 'flag') . '/includes/views',
2522
  );
2523
}
2524

    
2525
/**
2526
 * Implements hook_features_api().
2527
 */
2528
function flag_features_api() {
2529
  return array(
2530
    'flag' => array(
2531
      'name' => t('Flag'),
2532
      'feature_source' => TRUE,
2533
      'default_hook' => 'flag_default_flags',
2534
      'file' => drupal_get_path('module', 'flag') . '/includes/flag.features.inc',
2535
    ),
2536
  );
2537
}
2538

    
2539
/**
2540
 * Implements hook_ctools_plugin_directory().
2541
 */
2542
function flag_ctools_plugin_directory($module, $plugin) {
2543
  if ($module == 'ctools' && !empty($plugin)) {
2544
    return "plugins/$plugin";
2545
  }
2546
}
2547

    
2548
// ---------------------------------------------------------------------------
2549
// Entity Metadata callbacks
2550

    
2551
/**
2552
 * Getter callback that returns whether the given entity is flagged.
2553
 */
2554
function flag_properties_get_flagging_boolean($entity, array $options, $name, $entity_type, $property_info) {
2555
  list($entity_id,) = entity_extract_ids($entity_type, $entity);
2556

    
2557
  $flagging_data = flag_get_user_flags($entity_type, $entity_id);
2558
  return isset($flagging_data[$property_info['flag_name']]);
2559
}
2560

    
2561
/**
2562
 * Getter callback that returns entities the given user flagged.
2563
 */
2564
function flag_properties_get_flagged_entities($entity, array $options, $name, $entity_type, $property_info) {
2565
  // Need the entity type the flag applies to.
2566
  $flag_entity_type = $property_info['flag_entity_type'];
2567

    
2568
  $flagging_data = flag_get_user_flags($flag_entity_type, NULL, $entity->uid);
2569

    
2570
  $flag_name = $property_info['flag_name'];
2571
  if (isset($flagging_data[$flag_name])) {
2572
    return array_keys($flagging_data[$flag_name]);
2573
  }
2574
  return array();
2575
}
2576

    
2577
/**
2578
 * Getter callback that returns users who flagged the given entity.
2579
 */
2580
function flag_properties_get_flagging_users($entity, array $options, $name, $entity_type, $property_info) {
2581
  list($entity_id,) = entity_extract_ids($entity_type, $entity);
2582

    
2583
  $flagging_data = flag_get_entity_flags($entity_type, $entity_id, $property_info['flag_name']);
2584

    
2585
  return array_keys($flagging_data);
2586
}
2587

    
2588
/**
2589
 * Getter callback that returns the SID of the user that is being retrieved.
2590
 *
2591
 * Callback for hook_entity_property_info_alter().
2592
 *
2593
 * @param stdobj $entity
2594
 *  The entity object representing a user for which we are getting inforamtion for.
2595
 *
2596
 * @param array $options
2597
 *  Options reguarding the nature of the entity. Language, etc.
2598
 *
2599
 * @param string $name
2600
 *  The name of the property we are running this callback for.
2601
 *
2602
 * @param string $entity_type
2603
 *  The type that the stdobj $entity is supposed to be.
2604
 *
2605
 * @param $property_info
2606
 *  The ifnromatin that represents the property we are providing a result for.
2607
 *
2608
 * @return an integer representing the user's sid field from the session_api table
2609
 *
2610
 * @ingroup callbacks
2611
 */
2612
function flag_properties_get_user_sid($entity, array $options, $name, $entity_type, $property_info) {
2613
  $sid = flag_get_sid($entity->uid, FALSE);
2614
  return $sid;
2615
}