Projet

Général

Profil

Paste
Télécharger (83,7 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / flag / flag.module @ 87dbc3bf

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
    'type' => MENU_NORMAL_ITEM,
223
  );
224
  $items[FLAG_ADMIN_PATH . '/list'] = array(
225
    'title' => 'List',
226
    'type' => MENU_DEFAULT_LOCAL_TASK,
227
    'weight' => -10,
228
  );
229
  $items[FLAG_ADMIN_PATH . '/add'] = array(
230
    'title' => 'Add flag',
231
    'page callback' => 'flag_add_page',
232
    'access callback' => 'user_access',
233
    'access arguments' => array('administer flags'),
234
    'file' => 'includes/flag.admin.inc',
235
    'type' => MENU_LOCAL_ACTION,
236
    'weight' => 1,
237
  );
238
  $items[FLAG_ADMIN_PATH . '/import'] = array(
239
    'title' => 'Import',
240
    'page callback' => 'drupal_get_form',
241
    'page arguments' => array('flag_import_form'),
242
    'access arguments' => array('use flag import'),
243
    'file' => 'includes/flag.export.inc',
244
    'type' => MENU_LOCAL_TASK,
245
    'weight' => 2,
246
  );
247
  $items[FLAG_ADMIN_PATH . '/export'] = array(
248
    'title' => 'Export',
249
    'page callback' => 'drupal_get_form',
250
    'page arguments' => array('flag_export_form'),
251
    'access arguments' => array('administer flags'),
252
    'file' => 'includes/flag.export.inc',
253
    'type' => MENU_LOCAL_TASK,
254
    'weight' => 3,
255
  );
256

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

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

    
325
  return $items;
326
}
327

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

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

    
346
  if (module_exists('field_ui')) {
347
    foreach (entity_get_info() as $obj_type => $info) {
348
      if ($obj_type == 'flagging') {
349
        foreach ($info['bundles'] as $bundle_name => $bundle_info) {
350
          if (isset($bundle_info['admin'])) {
351
            $fields = array();
352

    
353
            foreach (field_info_instances($obj_type, $bundle_name) as $field) {
354
              $fields[] = $field['field_name'];
355
            }
356

    
357
            $arguments = array(
358
              '%flag' => array($bundle_name),
359
              '%field_ui_menu' => $fields,
360
            );
361

    
362
            $path = $bundle_info['admin']['path'];
363
            $map["$path/fields/%field_ui_menu"]['parent'] = "$path/fields";
364
            $map["$path/fields/%field_ui_menu"]['arguments'][] = $arguments;
365
          }
366
        }
367
      }
368
    }
369
  }
370

    
371
  return $map;
372
}
373

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

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

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

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

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

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

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

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

    
485
      return $output;
486
  }
487
}
488

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

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

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

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

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

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

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

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

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

    
616
  return $permissions;
617
}
618

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

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

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

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

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

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

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

    
691
  return $extra;
692
}
693

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1704
/**
1705
 * Get the count of flags for a particular entity type.
1706
 *
1707
 * When called during a flagging or unflagging (such as from a hook
1708
 * implementation or from Rules), the flagging or unflagging that is in the
1709
 * process of being performed:
1710
 *  - will be included during a flagging operation
1711
 *  - will STILL be included during an unflagging operation. That is, the count
1712
 *    will not yet have been decreased.
1713
 * This is because this queries the {flagging} table, which only has its record
1714
 * deleted at the very end of the unflagging process.
1715
 *
1716
 * @param $flag
1717
 *   The flag.
1718
 * @param $entity_type
1719
 *   The entity type. For example, 'node'.
1720
 *
1721
 * @return
1722
 *   The flag count with the flag name and entity type as the array key.
1723
 */
1724
function flag_get_entity_flag_counts($flag, $entity_type) {
1725
  $counts = &drupal_static(__FUNCTION__);
1726

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

    
1741
  return $counts[$flag->name][$entity_type];
1742
}
1743

    
1744
/**
1745
 * Get the user's flag count.
1746
 *
1747
 * When called during a flagging or unflagging (such as from a hook
1748
 * implementation or from Rules), the flagging or unflagging that is in the
1749
 * process of being performed:
1750
 *  - will be included during a flagging operation
1751
 *  - will STILL be included during an unflagging operation. That is, the count
1752
 *    will not yet have been decreased.
1753
 * This is because this queries the {flagging} table, which only has its record
1754
 * deleted at the very end of the unflagging process.
1755
 *
1756
 * @param $flag
1757
 *   The flag.
1758
 * @param $user
1759
 *   The user object.
1760
 *
1761
 * @return
1762
 *   The flag count with the flag name and the uid as the array key.
1763
 */
