Projet

Général

Profil

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

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

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 page request.)
164

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

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

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

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

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

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

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

    
320
  return $items;
321
}
322

    
323
/**
324
 * Implements hook_admin_menu_map().
325
 */
326
function flag_admin_menu_map() {
327
  if (!user_access('administer flags')) {
328
    return;
329
  }
330

    
331
  $map = array();
332
  $map[FLAG_ADMIN_PATH . '/manage/%flag'] = array(
333
    'parent' => FLAG_ADMIN_PATH,
334
    'arguments' => array(
335
      array(
336
        '%flag' => array_keys(flag_get_flags()),
337
      ),
338
    ),
339
  );
340

    
341
  if (module_exists('field_ui')) {
342
    foreach (entity_get_info() as $obj_type => $info) {
343
      if ($obj_type == 'flagging') {
344
        foreach ($info['bundles'] as $bundle_name => $bundle_info) {
345
          if (isset($bundle_info['admin'])) {
346
            $fields = array();
347

    
348
            foreach (field_info_instances($obj_type, $bundle_name) as $field) {
349
              $fields[] = $field['field_name'];
350
            }
351

    
352
            $arguments = array(
353
              '%flag' => array($bundle_name),
354
              '%field_ui_menu' => $fields,
355
            );
356

    
357
            $path = $bundle_info['admin']['path'];
358
            $map["$path/fields/%field_ui_menu"]['parent'] = "$path/fields";
359
            $map["$path/fields/%field_ui_menu"]['arguments'][] = $arguments;
360
          }
361
        }
362
      }
363
    }
364
  }
365

    
366
  return $map;
367
}
368

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

    
403
/**
404
 * Menu title callback.
405
 */
406
function _flag_menu_title($flag) {
407
  // The following conditional it to handle a D7 bug (@todo: link).
408
  return $flag ? $flag->get_title() : '';
409
}
410

    
411
/**
412
 * Implements hook_help().
413
 */
414
function flag_help($path, $arg) {
415
  switch ($path) {
416
    case FLAG_ADMIN_PATH:
417
      $output = '<p>' . t('This page lists all the <em>flags</em> that are currently defined on this system.') . '</p>';
418
      return $output;
419
    case FLAG_ADMIN_PATH . '/add':
420
      $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>';
421
      return $output;
422
    case FLAG_ADMIN_PATH . '/manage/%/fields':
423
      // Get the existing link types that provide a flagging form.
424
      $link_types = flag_get_link_types();
425
      $form_link_types = array();
426
      foreach (flag_get_link_types() as $link_type) {
427
        if ($link_type['provides form']) {
428
          $form_link_types[] = '<em>' . $link_type['title'] . '</em>';
429
        }
430
      }
431

    
432
      // Get the flag for which we're managing fields.
433
      $flag = menu_get_object('flag', FLAG_ADMIN_PATH_START + 1);
434

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

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

    
478
      return $output;
479
  }
480
}
481

    
482
/**
483
 * Implements hook_init().
484
 */
485
function flag_init() {
486
  module_load_include('inc', 'flag', 'includes/flag.actions');
487
}
488

    
489
/**
490
 * Implements hook_hook_info().
491
 */
492
function flag_hook_info() {
493
  $hooks['flag_type_info'] = array(
494
    'group' => 'flag',
495
  );
496
  $hooks['flag_type_info_alter'] = array(
497
    'group' => 'flag',
498
  );
499
  $hooks['flag_link_type_info'] = array(
500
    'group' => 'flag',
501
  );
502
  $hooks['flag_link_type_info_alter'] = array(
503
    'group' => 'flag',
504
  );
505
  return $hooks;
506
}
507

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

    
530
      cache_set('flag_type_info', $definitions);
531
    }
532
  }
533

    
534
  if (isset($entity_type)) {
535
    if (isset($definitions[$entity_type])) {
536
      return $definitions[$entity_type];
537
    }
538
  }
539
  else {
540
    return $definitions;
541
  }
542
}
543

    
544
/**
545
 * Returns all flag types defined on the system.
546
 *
547
 * @return
548
 *   An array of flag type names.
549
 */
550
function flag_get_types() {
551
  $types = &drupal_static(__FUNCTION__);
552
  if (!isset($types)) {
553
    $types = array_keys(flag_fetch_definition());
554
  }
555
  return $types;
556
}
557

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

    
585
/**
586
 * Implements hook_permission().
587
 */
588
function flag_permission() {
589
  $permissions = array(
590
    'administer flags' => array(
591
      'title' => t('Administer flags'),
592
      'description' => t('Create and edit site-wide flags.'),
593
    ),
594
    'use flag import' => array(
595
      'title' => t('Use flag importer'),
596
      'description' => t('Access the flag import functionality.'),
597
      'restrict access' => TRUE,
598
    ),
599
  );
600

    
601
  // Reset static cache to ensure all flag permissions are available.
602
  drupal_static_reset('flag_get_flags');
603
  $flags = flag_get_flags();
604
  // Provide flag and unflag permissions for each flag.
605
  foreach ($flags as $flag_name => $flag) {
606
    $permissions += $flag->get_permissions();
607
  }
608

    
609
  return $permissions;
610
}
611

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

    
629
/**
630
 * Implements hook_flag_link().
631
 */
632
function flag_flag_link($flag, $action, $entity_id) {
633
  $token = flag_get_token($entity_id);
634
  return array(
635
    'href' => 'flag/' . ($flag->link_type == 'confirm' ? 'confirm/' : '') . "$action/$flag->name/$entity_id",
636
    'query' => drupal_get_destination() + ($flag->link_type == 'confirm' ? array() : array('token' => $token)),
637
  );
638
}
639

    
640
/**
641
 * Implements hook_field_extra_fields().
642
 */
643
function flag_field_extra_fields() {
644
  $extra = array();
645

    
646
  $flags = flag_get_flags();
647
  foreach ($flags as $name => $flag) {
648
    // Skip flags that aren't on entities.
649
    if (!($flag instanceof flag_entity)) {
650
      continue;
651
    }
652

    
653
    $applicable_bundles = $flag->types;
654
    // If the list of bundles is empty, it indicates all bundles apply.
655
    if (empty($applicable_bundles)) {
656
      $entity_info = entity_get_info($flag->entity_type);
657
      $applicable_bundles = array_keys($entity_info['bundles']);
658
    }
659

    
660
    foreach ($applicable_bundles as $bundle_name) {
661
      if ($flag->show_on_form) {
662
        $extra[$flag->entity_type][$bundle_name]['form']['flag'] = array(
663
          'label' => t('Flags'),
664
          'description' => t('Checkboxes for toggling flags'),
665
          'weight' => 10
666
        );
667
      }
668

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

    
684
  return $extra;
685
}
686

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

    
713
  if (isset($form['workflow']['flag'])) {
714
    $form['workflow']['flag'] += array(
715
      '#type' => 'item',
716
      '#title' => t('Default flags'),
717
      '#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))),
718
      // Make the spacing a bit more compact:
719
      '#prefix' => '<div class="form-checkboxes">',
720
      '#suffix' => '</div>',
721
    );
722
  }
723
}
724

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

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

    
746
  // Get all possible flags for this entity type.
747
  $flags = flag_get_flags($entity_type);
748

    
749
  // Filter out flags which need to be included on the node form.
750
  $flags_in_form = 0;
751
  $flags_visible = 0;
752
  foreach ($flags as $flag) {
753
    if (!$flag->show_on_form) {
754
      continue;
755
    }
756

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

    
771
      // For a new, unsaved entity, make a dummy entity ID so that the flag
772
      // handler can remember the entity. This allows access to the flag to be
773
      // correctly handled in node and comment preview.
774
      $id = 'new';
775
      $flag->remember_entity($id, $entity);
776
    }
777

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

    
790
    $form['flag'][$flag->name] = array(
791
      '#type' => 'checkbox',
792
      '#title' => $flag->get_label('flag_short', $id),
793
      '#description' => $flag->get_label('flag_long', $id),
794
      '#default_value' => $flag_status,
795
      '#return_value' => 1,
796
      // Used by our drupalSetSummary() on vertical tabs.
797
      '#attributes' => array('title' => $flag->get_title()),
798
    );
799

    
800
    // If the user does not have access to the flag, set as a value.
801
    if (!$access) {
802
      $form['flag'][$flag->name]['#type'] = 'value';
803
      $form['flag'][$flag->name]['#value'] = $flag_status;
804
    }
805
    else {
806
      $flags_visible++;
807
    }
808
    $flags_in_form++;
809
  }
810

    
811
  if ($flags_in_form) {
812
    $form['flag'] += array(
813
      '#weight' => 1,
814
      '#tree' => TRUE,
815
    );
816
  }
817
  if ($flags_visible) {
818
    $form['flag'] += array(
819
      '#type' => 'fieldset',
820
      '#title' => t('Flags'),
821
      '#collapsible' => TRUE,
822
    );
823

    
824
    if ($entity_type == 'node') {
825
      // Turn the fieldset into a vertical tab.
826
      $form['flag'] += array(
827
        '#group' => 'additional_settings',
828
        '#attributes' => array('class' => array('flag-fieldset')),
829
        '#attached' => array(
830
          'js' => array(
831
            'vertical-tabs' => drupal_get_path('module', 'flag') . '/theme/flag-admin.js',
832
          ),
833
        ),
834
      );
835
    }
836
  }
837
}
838

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

    
853
    // Put the form values in the entity so flag_field_attach_save() can find
854
    // them. We can't call flag() here as new entities have no id yet.
855
    $entity->flag = $flag_values;
856
  }
857
}
858

    
859
/**
860
 * Implements hook_field_attach_insert().
861
 */