1764
function flag_get_user_flag_counts($flag, $user) {
1765
  $counts = &drupal_static(__FUNCTION__);
1766

    
1767
  // We check to see if the flag count is already in the cache,
1768
  // if it's not, run the query.
1769
  if (!isset($counts[$flag->name][$user->uid])) {
1770
    $counts[$flag->name][$user->uid] = array();
1771
    $result = db_select('flagging', 'f')
1772
      ->fields('f', array('fid'))
1773
      ->condition('fid', $flag->fid)
1774
      ->condition('uid', $user->uid)
1775
      ->countQuery()
1776
      ->execute()
1777
      ->fetchField();
1778
    $counts[$flag->name][$user->uid] = $result;
1779
  }
1780

    
1781
  return $counts[$flag->name][$user->uid];
1782
}
1783

    
1784
/**
1785
 * Get flag counts for all flags on a node.
1786
 *
1787
 * When called during a flagging or unflagging (such as from a hook
1788
 * implementation or from Rules), the count this returns takes into account the
1789
 * the flagging or unflagging that is in the process of being performed.
1790
 *
1791
 * @param $entity_type
1792
 *   The entity type (usually 'node').
1793
 * @param $entity_id
1794
 *   The entity ID (usually the node ID).
1795
 *
1796
 * @return
1797
 *   The flag count with the entity type and id as array keys.
1798
 */
1799
function flag_get_counts($entity_type, $entity_id) {
1800
  $counts = &drupal_static(__FUNCTION__);
1801

    
1802
  if (!isset($counts[$entity_type][$entity_id])) {
1803
    $counts[$entity_type][$entity_id] = array();
1804
    $query = db_select('flag', 'f');
1805
    $query->leftJoin('flag_counts', 'fc', 'f.fid = fc.fid');
1806
    $result = $query
1807
      ->fields('f', array('name'))
1808
      ->fields('fc', array('count'))
1809
      ->condition('fc.entity_type', $entity_type)
1810
      ->condition('fc.entity_id', $entity_id)
1811
      ->execute();
1812
    foreach ($result as $row) {
1813
      $counts[$entity_type][$entity_id][$row->name] = $row->count;
1814
    }
1815
  }
1816

    
1817
  return $counts[$entity_type][$entity_id];
1818
}
1819

    
1820
/**
1821
 * Get the total count of items flagged within a flag.
1822
 *
1823
 * When called during a flagging or unflagging (such as from a hook
1824
 * implementation or from Rules), the count this returns takes into account the
1825
 * the flagging or unflagging that is in the process of being performed.
1826
 *
1827
 * @param $flag_name
1828
 *   The flag name for which to retrieve a flag count.
1829
 * @param $reset
1830
 *   (optional) Reset the internal cache and execute the SQL query another time.
1831
 */
1832
function flag_get_flag_counts($flag_name, $reset = FALSE) {
1833
  $counts = &drupal_static(__FUNCTION__);
1834

    
1835
  if ($reset) {
1836
    $counts = array();
1837
  }
1838
  if (!isset($counts[$flag_name])) {
1839
    $flag = flag_get_flag($flag_name);
1840
    $counts[$flag_name] = db_select('flag_counts', 'fc')
1841
      ->fields('fc', array('fid'))
1842
      ->condition('fid', $flag->fid)
1843
      ->countQuery()
1844
      ->execute()
1845
      ->fetchField();
1846
  }
1847

    
1848
  return $counts[$flag_name];
1849
}
1850

    
1851
/**
1852
 * Load a single flag either by name or by flag ID.
1853
 *
1854
 * @param $name
1855
 *  (optional) The flag name.
1856
 * @param $fid
1857
 *  (optional) The the flag id.
1858
 *
1859
 * @return
1860
 *  The flag object, or FALSE if no matching flag was found.
1861
 */
1862
function flag_get_flag($name = NULL, $fid = NULL) {
1863
  $flags = flag_get_flags();
1864
  if (isset($name)) {
1865
    if (isset($flags[$name])) {
1866
      return $flags[$name];
1867
    }
1868
  }
1869
  elseif (isset($fid)) {
1870
    foreach ($flags as $flag) {
1871
      if ($flag->fid == $fid) {
1872
        return $flag;
1873
      }
1874
    }
1875
  }
1876
  return FALSE;
1877
}
1878

    
1879
/**
1880
 * List all flags available.
1881
 *
1882
 * If node type or account are entered, a list of all possible flags will be
1883
 * returned.
1884
 *
1885
 * @param $entity_type
1886
 *   (optional) The type of entity for which to load the flags. Usually 'node'.
1887
 * @param $content_subtype
1888
 *   (optional) The node type for which to load the flags.
1889
 * @param $account
1890
 *   (optional) The user accont to filter available flags. If not set, all
1891
 *   flags for will this node will be returned.
1892
 *
1893
 * @return
1894
 *   An array of the structure [fid] = flag_object.
1895
 */