862
function flag_field_attach_insert($entity_type, $entity) {
863
  if (isset($entity->flag)) {
864
    flag_field_attach_save($entity_type, $entity);
865
  }
866
}
867

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

    
877
/**
878
 * Shared saving routine between flag_field_attach_insert/update().
879
 *
880
 * @see flag_field_attach_form().
881
 */
882
function flag_field_attach_save($entity_type, $entity) {
883
  list($id) = entity_extract_ids($entity_type, $entity);
884
  // Get the flag values we stashed in the entity in flag_field_attach_submit().
885
  foreach ($entity->flag as $flag_name => $state) {
886
    flag($state ? 'flag' : 'unflag', $flag_name, $id);
887
  }
888
}
889

    
890
/*
891
 * Implements hook_contextual_links_view_alter().
892
 */
893
function flag_contextual_links_view_alter(&$element, $items) {
894
  if (isset($element['#element']['#entity_type'])) {
895
    $entity_type = $element['#element']['#entity_type'];
896

    
897
    // Get the entity out of the element. This requires a bit of legwork.
898
    if (isset($element['#element']['#entity'])) {
899
      // EntityAPI entities will all have the entity in the same place.
900
      $entity = $element['#element']['#entity'];
901
    }
902
    elseif (isset($element['#element']['#' . $entity_type])) {
903
      // Node module at least puts it here.
904
      $entity = $element['#element']['#' . $entity_type];
905
    }
906
    else {
907
      // Give up.
908
      return;
909
    }
910

    
911
    // Get all possible flags for this entity type.
912
    $flags = flag_get_flags($entity_type);
913

    
914
    foreach ($flags as $name => $flag) {
915
      if (!$flag->show_contextual_link) {
916
        continue;
917
      }
918

    
919
      list($entity_id) = entity_extract_ids($entity_type, $entity);
920
      if (!$flag->access($entity_id) && (!$flag->is_flagged($entity_id) || !$flag->access($entity_id, 'flag'))) {
921
        // User has no permission to use this flag or flag does not apply to this
922
        // object. The link is not skipped if the user has "flag" access but
923
        // not "unflag" access (this way the unflag denied message is shown).
924
        continue;
925
      }
926

    
927
      $element['#links']['flag-'. $name] = array(
928
        'title' => $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id),
929
        'html' => TRUE,
930
      );
931
    }
932
  }
933
}
934

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

    
953
    $entity_id = $flag->get_entity_id($entity);
954
    // For a new, unsaved entity, make a dummy entity ID so that the flag
955
    // handler can remember the entity. This allows access to the flag to be
956
    // correctly handled in node and comment preview.
957
    if (is_null($entity_id)) {
958
      $entity_id = 'new';
959
    }
960
    $flag->remember_entity($entity_id, $entity);
961

    
962
    if (!$flag->access($entity_id) && (!$flag->is_flagged($entity_id) || !$flag->access($entity_id, 'flag'))) {
963
      // User has no permission to use this flag or flag does not apply to this
964
      // entity. The link is not skipped if the user has "flag" access but
965
      // not "unflag" access (this way the unflag denied message is shown).
966
      continue;
967
    }
968

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

    
971
    // The old-style entity links output.
972
    if ($flag->shows_in_entity_links($view_mode)) {
973
      // The flag links are actually fully rendered theme functions.
974
      // The HTML attribute is set to TRUE to allow whatever the themer desires.
975
      $links['flag-' . $flag->name] = array(
976
        'title' => $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id),
977
        'html' => TRUE,
978
      );
979
    }
980

    
981
    // The pseudofield output.
982
    if ($flag->show_as_field) {
983
      $entity->content['flag_' . $flag->name] = array(
984
       '#markup' => $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id, array('needs_wrapping_element' => TRUE)),
985
      );
986
    }
987
  }
988

    
989
  // If any links were made, add them to the entity's links array.
990
  if (isset($links)) {
991
    $entity->content['links']['flag'] = array(
992
      '#theme' => 'links',
993
      '#links' => $links,
994
      '#attributes' => array('class' => array('links', 'inline')),
995
    );
996
  }
997
}
998

    
999
/**
1000
 * Implements hook_node_insert().
1001
 */
1002
function flag_node_insert($node) {
1003
  flag_node_save($node);
1004
}
1005

    
1006
/**
1007
 * Implements hook_node_update().
1008
 */
1009
function flag_node_update($node) {
1010
  flag_node_save($node);
1011
}
1012

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

    
1031
      $action = $state ? 'flag' : 'unflag';
1032
      // Pass TRUE for $skip_permission_check so that flags that have been
1033
      // passed through as hidden form values are saved.
1034
      $flag->flag($action, $node->nid, NULL, TRUE);
1035
    }
1036
  }
1037
}
1038

    
1039
/**
1040
 * Implements hook_entity_delete().
1041
 */