1896
function flag_get_flags($entity_type = NULL, $content_subtype = NULL, $account = NULL) {
1897
  $flags = &drupal_static(__FUNCTION__);
1898

    
1899
  // Retrieve a list of all flags, regardless of the parameters.
1900
  if (!isset($flags)) {
1901
    $flags = array();
1902

    
1903
    // Database flags.
1904
    $query = db_select('flag', 'f');
1905
    $query->leftJoin('flag_types', 'fn', 'fn.fid = f.fid');
1906
    $result = $query
1907
      ->fields('f', array(
1908
        'fid',
1909
        'entity_type',
1910
        'name',
1911
        'title',
1912
        'global',
1913
        'options',
1914
      ))
1915
      ->fields('fn', array('type'))
1916
      ->execute();
1917
    foreach ($result as $row) {
1918
      if (!isset($flags[$row->name])) {
1919
        $flags[$row->name] = flag_flag::factory_by_row($row);
1920
      }
1921
      else {
1922
        $flags[$row->name]->types[] = $row->type;
1923
      }
1924
    }
1925

    
1926
    // Add code-based flags provided by modules.
1927
    $default_flags = flag_get_default_flags();
1928
    foreach ($default_flags as $name => $default_flag) {
1929
      // Insert new enabled flags into the database to give them an FID.
1930
      if ($default_flag->status && !isset($flags[$name])) {
1931
        $default_flag->save();
1932
        $flags[$name] = $default_flag;
1933
      }
1934

    
1935
      if (isset($flags[$name])) {
1936
        // Ensure overridden flags are associated with their parent module.
1937
        $flags[$name]->module = $default_flag->module;
1938

    
1939
        // Update the flag with any properties that are "locked" by the code
1940
        // version.
1941
        if (isset($default_flag->locked)) {
1942
          $flags[$name]->locked = $default_flag->locked;
1943
          foreach ($default_flag->locked as $property) {
1944
            $flags[$name]->$property = $default_flag->$property;
1945
          }
1946
        }
1947
      }
1948
    }
1949

    
1950
    // Sort the list of flags by weight.
1951
    uasort($flags, '_flag_compare_weight');
1952

    
1953
    foreach ($flags as $flag) {
1954
      // Allow modules implementing hook_flag_alter(&$flag) to modify each flag.
1955
      drupal_alter('flag', $flag);
1956
    }
1957
  }
1958

    
1959
  // Make a variable copy to filter types and account.
1960
  $filtered_flags = $flags;
1961

    
1962
  // Filter out flags based on type and subtype.
1963
  if (isset($entity_type) || isset($content_subtype)) {
1964
    foreach ($filtered_flags as $name => $flag) {
1965
      if (!$flag->access_entity_enabled($entity_type, $content_subtype)) {
1966
        unset($filtered_flags[$name]);
1967
      }
1968
    }
1969
  }
1970

    
1971
  // Filter out flags based on account permissions.
1972
  if (isset($account) && $account->uid != 1) {
1973
    foreach ($filtered_flags as $name => $flag) {
1974
      // We test against the 'flag' action, which is the minimum permission to
1975
      // use a flag.
1976
      if (!$flag->user_access('flag', $account)) {
1977
        unset($filtered_flags[$name]);
1978
      }
1979
    }
1980
  }
1981

    
1982
  return $filtered_flags;
1983
}
1984

    
1985
/**
1986
 * Comparison function for uasort().
1987
 */
1988
function _flag_compare_weight($flag1, $flag2) {
1989
  if ($flag1->weight == $flag2->weight) {
1990
    return 0;
1991
  }
1992
  return $flag1->weight < $flag2->weight ? -1 : 1;
1993
}
1994

    
1995
/**
1996
 * Retrieve a list of flags defined by modules.
1997
 *
1998
 * @param $include_disabled
1999
 *   (optional) Unless specified, only enabled flags will be returned.
2000
 *
2001
 * @return
2002
 *   An array of flag prototypes, not usable for flagging. Use flag_get_flags()
2003
 *   if needing to perform a flagging with any enabled flag.
2004
 */
2005
function flag_get_default_flags($include_disabled = FALSE) {
2006
  $default_flags = array();
2007
  $flag_status = variable_get('flag_default_flag_status', array());
2008

    
2009
  $default_flags_info = array();
2010
  foreach (module_implements('flag_default_flags') as $module) {
2011
    $function = $module . '_flag_default_flags';
2012
    foreach ($function() as $flag_name => $flag_info) {
2013
      // Backward compatibility: old exported default flags have their names
2014
      // in $flag_info instead, so we use the + operator to not overwrite it.
2015
      $default_flags_info[$flag_name] = $flag_info + array(
2016
        'name' => $flag_name,
2017
        'module' => $module,
2018
      );
2019
    }
2020
  }
2021

    
2022
  // Allow modules to alter definitions using hook_flag_default_flags_alter().
2023
  drupal_alter('flag_default_flags', $default_flags_info);
2024

    
2025
  foreach ($default_flags_info as $flag_info) {
2026
    $flag = flag_flag::factory_by_array($flag_info);
2027

    
2028
    // Disable flags that are not at the current API version.
2029
    if (!$flag->is_compatible()) {
2030
      $flag->status = FALSE;
2031
    }
2032

    
2033
    // Add flags that have been enabled.
2034
    if ((!isset($flag_status[$flag->name]) && (!isset($flag->status) || $flag->status)) || !empty($flag_status[$flag->name])) {
2035
      $flag->status = TRUE;
2036
      $default_flags[$flag->name] = $flag;
2037
    }
2038
    // Add flags that have been disabled.
2039
    elseif ($include_disabled) {
2040
      $flag->status = FALSE;
2041
      $default_flags[$flag->name] = $flag;
2042
    }
2043
  }
2044

    
2045
  return $default_flags;
2046
}
2047

    
2048
/**
2049
 * Get all flagged entities in a flag.
2050
 *
2051
 * @param $flag_name
2052
 *   The flag name for which to retrieve flagged entites.
2053
 *
2054
 * @return
2055
 *   An array of flagging data, keyed by the flagging ID.
2056
 */
2057
function flag_get_flag_flagging_data($flag_name) {
2058
  $return = array();
2059
  $flag = flag_get_flag($flag_name);
2060
  $result = db_select('flagging', 'fc')
2061
    ->fields('fc')
2062
    ->condition('fid', $flag->fid)
2063
    ->execute();
2064
  return $result->fetchAllAssoc('flagging_id');
2065
}
2066

    
2067
/**
2068
 * Find what a user has flagged, either a single entity or on the entire site.
2069
 *
2070
 * When called during a flagging or unflagging (such as from a hook
2071
 * implementation or from Rules), the flagging or unflagging that is in the
2072
 * process of being performed:
2073
 *  - will be included during a flagging operation
2074
 *  - will STILL be included during an unflagging operation. That is, the count
2075
 *    will not yet have been decreased.
2076
 * This is because this queries the {flagging} table, which only has its record
2077
 * deleted at the very end of the unflagging process.
2078
 *
2079
 * @param $entity_type
2080
 *   The type of entity that will be retrieved. Usually 'node'.
2081
 * @param $entity_id
2082
 *   (optional) The entity ID to check for flagging. If none given, all
2083
 *   entities flagged by this user will be returned.
2084
 * @param $uid
2085
 *   (optional) The user ID whose flags we're checking. If none given, the
2086
 *   current user will be used.
2087
 * @param $sid
2088
 *   (optional) The user SID (provided by Session API) whose flags we're
2089
 *   checking. If none given, the current user will be used. The SID is 0 for
2090
 *   logged in users.
2091
 *
2092
 * @return
2093
 *   If returning a single item's flags (that is, when $entity_id isn't NULL),
2094
 *   an array of the structure
2095
 *   [flag_name] => (
2096
 *     flagging_id => [flagging_id],
2097
 *     uid => [uid],
2098
 *     entity_id => [entity_id],
2099
 *     timestamp => [timestamp],
2100
 *     ...)
2101
 *
2102
 *   If returning all items' flags, an array of arrays for each flag:
2103
 *   [flag_name] => [entity_id] => Object from above.
2104
 */
2105
function flag_get_user_flags($entity_type, $entity_id = NULL, $uid = NULL, $sid = NULL) {
2106
  $flagged_content = &drupal_static(__FUNCTION__);
2107

    
2108
  $uid = !isset($uid) ? $GLOBALS['user']->uid : $uid;
2109
  $sid = !isset($sid) ? flag_get_sid($uid) : $sid;
2110

    
2111
  if (isset($entity_id)) {
2112
    if (!isset($flagged_content[$uid][$sid][$entity_type][$entity_id])) {
2113
      $flag_names = _flag_get_flag_names();
2114
      $flagged_content[$uid][$sid][$entity_type][$entity_id] = array();
2115
      $result = db_select('flagging', 'fc')
2116
        ->fields('fc')
2117
        ->condition('entity_type', $entity_type)
2118
        ->condition('entity_id', $entity_id)
2119
        ->condition(db_or()
2120
          ->condition('uid', $uid)
2121
          ->condition('uid', 0)
2122
        )
2123
        ->condition('sid', $sid)
2124
        ->execute();
2125

    
2126
      foreach ($result as $flagging_data) {
2127
        $flagged_content[$uid][$sid][$entity_type][$entity_id][$flag_names[$flagging_data->fid]] = $flagging_data;
2128
      }
2129
    }
2130
    return $flagged_content[$uid][$sid][$entity_type][$entity_id];
2131
  }
2132

    
2133
  else {
2134
    if (!isset($flagged_content[$uid][$sid][$entity_type]['all'])) {
2135
      $flag_names = _flag_get_flag_names();
2136
      $flagged_content[$uid][$sid][$entity_type]['all'] = array();
2137
      $result = db_select('flagging', 'fc')
2138
        ->fields('fc')
2139
        ->condition('entity_type', $entity_type)
2140
        ->condition(db_or()
2141
          ->condition('uid', $uid)
2142
          ->condition('uid', 0)
2143
        )
2144
        ->condition('sid', $sid)
2145
        ->execute();
2146
      foreach ($result as $flagging_data) {
2147
        $flagged_content[$uid][$sid][$entity_type]['all'][$flag_names[$flagging_data->fid]][$flagging_data->entity_id] = $flagging_data;
2148
      }
2149
    }
2150
    return $flagged_content[$uid][$sid][$entity_type]['all'];
2151
  }
2152

    
2153
}
2154

    
2155
/**
2156
 * Return a list of users who have flagged an entity.
2157
 *
2158
 * When called during a flagging or unflagging (such as from a hook
2159
 * implementation or from Rules), the flagging or unflagging that is in the
2160
 * process of being performed:
2161
 *  - will be included during a flagging operation
2162
 *  - will STILL be included during an unflagging operation. That is, the count
2163
 *    will not yet have been decreased.
2164
 * This is because this queries the {flagging} table, which only has its record
2165
 * deleted at the very end of the unflagging process.
2166
 *
2167
 * @param $entity_type
2168
 *   The type of entity that will be retrieved. Usually 'node'.
2169
 * @param $entity_id
2170
 *   The entity ID to check for flagging.
2171
 * @param $flag_name
2172
 *   (optional) The name of a flag if wanting a list specific to a single flag.
2173
 *
2174
 * @return
2175
 *   A nested array of flagging records (i.e. rows from the {flagging} table,
2176
 *   rather than complete Flagging entities). The structure depends on the
2177
 *   presence of the $flag_name parameter:
2178
 *    - if $flag_name is omitted, the array is keyed first by the user ID of
2179
 *      the users that flagged the entity, then by flag name. Each value is
2180
 *      then the flagging record.
2181
 *    - if $flag_name is given, the array is keyed only by user ID. Each value
2182
 *      is the flagging record.
2183
 *   If no flags were found an empty array is returned.
2184
 */