1042
function flag_entity_delete($entity, $type) {
1043
  // Node and user flags handle things through the entity type delete hooks.
1044
  // @todo: make this configurable in the flag type definition?
1045
  if ($type == 'node' || $type == 'user') {
1046
    return;
1047
  }
1048

    
1049
  list($id) = entity_extract_ids($type, $entity);
1050
  _flag_entity_delete($type, $id);
1051
}
1052

    
1053
/**
1054
 * Implements hook_node_delete().
1055
 */
1056
function flag_node_delete($node) {
1057
  foreach (flag_get_flags('node') as $flag) {
1058
    // If the flag is being tracked by translation set and the node is part
1059
    // of a translation set, don't delete the flagging record.
1060
    // Instead, data will be updated in hook_node_translation_change(), below.
1061
    if (!$flag->i18n || empty($node->tnid)) {
1062
      _flag_entity_delete('node', $node->nid, $flag->fid);
1063
    }
1064
  }
1065
}
1066

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

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

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

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

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

    
1160
    // Clean up anonymous cookies.
1161
    FlagCookieStorage::drop();
1162
  }
1163
}
1164

    
1165
/**
1166
 * Implements hook_user_cancel().
1167
 */
1168
function flag_user_cancel($edit, $account, $method) {
1169
  flag_user_account_removal($account);
1170
}
1171

    
1172
/**
1173
 * Implements hook_user_delete().
1174
 */
1175
function flag_user_delete($account) {
1176
  flag_user_account_removal($account);
1177
}
1178

    
1179
/**
1180
 * Shared helper for user account cancellation or deletion.
1181
 */
1182
function flag_user_account_removal($account) {
1183
  // Remove flags by this user.
1184
  $query = db_select('flagging', 'fc');
1185
  $query->leftJoin('flag_counts', 'c', 'fc.entity_id = c.entity_id AND fc.entity_type = c.entity_type AND fc.fid = c.fid');
1186
  $result = $query
1187
    ->fields('fc', array('fid', 'entity_id'))
1188
    ->fields('c', array('count'))
1189
    ->condition('fc.uid', $account->uid)
1190
    ->execute();
1191

    
1192
  foreach ($result as $flag_data) {
1193
    // Only decrement the flag count table if it's greater than 1.
1194
    if ($flag_data->count > 0) {
1195
      $flag_data->count--;
1196
      db_update('flag_counts')
1197
        ->fields(array(
1198
          'count' => $flag_data->count,
1199
        ))
1200
        ->condition('fid', $flag_data->fid)
1201
        ->condition('entity_id', $flag_data->entity_id)
1202
        ->execute();
1203
    }
1204
    elseif ($flag_data->count == 0) {
1205
      db_delete('flag_counts')
1206
        ->condition('fid', $flag_data->fid)
1207
        ->condition('entity_id', $flag_data->entity_id)
1208
        ->execute();
1209
    }
1210
  }
1211
  db_delete('flagging')
1212
    ->condition('uid', $account->uid)
1213
    ->execute();
1214

    
1215
  // Remove flags that have been done to this user.
1216
  _flag_entity_delete('user', $account->uid);
1217
}
1218

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

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

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

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

    
1319
/**
1320
 * Implements hook_flag_flag().
1321
 */
1322
function flag_flag_flag($flag, $entity_id, $account, $flagging) {
1323
  if (module_exists('trigger')) {
1324
    flag_flag_trigger('flag', $flag, $entity_id, $account, $flagging);
1325
  }
1326
}
1327

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

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

    
1361
  // Generic "all flags" actions.
1362
  foreach (trigger_get_assigned_actions('flag_' . $action)  as $aid => $action_info) {
1363
    // The 'if ($aid)' is a safeguard against http://drupal.org/node/271460#comment-886564
1364
    if ($aid) {
1365
      actions_do($aid, $object, $context);
1366
    }
1367
  }
1368
  // Actions specifically for this flag.
1369
  foreach (trigger_get_assigned_actions('flag_' . $action . '_' . $flag->name) as $aid => $action_info) {
1370
    if ($aid) {
1371
      actions_do($aid, $object, $context);
1372
    }
1373
  }
1374
}
1375

    
1376
/**
1377
 * Implements hook_flag_access().
1378
 */
1379
function flag_flag_access($flag, $entity_id, $action, $account) {
1380
  // Do nothing if there is no restriction by authorship.
1381
  if (empty($flag->access_author)) {
1382
    return;
1383
  }
1384

    
1385
  // Restrict access by authorship. It's important that TRUE is never returned
1386
  // here, otherwise we'd grant permission even if other modules denied access.
1387
  if ($flag->entity_type == 'node') {
1388
    // For non-existent nodes (such as on the node add form), assume that the
1389
    // current user is creating the content.
1390
    if (empty($entity_id) || !($node = $flag->fetch_entity($entity_id))) {
1391
      return $flag->access_author == 'others' ? FALSE : NULL;
1392
    }
1393

    
1394
    if ($flag->access_author == 'own' && $node->uid != $account->uid) {
1395
      return FALSE;
1396
    }
1397
    elseif ($flag->access_author == 'others' && $node->uid == $account->uid) {
1398
      return FALSE;
1399
    }
1400
  }
1401

    
1402
  // Restrict access by comment authorship.
1403
  if ($flag->entity_type == 'comment') {
1404
    // For non-existent comments (such as on the comment add form), assume that
1405
    // the current user is creating the content.
1406
    if (empty($entity_id) || !($comment = $flag->fetch_entity($entity_id))) {
1407
      return $flag->access_author == 'comment_others' ? FALSE : NULL;
1408
    }
1409

    
1410
    $node = node_load($comment->nid);
1411
    if ($flag->access_author == 'node_own' && $node->uid != $account->uid) {
1412
      return FALSE;
1413
    }
1414
    elseif ($flag->access_author == 'node_others' && $node->uid == $account->uid) {
1415
      return FALSE;
1416
    }
1417
    elseif ($flag->access_author == 'comment_own' && $comment->uid != $account->uid) {
1418
      return FALSE;
1419
    }
1420
    elseif ($flag->access_author == 'comment_others' && $comment->uid == $account->uid) {
1421
      return FALSE;
1422
    }
1423
  }
1424
}
1425

    
1426
/**
1427
 * Implements hook_flag_access_multiple().
1428
 */
1429
function flag_flag_access_multiple($flag, $entity_ids, $account) {
1430
  $access = array();
1431

    
1432
  // Do nothing if there is no restriction by authorship.
1433
  if (empty($flag->access_author)) {
1434
    return $access;
1435
  }
1436

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

    
1457
  if ($flag->entity_type == 'comment') {
1458
    // Restrict access by comment ownership.
1459
    $query = db_select('comment', 'c');
1460
    $query->leftJoin('node', 'n', 'c.nid = n.nid');
1461
    $query
1462
      ->fields('c', array('cid', 'nid', 'uid'))
1463
      ->condition('c.cid', $entity_ids, 'IN');
1464
    $query->addField('c', 'uid', 'comment_uid');
1465
    $result = $query->execute();
1466

    
1467
    foreach ($result as $row) {
1468
      if ($flag->access_author == 'node_own') {
1469
        $access[$row->cid] = $row->node_uid != $account->uid ? FALSE : NULL;
1470
      }
1471
      elseif ($flag->access_author == 'node_others') {
1472
        $access[$row->cid] = $row->node_uid == $account->uid ? FALSE : NULL;
1473
      }
1474
      elseif ($flag->access_author == 'comment_own') {
1475
        $access[$row->cid] = $row->comment_uid != $account->uid ? FALSE : NULL;
1476
      }
1477
      elseif ($flag->access_author == 'comment_others') {
1478
        $access[$row->cid] = $row->comment_uid == $account->uid ? FALSE : NULL;
1479
      }
1480
    }
1481
  }
1482

    
1483
  // Always return an array (even if empty) of accesses.
1484
  return $access;
1485
}
1486

    
1487
/**
1488
 * Implements hook_theme().
1489
 */