2185
function flag_get_entity_flags($entity_type, $entity_id, $flag_name = NULL) {
2186
  $entity_flags = &drupal_static(__FUNCTION__, array());
2187

    
2188
  if (!isset($entity_flags[$entity_type][$entity_id])) {
2189
    $flag_names = _flag_get_flag_names();
2190
    $result = db_select('flagging', 'fc')
2191
      ->fields('fc')
2192
      ->condition('entity_type', $entity_type)
2193
      ->condition('entity_id', $entity_id)
2194
      ->orderBy('timestamp', 'DESC')
2195
      ->execute();
2196
    $entity_flags[$entity_type][$entity_id] = array();
2197
    foreach ($result as $flagging_data) {
2198
      // Build a list of flaggings for all flags by user.
2199
      $entity_flags[$entity_type][$entity_id]['users'][$flagging_data->uid][$flag_names[$flagging_data->fid]] = $flagging_data;
2200
      // Build a list of flaggings for each individual flag.
2201
      $entity_flags[$entity_type][$entity_id]['flags'][$flag_names[$flagging_data->fid]][$flagging_data->uid] = $flagging_data;
2202
    }
2203
  }
2204
  if (empty($entity_flags[$entity_type][$entity_id])) {
2205
    return array();
2206
  }
2207
  if (isset($flag_name)) {
2208
    if (isset($entity_flags[$entity_type][$entity_id]['flags'][$flag_name])) {
2209
      return $entity_flags[$entity_type][$entity_id]['flags'][$flag_name];
2210
    }
2211
    return array();
2212
  }
2213
  return $entity_flags[$entity_type][$entity_id]['users'];
2214
}
2215

    
2216
/**
2217
 * A utility function for outputting a flag link.
2218
 *
2219
 * You should call this function from your template when you want to put the
2220
 * link on the page yourself. For example, you could call this function from
2221
 * your theme preprocessor for node.tpl.php:
2222
 * @code
2223
 * $variables['my_flag_link'] = flag_create_link('bookmarks', $node->nid);
2224
 * @endcode
2225
 *
2226
 * @param $flag_name
2227
 *   The "machine readable" name of the flag; e.g. 'bookmarks'.
2228
 * @param $entity_id
2229
 *   The entity ID to check for flagging, for example a node ID.
2230
 * @param $variables
2231
 *  An array of further variables to pass to theme('flag'). For the full list
2232
 *  of parameters, see flag.tpl.php. Of particular interest:
2233
 *  - after_flagging: Set to TRUE if this flag link is being displayed as the
2234
 *    result of a flagging action.
2235
 *  - errors: An array of error messages.
2236
 *
2237
 * @return
2238
 *   The HTML for the themed flag link.
2239
 */
2240
function flag_create_link($flag_name, $entity_id, $variables = array()) {
2241
  $flag = flag_get_flag($flag_name);
2242
  if (!$flag) {
2243
    // Flag does not exist.
2244
    return;
2245
  }
2246
  if (!$flag->access($entity_id) && (!$flag->is_flagged($entity_id) || !$flag->access($entity_id, 'flag'))) {
2247
    // User has no permission to use this flag.
2248
    return;
2249
  }
2250
  return $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id, $variables);