1490
function flag_theme() {
1491
  $path = drupal_get_path('module', 'flag') . '/theme';
1492

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

    
1540
/**
1541
 * A preprocess function for our theme('flag'). It generates the
1542
 * variables needed there.
1543
 *
1544
 * The $variables array initially contains the following arguments:
1545
 * - $flag
1546
 * - $action
1547
 * - $entity_id
1548
 * - $after_flagging
1549
 * - $errors
1550
 * - $needs_wrapping_element
1551
 *
1552
 * See 'flag.tpl.php' for their documentation.
1553
 */
1554
function template_preprocess_flag(&$variables) {
1555
  global $user;
1556
  $initialized = &drupal_static(__FUNCTION__, array());
1557

    
1558
  // Some typing shotcuts:
1559
  $flag =& $variables['flag'];
1560
  $action = $variables['action'];
1561
  $entity_id = $variables['entity_id'];
1562
  $errors = join('<br />', $variables['errors']);
1563
  $flag_css_name = str_replace('_', '-', $flag->name);
1564

    
1565
  // Generate the link URL.
1566
  $link_type = $flag->get_link_type();
1567
  $link = module_invoke($link_type['module'], 'flag_link', $flag, $action, $entity_id);
1568
  if (isset($link['title']) && empty($link['html'])) {
1569
    $link['title'] = check_plain($link['title']);
1570
  }
1571

    
1572
  // Replace the link with the access denied text if unable to flag.
1573
  if ($action == 'unflag' && !$flag->access($entity_id, 'unflag')) {
1574
    $link['title'] = $flag->get_label('unflag_denied_text', $entity_id);
1575
    unset($link['href']);
1576
  }
1577

    
1578
  // Anonymous users always need the JavaScript to maintain their flag state.
1579
  if ($user->uid == 0) {
1580
    $link_type['uses standard js'] = TRUE;
1581
  }
1582

    
1583
  // Load the JavaScript/CSS, if the link type requires it.
1584
  if (!isset($initialized[$link_type['name']])) {
1585
    if ($link_type['uses standard css']) {
1586
      drupal_add_css(drupal_get_path('module', 'flag') . '/theme/flag.css');
1587
    }
1588
    if ($link_type['uses standard js']) {
1589
      drupal_add_js(drupal_get_path('module', 'flag') . '/theme/flag.js');
1590
    }
1591
    $initialized[$link_type['name']] = TRUE;
1592
  }
1593

    
1594
  $variables['link'] = $link;
1595
  $variables['link_href'] = isset($link['href']) ? check_url(url($link['href'], $link)) : FALSE;
1596
  $variables['link_text'] = isset($link['title']) ? $link['title'] : $flag->get_label($action . '_short', $entity_id);
1597
  $variables['link_title'] = isset($link['attributes']['title']) ? check_plain($link['attributes']['title']) : check_plain(strip_tags($flag->get_label($action . '_long', $entity_id)));
1598
  $variables['status'] = ($action == 'flag' ? 'unflagged' : 'flagged');
1599
  $variables['flag_name_css'] = $flag_css_name;
1600

    
1601
  $variables['flag_wrapper_classes_array'] = array();
1602
  $variables['flag_wrapper_classes_array'][] = 'flag-wrapper';
1603
  $variables['flag_wrapper_classes_array'][] = 'flag-' . $flag_css_name;
1604
  $variables['flag_wrapper_classes_array'][] = 'flag-' . $flag_css_name . '-' . $entity_id;
1605

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

    
1641
/**
1642
 * Theme processor for flag.tpl.php.
1643
 *
1644
 * @param array &$variables
1645
 *  An array of variables for the template. See 'flag.tpl.php' for their
1646
 *  documentation.
1647
 */
1648
function template_process_flag(&$variables) {
1649
  // Convert class arrays to strings.
1650
  $variables['flag_wrapper_classes'] = implode(' ', $variables['flag_wrapper_classes_array']);
1651
  $variables['flag_classes'] = implode(' ', $variables['flag_classes_array']);
1652
  $variables['message_classes'] = implode(' ', $variables['message_classes_array']);
1653
}
1654

    
1655
/**
1656
 * Return an array of flag names keyed by fid.
1657
 */
1658
function _flag_get_flag_names() {
1659
  $flags = flag_get_flags();
1660
  $flag_names = array();
1661
  foreach ($flags as $flag) {
1662
    $flag_names[$flag->fid] = $flag->name;
1663
  }
1664
  return $flag_names;
1665
}
1666

    
1667
/**
1668
 * Return an array of flag link types suitable for a select list or radios.
1669
 */
1670
function _flag_link_type_options() {
1671
  $options = array();
1672
  $types = flag_get_link_types();
1673
  foreach ($types as $type_name => $type) {
1674
    $options[$type_name] = $type['title'];
1675
  }
1676
  return $options;
1677
}
1678

    
1679
/**
1680
 * Return an array of flag link type descriptions.
1681
 */
1682
function _flag_link_type_descriptions() {
1683
  $options = array();
1684
  $types = flag_get_link_types();
1685
  foreach ($types as $type_name => $type) {
1686
    $options[$type_name] = $type['description'];
1687
  }
1688
  return $options;
1689
}
1690

    
1691
// ---------------------------------------------------------------------------
1692
// Non-Views public API
1693

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

    
1717
  // We check to see if the flag count is already in the cache,
1718
  // if it's not, run the query.
1719
  if (!isset($counts[$flag->name][$entity_type])) {
1720
    $counts[$flag->name][$entity_type] = array();
1721
    $result = db_select('flagging', 'f')
1722
      ->fields('f', array('fid'))
1723
      ->condition('fid', $flag->fid)
1724
      ->condition('entity_type', $entity_type)
1725
      ->countQuery()
1726
      ->execute()
1727
      ->fetchField();
1728
    $counts[$flag->name][$entity_type] =  $result;
1729
  }
1730

    
1731
  return $counts[$flag->name][$entity_type];
1732
}
1733

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

    
1757
  // We check to see if the flag count is already in the cache,
1758
  // if it's not, run the query.
1759
  if (!isset($counts[$flag->name][$user->uid])) {
1760
    $counts[$flag->name][$user->uid] = array();
1761
    $result = db_select('flagging', 'f')
1762
      ->fields('f', array('fid'))
1763
      ->condition('fid', $flag->fid)
1764
      ->condition('uid', $user->uid)
1765
      ->countQuery()
1766
      ->execute()
1767
      ->fetchField();
1768
    $counts[$flag->name][$user->uid] = $result;
1769
  }
1770

    
1771
  return $counts[$flag->name][$user->uid];
1772
}
1773

    
1774
/**
1775
 * Get flag counts for all flags on a node.
1776
 *
1777
 * When called during a flagging or unflagging (such as from a hook
1778
 * implementation or from Rules), the count this returns takes into account the
1779
 * the flagging or unflagging that is in the process of being performed.
1780
 *
1781
 * @param $entity_type
1782
 *   The entity type (usually 'node').
1783
 * @param $entity_id
1784
 *   The entity ID (usually the node ID).
1785
 *
1786
 * @return
1787
 *   The flag count with the entity type and id as array keys.
1788
 */
1789
function flag_get_counts($entity_type, $entity_id) {
1790
  $counts = &drupal_static(__FUNCTION__);
1791

    
1792
  if (!isset($counts[$entity_type][$entity_id])) {
1793
    $counts[$entity_type][$entity_id] = array();
1794
    $query = db_select('flag', 'f');
1795
    $query->leftJoin('flag_counts', 'fc', 'f.fid = fc.fid');
1796
    $result = $query
1797
      ->fields('f', array('name'))
1798
      ->fields('fc', array('count'))
1799
      ->condition('fc.entity_type', $entity_type)
1800
      ->condition('fc.entity_id', $entity_id)
1801
      ->execute();
1802
    foreach ($result as $row) {
1803
      $counts[$entity_type][$entity_id][$row->name] = $row->count;
1804
    }
1805
  }
1806

    
1807
  return $counts[$entity_type][$entity_id];
1808
}
1809

    
1810
/**
1811
 * Get the total count of items flagged within a flag.
1812
 *
1813
 * When called during a flagging or unflagging (such as from a hook
1814
 * implementation or from Rules), the count this returns takes into account the
1815
 * the flagging or unflagging that is in the process of being performed.
1816
 *
1817
 * @param $flag_name
1818
 *   The flag name for which to retrieve a flag count.
1819
 * @param $reset
1820
 *   (optional) Reset the internal cache and execute the SQL query another time.
1821
 */
1822
function flag_get_flag_counts($flag_name, $reset = FALSE) {
1823
  $counts = &drupal_static(__FUNCTION__);
1824

    
1825
  if ($reset) {
1826
    $counts = array();
1827
  }
1828
  if (!isset($counts[$flag_name])) {
1829
    $flag = flag_get_flag($flag_name);
1830
    $counts[$flag_name] = db_select('flag_counts', 'fc')
1831
      ->fields('fc', array('fid'))
1832
      ->condition('fid', $flag->fid)
1833
      ->countQuery()
1834
      ->execute()
1835
      ->fetchField();
1836
  }
1837

    
1838
  return $counts[$flag_name];
1839
}
1840

    
1841
/**
1842
 * Load a single flag either by name or by flag ID.
1843
 *
1844
 * @param $name
1845
 *  (optional) The flag name.
1846
 * @param $fid
1847
 *  (optional) The the flag id.
1848
 *
1849
 * @return
1850
 *  The flag object, or FALSE if no matching flag was found.
1851
 */
1852
function flag_get_flag($name = NULL, $fid = NULL) {
1853
  $flags = flag_get_flags();
1854
  if (isset($name)) {
1855
    if (isset($flags[$name])) {
1856
      return $flags[$name];
1857
    }
1858
  }
1859
  elseif (isset($fid)) {
1860
    foreach ($flags as $flag) {
1861
      if ($flag->fid == $fid) {
1862
        return $flag;
1863
      }
1864
    }
1865
  }
1866
  return FALSE;
1867
}
1868

    
1869
/**
1870
 * List all flags available.
1871
 *
1872
 * If node type or account are entered, a list of all possible flags will be
1873
 * returned.
1874
 *
1875
 * @param $entity_type
1876
 *   (optional) The type of entity for which to load the flags. Usually 'node'.
1877
 * @param $content_subtype
1878
 *   (optional) The node type for which to load the flags.
1879
 * @param $account
1880
 *   (optional) The user accont to filter available flags. If not set, all
1881
 *   flags for will this node will be returned.
1882
 *
1883
 * @return
1884
 *   An array of the structure [fid] = flag_object.
1885
 */