2251
}
2252

    
2253
/**
2254
 * Trim a flag to a certain size.
2255
 *
2256
 * @param $fid
2257
 *   The flag object.
2258
 * @param $account
2259
 *   The user object on behalf the trimming will occur.
2260
 * @param $cutoff_size
2261
 *   The number of flaggings allowed. Any flaggings beyond that will be trimmed.
2262
 * @param $trim_newest
2263
 *   An optional boolean indicating whether to trim the newest flags.
2264
 * @param $permissions_check
2265
 *   (optional) A boolean indicating whether to skip permissions.
2266
 *   This will trim the flag if $permissions_check is TRUE even if the user
2267
 *   doesn't have the permission to flag/unflag.
2268
 */
2269
function flag_trim_flag($flag, $account, $cutoff_size, $trim_newest, $permissions_check = FALSE) {
2270
  $query = db_select('flagging', 'fc')
2271
    ->fields('fc')
2272
    ->condition('fid', $flag->fid)
2273
    ->condition(db_or()->condition('uid', $account->uid)->condition('uid', 0))
2274
    // Account for session ID (in the case of anonymous users).
2275
    ->condition('sid', flag_get_sid($account->uid));
2276
  // If $trim_newest is TRUE, then, we should order by 'ASC' as we should trim
2277
  // the newest flags.
2278
  if ($trim_newest) {
2279
    $query->orderBy('timestamp', 'ASC');
2280
  }
2281
  else {
2282
    $query->orderBy('timestamp', 'DESC');
2283
  }
2284

    
2285
  // Execute the query.
2286
  $result = $query->execute();
2287

    
2288
  $i = 1;
2289
  foreach ($result as $row) {
2290
    if ($i++ > $cutoff_size) {
2291
      flag('unflag', $flag->name, $row->entity_id, $account, $permissions_check);
2292
    }
2293
  }
2294
}
2295

    
2296
/**
2297
 * Remove all flagged entities from a flag.
2298
 *
2299
 * @param $flag
2300
 *   The flag object.
2301
 * @param $entity_id
2302
 *   (optional) The entity ID on which all flaggings will be removed. If left
2303
 *   empty, this will remove all of this flag's entities.
2304
 */
2305
function flag_reset_flag($flag, $entity_id = NULL) {
2306
  $query = db_select('flagging', 'fc')
2307
    ->fields('fc')
2308
    ->condition('fid', $flag->fid);
2309

    
2310
  if ($entity_id) {
2311
    $query->condition('entity_id', $entity_id);
2312
  }
2313

    
2314
  $result = $query->execute()->fetchAllAssoc('flagging_id', PDO::FETCH_ASSOC);
2315
  $rows = array();
2316
  foreach ($result as $row) {
2317
    $rows[] = $row;
2318
  }
2319
  module_invoke_all('flag_reset', $flag, $entity_id, $rows);
2320

    
2321
  $query = db_delete('flagging')->condition('fid', $flag->fid);
2322
  // Update the flag_counts table.
2323
  $count_query = db_delete('flag_counts')->condition('fid', $flag->fid);
2324
  if ($entity_id) {
2325
    $query->condition('entity_id', $entity_id);
2326
    $count_query->condition('entity_id', $entity_id);
2327
  }
2328
  $count_query->execute();
2329
  return $query->execute();
2330
}
2331

    
2332
/**
2333
 * Return an array of link types provided by modules.
2334
 *
2335
 * @return
2336
 *  An array of link types as defined by hook_flag_link_type_info(). These are
2337
 *  keyed by the type name, and each value is an array of properties. In
2338
 *  addition to those defined in hook_flag_link_type_info(), the following
2339
 *  properties are set:
2340
 *  - 'module': The providing module.
2341
 *  - 'name': The machine name of the type.
2342
 *
2343
 * @see hook_flag_link_type_info()
2344
 * @see hook_flag_link_type_info_alter()
2345
 */
2346
function flag_get_link_types() {
2347
  $link_types = &drupal_static(__FUNCTION__);
2348

    
2349
  if (!isset($link_types)) {
2350
    if ($cache = cache_get('flag_link_type_info')) {
2351
      $link_types = $cache->data;
2352
    }
2353
    // In some rare edge cases cache_get() can return an empty result. If it
2354
    // does, we make sure to fetch the link types again.
2355
    if (empty($link_types)) {
2356
      $link_types = array();
2357
      foreach (module_implements('flag_link_type_info') as $module) {
2358
        $module_types = module_invoke($module, 'flag_link_type_info');
2359
        foreach ($module_types as $type_name => $info) {
2360
          $link_types[$type_name] = $info + array(
2361
            'module' => $module,
2362
            'name' => $type_name,
2363
            'title' => '',
2364
            'description' => '',
2365
            'options' => array(),
2366
            'uses standard js' => TRUE,
2367
            'uses standard css' => TRUE,
2368
            'provides form' => FALSE,
2369
          );
2370
        }
2371
      }
2372
      drupal_alter('flag_link_type_info', $link_types);
2373

    
2374
      cache_set('flag_link_type_info', $link_types);
2375
    }
2376
  }
2377

    
2378
  return $link_types;
2379
}
2380

    
2381
/**
2382
 * Get a private token used to protect links from spoofing - CSRF.
2383
 */
2384
function flag_get_token($entity_id) {
2385
  // Anonymous users get a less secure token, since it must be the same for all
2386
  // anonymous users on the entire site to work with page caching.
2387
  return ($GLOBALS['user']->uid) ? drupal_get_token($entity_id) : md5(drupal_get_private_key() . $entity_id);
2388
}
2389

    
2390
/**
2391
 * Check to see if a token value matches the specified node.
2392
 */
2393
function flag_check_token($token, $entity_id) {
2394
  return flag_get_token($entity_id) == $token;
2395
}
2396

    
2397
/**
2398
 * Set the Session ID for a user. Utilizes the Session API module.
2399
 *
2400
 * Creates a Session ID for an anonymous user and returns it. It will always
2401
 * return 0 for registered users.
2402
 *
2403
 * @param int $uid
2404
 *   (optional) The user ID to create a session ID for. Defaults to the
2405
 *   current user.
2406
 * @param bool $create
2407
 *   (optional) Determines whether a session should be created if it doesn't
2408
 *   exist yet. Defaults to TRUE.
2409
 *
2410
 * @return
2411
 *   The session ID, if a session was created. If not, the return value is 0.
2412
 *
2413
 * @see flag_get_sid()
2414
 */
2415
function flag_set_sid($uid = NULL, $create = TRUE) {
2416
  $sids = &drupal_static(__FUNCTION__, array());
2417

    
2418
  if (!isset($uid)) {
2419
    $uid = $GLOBALS['user']->uid;
2420
  }
2421

    
2422
  // Set the sid if none has been set yet. If the caller specified to create an
2423
  // sid and we have an invalid one (-1), create it.
2424
  if (!isset($sids[$uid]) || ($sids[$uid] == -1 && $create)) {
2425
    if (module_exists('session_api') && session_api_available() && $uid == 0) {
2426
      // This returns one of the following:
2427
      // - -1. This indicates that no session exists and none was created.
2428
      // - A positive integer with the Session ID when it does exist.
2429
      $sids[$uid] = session_api_get_sid($create);
2430
    }
2431
    else {
2432
      $sids[$uid] = 0;
2433
    }
2434
  }
2435

    
2436
  // Keep the -1 case internal and let the outside world only distinguish two
2437
  // cases: (1) there is an SID; (2) there is no SID (-> 0).
2438
  return $sids[$uid] == -1 ? 0 : $sids[$uid];
2439
}
2440

    
2441
/**
2442
 * Get the Session ID for a user. Utilizes the Session API module.
2443
 *
2444
 * Gets the Session ID for an anonymous user. It will always return 0 for
2445
 * registered users.
2446
 *
2447
 * @param int $uid
2448
 *   (optional) The user ID to return the session ID for. Defaults to the
2449
 *   current user.
2450
 * @param bool $create
2451
 *   (optional) Determines whether a session should be created if it doesn't
2452
 *   exist yet. Defaults to FALSE.
2453
 *
2454
 * @return
2455
 *   The session ID, if the session exists. If not, the return value is 0.
2456
 *
2457
 * @see flag_set_sid()
2458
 */
2459
function flag_get_sid($uid = NULL, $create = FALSE) {
2460
  return flag_set_sid($uid, $create);
2461
}
2462

    
2463
// ---------------------------------------------------------------------------
2464
// Drupal Core operations
2465

    
2466
/**
2467
 * Implements hook_node_operations().
2468
 *
2469
 * Add additional options on the admin/build/node page.
2470
 */
2471
function flag_node_operations() {
2472
  global $user;
2473

    
2474
  $flags = flag_get_flags('node', NULL, $user);
2475
  $operations = array();
2476

    
2477
  foreach ($flags as $flag) {
2478
    $operations['flag_' . $flag->name] = array(
2479
      'label' => $flag->get_label('flag_short'),
2480
      'callback' => 'flag_nodes',
2481
      'callback arguments' => array('flag', $flag->name),
2482
      'behavior' => array(),
2483
    );
2484
    $operations['unflag_' . $flag->name] = array(
2485
      'label' => $flag->get_label('unflag_short'),
2486
      'callback' => 'flag_nodes',
2487
      'callback arguments' => array('unflag', $flag->name),
2488
      'behavior' => array(),
2489
    );
2490
  }
2491
  return $operations;
2492
}
2493

    
2494
/**
2495
 * Callback function for hook_node_operations().
2496
 */