1886
function flag_get_flags($entity_type = NULL, $content_subtype = NULL, $account = NULL) {
1887
  $flags = &drupal_static(__FUNCTION__);
1888

    
1889
  // Retrieve a list of all flags, regardless of the parameters.
1890
  if (!isset($flags)) {
1891
    $flags = array();
1892

    
1893
    // Database flags.
1894
    $query = db_select('flag', 'f');
1895
    $query->leftJoin('flag_types', 'fn', 'fn.fid = f.fid');
1896
    $result = $query
1897
      ->fields('f', array('fid', 'entity_type', 'name', 'title', 'global', 'options'))
1898
      ->fields('fn', array('type'))
1899
      ->execute();
1900
    foreach ($result as $row) {
1901
      if (!isset($flags[$row->name])) {
1902
        $flags[$row->name] = flag_flag::factory_by_row($row);
1903
      }
1904
      else {
1905
        $flags[$row->name]->types[] = $row->type;
1906
      }
1907
    }
1908

    
1909
    // Add code-based flags provided by modules.
1910
    $default_flags = flag_get_default_flags();
1911
    foreach ($default_flags as $name => $default_flag) {
1912
      // Insert new enabled flags into the database to give them an FID.
1913
      if ($default_flag->status && !isset($flags[$name])) {
1914
        $default_flag->save();
1915
        $flags[$name] = $default_flag;
1916
      }
1917

    
1918
      if (isset($flags[$name])) {
1919
        // Ensure overridden flags are associated with their parent module.
1920
        $flags[$name]->module = $default_flag->module;
1921

    
1922
        // Update the flag with any properties that are "locked" by the code version.
1923
        if (isset($default_flag->locked)) {
1924
          $flags[$name]->locked = $default_flag->locked;
1925
          foreach ($default_flag->locked as $property) {
1926
            $flags[$name]->$property = $default_flag->$property;
1927
          }
1928
        }
1929
      }
1930
    }
1931

    
1932
    // Sort the list of flags by weight.
1933
    uasort($flags, '_flag_compare_weight');
1934

    
1935
    foreach ($flags as $flag) {
1936
      // Allow modules implementing hook_flag_alter(&$flag) to modify each flag.
1937
      drupal_alter('flag', $flag);
1938
    }
1939
  }
1940

    
1941
  // Make a variable copy to filter types and account.
1942
  $filtered_flags = $flags;
1943

    
1944
  // Filter out flags based on type and subtype.
1945
  if (isset($entity_type) || isset($content_subtype)) {
1946
    foreach ($filtered_flags as $name => $flag) {
1947
      if (!$flag->access_entity_enabled($entity_type, $content_subtype)) {
1948
        unset($filtered_flags[$name]);
1949
      }
1950
    }
1951
  }
1952

    
1953
  // Filter out flags based on account permissions.
1954
  if (isset($account) && $account->uid != 1) {
1955
    foreach ($filtered_flags as $name => $flag) {
1956
      // We test against the 'flag' action, which is the minimum permission to
1957
      // use a flag.
1958
      if (!$flag->user_access('flag', $account)) {
1959
        unset($filtered_flags[$name]);
1960
      }
1961
    }
1962
  }
1963

    
1964
  return $filtered_flags;
1965
}
1966

    
1967
/**
1968
 * Comparison function for uasort().
1969
 */
1970
function _flag_compare_weight($flag1, $flag2) {
1971
  if ($flag1->weight == $flag2->weight) {
1972
    return 0;
1973
  }
1974
  return $flag1->weight < $flag2->weight ? -1 : 1;
1975
}
1976

    
1977
/**
1978
 * Retrieve a list of flags defined by modules.
1979
 *
1980
 * @param $include_disabled
1981
 *   (optional) Unless specified, only enabled flags will be returned.
1982
 *
1983
 * @return
1984
 *   An array of flag prototypes, not usable for flagging. Use flag_get_flags()
1985
 *   if needing to perform a flagging with any enabled flag.
1986
 */
1987
function flag_get_default_flags($include_disabled = FALSE) {
1988
  $default_flags = array();
1989
  $flag_status = variable_get('flag_default_flag_status', array());
1990

    
1991
  foreach (module_implements('flag_default_flags') as $module) {
1992
    $function = $module . '_flag_default_flags';
1993
    foreach ($function() as $flag_name => $flag_info) {
1994
      // Backward compatibility: old exported default flags have their names
1995
      // in $flag_info instead, so we use the += operator to not overwrite it.
1996
      $flag_info += array(
1997
        'name' => $flag_name,
1998
        'module' => $module,
1999
      );
2000
      $flag = flag_flag::factory_by_array($flag_info);
2001

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

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

    
2020
  return $default_flags;
2021
}
2022

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

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

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

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

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

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

    
2124
}
2125

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

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

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

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

    
2256
  // Execute the query
2257
  $result = $query->execute();
2258

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

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

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

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

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

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

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

    
2345
      cache_set('flag_link_type_info', $link_types);
2346
    }
2347
  }
2348

    
2349
  return $link_types;
2350
}
2351

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

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

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

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

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

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

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

    
2434
// ---------------------------------------------------------------------------
2435
// Drupal Core operations
2436

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

    
2445
  $flags = flag_get_flags('node', NULL, $user);
2446
  $operations = array();
2447

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

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

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

    
2484
  $flags = flag_get_flags('user', NULL, $user);
2485
  $operations = array();
2486

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

    
2510
// ---------------------------------------------------------------------------
2511
// Contrib integration hooks
2512

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

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

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

    
2546
// ---------------------------------------------------------------------------
2547
// Entity Metadata callbacks
2548

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

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

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

    
2566
  $flagging_data = flag_get_user_flags($flag_entity_type, NULL, $entity->uid);
2567

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

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

    
2581
  $flagging_data = flag_get_entity_flags($entity_type, $entity_id, $property_info['flag_name']);
2582

    
2583
  return array_keys($flagging_data);
2584
}
2585

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