2497
function flag_nodes($nodes, $action, $flag_name) {
2498
  $performed = FALSE;
2499
  foreach ($nodes as $nid) {
2500
    $performed |= flag($action, $flag_name, $nid);
2501
  }
2502
  if ($performed) {
2503
    drupal_set_message(t('The update has been performed.'));
2504
  }
2505
}
2506

    
2507
/**
2508
 * Implements hook_user_operations().
2509
 */
2510
function flag_user_operations() {
2511
  global $user;
2512

    
2513
  $flags = flag_get_flags('user', NULL, $user);
2514
  $operations = array();
2515

    
2516
  foreach ($flags as $flag) {
2517
    $operations['flag_' . $flag->name] = array(
2518
      'label' => $flag->get_label('flag_short'),
2519
      'callback' => 'flag_users',
2520
      'callback arguments' => array('flag', $flag->name),
2521
    );
2522
    $operations['unflag_' . $flag->name] = array(
2523
      'label' => $flag->get_label('unflag_short'),
2524
      'callback' => 'flag_users',
2525
      'callback arguments' => array('unflag', $flag->name),
2526
    );
2527
  }
2528
  return $operations;
2529
}
2530
/**
2531
 * Callback function for hook_user_operations().
2532
 */
2533
function flag_users($users, $action, $flag_name) {
2534
  foreach ($users as $uid) {
2535
    flag($action, $flag_name, $uid);
2536
  }
2537
}
2538

    
2539
// ---------------------------------------------------------------------------
2540
// Contrib integration hooks
2541

    
2542
/**
2543
 * Implements hook_views_api().
2544
 */
2545
function flag_views_api() {
2546
  return array(
2547
    'api' => 3.0,
2548
    'path' => drupal_get_path('module', 'flag') . '/includes/views',
2549
  );
2550
}
2551

    
2552
/**
2553
 * Implements hook_features_api().
2554
 */
2555
function flag_features_api() {
2556
  return array(
2557
    'flag' => array(
2558
      'name' => t('Flag'),
2559
      'feature_source' => TRUE,
2560
      'default_hook' => 'flag_default_flags',
2561
      'file' => drupal_get_path('module', 'flag') . '/includes/flag.features.inc',
2562
    ),
2563
  );
2564
}
2565

    
2566
/**
2567
 * Implements hook_ctools_plugin_directory().
2568
 */
2569
function flag_ctools_plugin_directory($module, $plugin) {
2570
  if ($module == 'ctools' && !empty($plugin)) {
2571
    return "plugins/$plugin";
2572
  }
2573
}
2574

    
2575
// ---------------------------------------------------------------------------
2576
// Entity Metadata callbacks
2577

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

    
2584
  $flagging_data = flag_get_user_flags($entity_type, $entity_id);
2585
  return isset($flagging_data[$property_info['flag_name']]);
2586
}
2587

    
2588
/**
2589
 * Getter callback that returns entities the given user flagged.
2590
 */
2591
function flag_properties_get_flagged_entities($entity, array $options, $name, $entity_type, $property_info) {
2592
  // Need the entity type the flag applies to.
2593
  $flag_entity_type = $property_info['flag_entity_type'];
2594

    
2595
  $flagging_data = flag_get_user_flags($flag_entity_type, NULL, $entity->uid);
2596

    
2597
  $flag_name = $property_info['flag_name'];
2598
  if (isset($flagging_data[$flag_name])) {
2599
    return array_keys($flagging_data[$flag_name]);
2600
  }
2601
  return array();
2602
}
2603

    
2604
/**
2605
 * Getter callback that returns users who flagged the given entity.
2606
 */
2607
function flag_properties_get_flagging_users($entity, array $options, $name, $entity_type, $property_info) {
2608
  list($entity_id,) = entity_extract_ids($entity_type, $entity);
2609

    
2610
  $flagging_data = flag_get_entity_flags($entity_type, $entity_id, $property_info['flag_name']);
2611

    
2612
  return array_keys($flagging_data);
2613
}
2614

    
2615
/**
2616
 * Getter callback that returns the SID of the user that is being retrieved.
2617
 *
2618
 * Callback for hook_entity_property_info_alter().
2619
 *
2620
 * @param stdobj $entity
2621
 *  The entity object representing a user for which we are getting inforamtion for.
2622
 *
2623
 * @param array $options
2624
 *  Options reguarding the nature of the entity. Language, etc.
2625
 *
2626
 * @param string $name
2627
 *  The name of the property we are running this callback for.
2628
 *
2629
 * @param string $entity_type
2630
 *  The type that the stdobj $entity is supposed to be.
2631
 *
2632
 * @param $property_info
2633
 *  The ifnromatin that represents the property we are providing a result for.
2634
 *
2635
 * @return an integer representing the user's sid field from the session_api table
2636
 *
2637
 * @ingroup callbacks
2638
 */
2639
function flag_properties_get_user_sid($entity, array $options, $name, $entity_type, $property_info) {
2640
  $sid = flag_get_sid($entity->uid, FALSE);
2641
  return $sid;
2642
}