Projet

Général

Profil

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

root / htmltest / sites / all / modules / flag / flag.module @ a5572547

1 85ad3d82 Assos Assos
<?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 018e218c Assos Assos
      'creation callback' => 'flagging_create',
37 85ad3d82 Assos Assos
    ),
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 018e218c Assos Assos
/**
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 85ad3d82 Assos Assos
/**
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
  $flags = flag_get_flags();
602
  // Provide flag and unflag permissions for each flag.
603
  foreach ($flags as $flag_name => $flag) {
604
    $permissions += $flag->get_permissions();
605
  }
606
607
  return $permissions;
608
}
609
610
/**
611
 * Implements hook_form_FORM_ID_alter(): user_admin_permissions.
612
 *
613
 * Disable permission on the permissions form that don't make sense for
614
 * anonymous users when Session API module is not enabled.
615
 */
616
function flag_form_user_admin_permissions_alter(&$form, &$form_state, $form_id) {
617
  if (!module_exists('session_api')) {
618
    $flags = flag_get_flags();
619
    // Disable flag and unflag permission checkboxes for anonymous users.
620
    foreach ($flags as $flag_name => $flag) {
621
      $form['checkboxes'][DRUPAL_ANONYMOUS_RID]["flag $flag_name"]['#disabled'] = TRUE;
622
      $form['checkboxes'][DRUPAL_ANONYMOUS_RID]["unflag $flag_name"]['#disabled'] = TRUE;
623
    }
624
  }
625
}
626
627
/**
628
 * Implements hook_flag_link().
629
 */
630
function flag_flag_link($flag, $action, $entity_id) {
631
  $token = flag_get_token($entity_id);
632
  return array(
633
    'href' => 'flag/' . ($flag->link_type == 'confirm' ? 'confirm/' : '') . "$action/$flag->name/$entity_id",
634
    'query' => drupal_get_destination() + ($flag->link_type == 'confirm' ? array() : array('token' => $token)),
635
  );
636
}
637
638
/**
639
 * Implements hook_field_extra_fields().
640
 */
641
function flag_field_extra_fields() {
642
  $extra = array();
643
644
  $flags = flag_get_flags();
645
  foreach ($flags as $name => $flag) {
646
    // Skip flags that aren't on entities.
647
    if (!($flag instanceof flag_entity)) {
648
      continue;
649
    }
650
651 018e218c Assos Assos
    $applicable_bundles = $flag->types;
652
    // If the list of bundles is empty, it indicates all bundles apply.
653
    if (empty($applicable_bundles)) {
654
      $entity_info = entity_get_info($flag->entity_type);
655
      $applicable_bundles = array_keys($entity_info['bundles']);
656
    }
657
658
    foreach ($applicable_bundles as $bundle_name) {
659 85ad3d82 Assos Assos
      if ($flag->show_on_form) {
660
        $extra[$flag->entity_type][$bundle_name]['form']['flag'] = array(
661
          'label' => t('Flags'),
662
          'description' => t('Checkboxes for toggling flags'),
663
          'weight' => 10
664
        );
665
      }
666
667
      if ($flag->show_as_field) {
668
        $extra[$flag->entity_type][$bundle_name]['display']['flag_' . $name] = array(
669
          // It would be nicer to use % as the placeholder, but the label is
670
          // run through check_plain() by field_ui_display_overview_form()
671
          // (arguably incorrectly; see http://drupal.org/node/1991292).
672
          'label' => t('Flag: @title', array(
673
            '@title' => $flag->title,
674
          )),
675
          'description' => t('Individual flag link'),
676
          'weight' => 10,
677
        );
678
      }
679
    }
680
  }
681
682
  return $extra;
683
}
684
685
/**
686
 * Implements hook_form_FORM_ID_alter(): node_type_form.
687
 */
688
function flag_form_node_type_form_alter(&$form, &$form_state, $form_id) {
689
  global $user;
690
  $flags = flag_get_flags('node', $form['#node_type']->type, $user);
691
  foreach ($flags as $flag) {
692
    if ($flag->show_on_form) {
693
      // To be able to process node tokens in flag labels, we create a fake
694
      // node and store it in the flag's cache for replace_tokens() to find,
695
      // with a fake ID.
696
      $flag->remember_entity('fake', (object) array(
697
        'nid' => NULL,
698
        'type' => $form['#node_type']->type,
699
        'title' => '',
700
      ));
701
      $var = 'flag_' . $flag->name . '_default';
702
      $form['workflow']['flag'][$var] = array(
703
        '#type' => 'checkbox',
704
        '#title' => $flag->get_label('flag_short', 'fake'),
705
        '#default_value' => variable_get($var . '_' . $form['#node_type']->type, 0),
706
        '#return_value' => 1,
707
      );
708
    }
709
  }
710
711
  if (isset($form['workflow']['flag'])) {
712
    $form['workflow']['flag'] += array(
713
      '#type' => 'item',
714
      '#title' => t('Default flags'),
715
      '#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))),
716
      // Make the spacing a bit more compact:
717
      '#prefix' => '<div class="form-checkboxes">',
718
      '#suffix' => '</div>',
719
    );
720
  }
721
}
722
723
/**
724
 * Implements hook_field_attach_form().
725
 *
726
 * Handles the 'show_on_form' flag option.
727
 *
728
 * Warning: will not work on entity types that are not fieldable, as this relies
729
 * on a field module hook.
730
 *
731
 * @see flag_field_attach_submit().
732
 */
733
function flag_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
734
  list($id) = entity_extract_ids($entity_type, $entity);
735
  // Some modules are being stupid here. Commerce!
736
  if (empty($id)) {
737
    $id = NULL;
738
  }
739
740 018e218c Assos Assos
  // Keep track of whether the entity is new or not, as we're about to fiddle
741
  // with the entity id for the flag's entity cache.
742
  $is_existing_entity = !empty($id);
743
744 85ad3d82 Assos Assos
  // Get all possible flags for this entity type.
745
  $flags = flag_get_flags($entity_type);
746
747
  // Filter out flags which need to be included on the node form.
748
  $flags_in_form = 0;
749
  $flags_visible = 0;
750
  foreach ($flags as $flag) {
751
    if (!$flag->show_on_form) {
752
      continue;
753
    }
754
755
    // Get the flag status.
756 018e218c Assos Assos
    if ($is_existing_entity) {
757 85ad3d82 Assos Assos
      $flag_status = $flag->is_flagged($id);
758
    }
759
    else {
760
      // We don't have per-bundle defaults on general entities yet: default
761
      // status is just unflagged.
762
      $flag_status = FALSE;
763
      // Apply the per-bundle defaults for nodes.
764
      if ($entity_type == 'node') {
765 018e218c Assos Assos
        $node_type = $entity->type;
766
        $flag_status = variable_get('flag_' . $flag->name . '_default_' . $node_type, 0);
767 85ad3d82 Assos Assos
      }
768
769
      // For a new, unsaved entity, make a dummy entity ID so that the flag
770
      // handler can remember the entity. This allows access to the flag to be
771
      // correctly handled in node and comment preview.
772
      $id = 'new';
773
      $flag->remember_entity($id, $entity);
774
    }
775
776
    // If the flag is not global and the user doesn't have access, skip it.
777
    // Global flags have their value set even if the user doesn't have access
778
    // to it, similar to the way "published" and "promote" keep the default
779
    // values even if the user doesn't have "administer nodes" permission.
780
    // Furthermore, a global flag is set to its default value on new nodes
781
    // even if the user creating the node doesn't have access to the flag.
782
    global $user;
783
    $access = $flag->access($id, $flag_status ? 'unflag' : 'flag');
784
    if (!$access && !$flag->global) {
785
      continue;
786
    }
787
788
    $form['flag'][$flag->name] = array(
789
      '#type' => 'checkbox',
790
      '#title' => $flag->get_label('flag_short', $id),
791
      '#description' => $flag->get_label('flag_long', $id),
792
      '#default_value' => $flag_status,
793
      '#return_value' => 1,
794
      // Used by our drupalSetSummary() on vertical tabs.
795
      '#attributes' => array('title' => $flag->get_title()),
796
    );
797
798
    // If the user does not have access to the flag, set as a value.
799
    if (!$access) {
800
      $form['flag'][$flag->name]['#type'] = 'value';
801
      $form['flag'][$flag->name]['#value'] = $flag_status;
802
    }
803
    else {
804
      $flags_visible++;
805
    }
806
    $flags_in_form++;
807
  }
808
809
  if ($flags_in_form) {
810
    $form['flag'] += array(
811
      '#weight' => 1,
812
      '#tree' => TRUE,
813
    );
814
  }
815
  if ($flags_visible) {
816
    $form['flag'] += array(
817
      '#type' => 'fieldset',
818
      '#title' => t('Flags'),
819
      '#collapsible' => TRUE,
820
    );
821
822
    if ($entity_type == 'node') {
823
      // Turn the fieldset into a vertical tab.
824
      $form['flag'] += array(
825
        '#group' => 'additional_settings',
826
        '#attributes' => array('class' => array('flag-fieldset')),
827
        '#attached' => array(
828
          'js' => array(
829
            'vertical-tabs' => drupal_get_path('module', 'flag') . '/theme/flag-admin.js',
830
          ),
831
        ),
832
      );
833
    }
834
  }
835
}
836
837
/**
838
 * Implements hook_field_attach_submit().
839
 *
840
 * @see flag_field_attach_form().
841
 */
842
function flag_field_attach_submit($entity_type, $entity, $form, &$form_state) {
843
  // This is invoked for each flag_field_attach_form(), but possibly more than
844
  // once for a particular form in the case that a form is showing multiple
845
  // entities (field collection, inline entity form). Hence we can't simply
846
  // assume our submitted form values are in $form_state['values']['flag'].
847
  if (isset($form['flag'])) {
848
    $parents = $form['flag']['#parents'];
849
    $flag_values = drupal_array_get_nested_value($form_state['values'], $parents);
850
851
    // Put the form values in the entity so flag_field_attach_save() can find
852
    // them. We can't call flag() here as new entities have no id yet.
853
    $entity->flag = $flag_values;
854
  }
855
}
856
857
/**
858
 * Implements hook_field_attach_insert().
859
 */
860
function flag_field_attach_insert($entity_type, $entity) {
861
  if (isset($entity->flag)) {
862
    flag_field_attach_save($entity_type, $entity);
863
  }
864
}
865
866
/**
867
 * Implements hook_field_attach_update().
868
 */
869
function flag_field_attach_update($entity_type, $entity) {
870
  if (isset($entity->flag)) {
871
    flag_field_attach_save($entity_type, $entity);
872
  }
873
}
874
875
/**
876
 * Shared saving routine between flag_field_attach_insert/update().
877
 *
878
 * @see flag_field_attach_form().
879
 */
880
function flag_field_attach_save($entity_type, $entity) {
881
  list($id) = entity_extract_ids($entity_type, $entity);
882
  // Get the flag values we stashed in the entity in flag_field_attach_submit().
883
  foreach ($entity->flag as $flag_name => $state) {
884
    flag($state ? 'flag' : 'unflag', $flag_name, $id);
885
  }
886
}
887
888
/*
889
 * Implements hook_contextual_links_view_alter().
890
 */
891
function flag_contextual_links_view_alter(&$element, $items) {
892
  if (isset($element['#element']['#entity_type'])) {
893
    $entity_type = $element['#element']['#entity_type'];
894
895
    // Get the entity out of the element. This requires a bit of legwork.
896
    if (isset($element['#element']['#entity'])) {
897
      // EntityAPI entities will all have the entity in the same place.
898
      $entity = $element['#element']['#entity'];
899
    }
900
    elseif (isset($element['#element']['#' . $entity_type])) {
901
      // Node module at least puts it here.
902
      $entity = $element['#element']['#' . $entity_type];
903
    }
904
    else {
905
      // Give up.
906
      return;
907
    }
908
909
    // Get all possible flags for this entity type.
910
    $flags = flag_get_flags($entity_type);
911
912
    foreach ($flags as $name => $flag) {
913
      if (!$flag->show_contextual_link) {
914
        continue;
915
      }
916
917
      list($entity_id) = entity_extract_ids($entity_type, $entity);
918
      if (!$flag->access($entity_id) && (!$flag->is_flagged($entity_id) || !$flag->access($entity_id, 'flag'))) {
919
        // User has no permission to use this flag or flag does not apply to this
920
        // object. The link is not skipped if the user has "flag" access but
921
        // not "unflag" access (this way the unflag denied message is shown).
922
        continue;
923
      }
924
925
      $element['#links']['flag-'. $name] = array(
926
        'title' => $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id),
927
        'html' => TRUE,
928
      );
929
    }
930
  }
931
}
932
933
/**
934
 * Implements hook_entity_view().
935
 *
936
 * Handles the 'show_in_links' and 'show_as_field' flag options.
937
 *
938
 * Note this is broken for taxonomy terms for version of Drupal core < 7.17.
939
 */
940
function flag_entity_view($entity, $type, $view_mode, $langcode) {
941
  // Get all possible flags for this entity type.
942
  $flags = flag_get_flags($type);
943
  foreach ($flags as $flag) {
944
    // Check if the flag outputs on entity view.
945
    if (!($flag->show_as_field || $flag->shows_in_entity_links($view_mode))) {
946
      // Flag is not configured to output on entity view, so skip it to save on
947
      // calls to access checks.
948
      continue;
949
    }
950
951
    $entity_id = $flag->get_entity_id($entity);
952
    // For a new, unsaved entity, make a dummy entity ID so that the flag
953
    // handler can remember the entity. This allows access to the flag to be
954
    // correctly handled in node and comment preview.
955
    if (is_null($entity_id)) {
956
      $entity_id = 'new';
957
    }
958
    $flag->remember_entity($entity_id, $entity);
959
960
    if (!$flag->access($entity_id) && (!$flag->is_flagged($entity_id) || !$flag->access($entity_id, 'flag'))) {
961
      // User has no permission to use this flag or flag does not apply to this
962
      // entity. The link is not skipped if the user has "flag" access but
963
      // not "unflag" access (this way the unflag denied message is shown).
964
      continue;
965
    }
966
967
    // We're good to go. Output the flag in the appropriate manner(s).
968
969
    // The old-style entity links output.
970
    if ($flag->shows_in_entity_links($view_mode)) {
971
      // The flag links are actually fully rendered theme functions.
972
      // The HTML attribute is set to TRUE to allow whatever the themer desires.
973
      $links['flag-' . $flag->name] = array(
974
        'title' => $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id),
975
        'html' => TRUE,
976
      );
977
    }
978
979
    // The pseudofield output.
980
    if ($flag->show_as_field) {
981
      $entity->content['flag_' . $flag->name] = array(
982
       '#markup' => $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id, array('needs_wrapping_element' => TRUE)),
983
      );
984
    }
985
  }
986
987
  // If any links were made, add them to the entity's links array.
988
  if (isset($links)) {
989
    $entity->content['links']['flag'] = array(
990
      '#theme' => 'links',
991
      '#links' => $links,
992
      '#attributes' => array('class' => array('links', 'inline')),
993
    );
994
  }
995
}
996
997
/**
998
 * Implements hook_node_insert().
999
 */
1000
function flag_node_insert($node) {
1001
  flag_node_save($node);
1002
}
1003
1004
/**
1005
 * Implements hook_node_update().
1006
 */
1007
function flag_node_update($node) {
1008
  flag_node_save($node);
1009
}
1010
1011
/**
1012
 * Shared saving routine between flag_node_insert() and flag_node_update().
1013
 */
1014
function flag_node_save($node) {
1015
  // Response to the flag checkboxes added to the form in flag_form_alter().
1016
  $remembered = FALSE;
1017
  if (isset($node->flag)) {
1018
    foreach ($node->flag as $name => $state) {
1019
      $flag = flag_get_flag($name);
1020
      // Flagging may trigger actions. We want actions to get the current
1021
      // node, not a stale database-loaded one:
1022
      if (!$remembered) {
1023
        $flag->remember_entity($node->nid, $node);
1024
        // Actions may modify a node, and we don't want to overwrite this
1025
        // modification:
1026
        $remembered = TRUE;
1027
      }
1028
1029
      $action = $state ? 'flag' : 'unflag';
1030
      // Pass TRUE for $skip_permission_check so that flags that have been
1031
      // passed through as hidden form values are saved.
1032
      $flag->flag($action, $node->nid, NULL, TRUE);
1033
    }
1034
  }
1035
}
1036
1037
/**
1038
 * Implements hook_entity_delete().
1039
 */
1040
function flag_entity_delete($entity, $type) {
1041
  // Node and user flags handle things through the entity type delete hooks.
1042
  // @todo: make this configurable in the flag type definition?
1043
  if ($type == 'node' || $type == 'user') {
1044
    return;
1045
  }
1046
1047
  list($id) = entity_extract_ids($type, $entity);
1048
  _flag_entity_delete($type, $id);
1049
}
1050
1051
/**
1052
 * Implements hook_node_delete().
1053
 */
1054
function flag_node_delete($node) {
1055
  foreach (flag_get_flags('node') as $flag) {
1056
    // If the flag is being tracked by translation set and the node is part
1057
    // of a translation set, don't delete the flagging record.
1058
    // Instead, data will be updated in hook_node_translation_change(), below.
1059
    if (!$flag->i18n || empty($node->tnid)) {
1060
      _flag_entity_delete('node', $node->nid, $flag->fid);
1061
    }
1062
  }
1063
}
1064
1065
/**
1066
 * Implements hook_node_translation_change().
1067
 *
1068
 * (Hook provided by translation_helpers module.)
1069
 */
1070
function flag_node_translation_change($node) {
1071
  if (isset($node->translation_change)) {
1072
    // If there is only one node remaining, track by nid rather than tnid.
1073
    // Otherwise, use the new tnid.
1074
    $entity_id = $node->translation_change['new_tnid'] == 0 ? $node->translation_change['remaining_nid'] : $node->translation_change['new_tnid'];
1075
    foreach (flag_get_flags('node') as $flag) {
1076
      if ($flag->i18n) {
1077
        db_update('flagging')->fields(array('entity_id' => $entity_id))
1078
          ->condition('fid', $flag->fid)
1079
          ->condition('entity_id', $node->translation_change['old_tnid'])
1080
          ->execute();
1081
        db_update('flag_counts')->fields(array('entity_id' => $entity_id))
1082
          ->condition('fid', $flag->fid)
1083
          ->condition('entity_id', $node->translation_change['old_tnid'])
1084
          ->execute();
1085
      }
1086
    }
1087
  }
1088
}
1089
1090
/**
1091
 * Deletes flagging records for the entity.
1092
 *
1093
 * @param $entity_type
1094
 *   The type of the entity being deleted; e.g. 'node' or 'comment'.
1095
 * @param $entity_id
1096
 *   The ID of the entity being deleted.
1097
 * @param $fid
1098
 *   The flag id
1099
 */
1100
function _flag_entity_delete($entity_type, $entity_id, $fid = NULL) {
1101
  $query_content = db_delete('flagging')
1102
    ->condition('entity_type', $entity_type)
1103
    ->condition('entity_id', $entity_id);
1104
  $query_counts = db_delete('flag_counts')
1105
    ->condition('entity_type', $entity_type)
1106
    ->condition('entity_id', $entity_id);
1107
  if (isset($fid)) {
1108
    $query_content->condition('fid', $fid);
1109
    $query_counts->condition('fid', $fid);
1110
  }
1111
  $query_content->execute();
1112
  $query_counts->execute();
1113
}
1114
1115
/**
1116
 * Implements hook_user_login().
1117
 */
1118
function flag_user_login(&$edit, &$account) {
1119
  // Migrate anonymous flags to this user's account.
1120
  if (module_exists('session_api') && ($sid = flag_get_sid(0))) {
1121
    // Get a list of flagging IDs that will be moved over.
1122
    $duplicate_flaggings = array();
1123
    $flaggings = db_select('flagging', 'fc')
1124
      ->fields('fc', array('flagging_id', 'fid', 'entity_id'))
1125
      ->condition('uid', 0)
1126
      ->condition('sid', $sid)
1127
      ->execute()
1128
      ->fetchAllAssoc('flagging_id', PDO::FETCH_ASSOC);
1129
1130
    // Convert anonymous flaggings to their authenticated account.
1131
    foreach ($flaggings as $flagging_id => $flagging) {
1132
      // Each update is wrapped in a try block to prevent unique key errors.
1133
      // Any duplicate object that was flagged as anonoymous is deleted in the
1134
      // subsequent db_delete() call.
1135
      try {
1136
        db_update('flagging')
1137
          ->fields(array(
1138
            'uid' => $account->uid,
1139
            'sid' => 0,
1140
          ))
1141
          ->condition('flagging_id', $flagging_id)
1142
          ->execute();
1143
      }
1144
      catch (Exception $e) {
1145
        $duplicate_flaggings[$flagging_id] = $flagging;
1146
      }
1147
    }
1148
1149
    // Delete any remaining flags this user had as an anonymous user. We use the
1150
    // proper unflag action here to make sure the count gets decremented again
1151
    // and so that other modules can clean up their tables if needed.
1152
    $anonymous_user = drupal_anonymous_user();
1153
    foreach ($duplicate_flaggings as $flagging_id => $flagging) {
1154
      $flag = flag_get_flag(NULL, $flagging['fid']);
1155
      $flag->flag('unflag', $flagging['entity_id'], $anonymous_user, TRUE);
1156
    }
1157
1158
    // Clean up anonymous cookies.
1159
    FlagCookieStorage::drop();
1160
  }
1161
}
1162
1163
/**
1164
 * Implements hook_user_cancel().
1165
 */
1166
function flag_user_cancel($edit, $account, $method) {
1167
  flag_user_account_removal($account);
1168
}
1169
1170
/**
1171
 * Implements hook_user_delete().
1172
 */
1173
function flag_user_delete($account) {
1174
  flag_user_account_removal($account);
1175
}
1176
1177
/**
1178
 * Shared helper for user account cancellation or deletion.
1179
 */
1180
function flag_user_account_removal($account) {
1181
  // Remove flags by this user.
1182
  $query = db_select('flagging', 'fc');
1183
  $query->leftJoin('flag_counts', 'c', 'fc.entity_id = c.entity_id AND fc.entity_type = c.entity_type');
1184
  $result = $query
1185
    ->fields('fc', array('fid', 'entity_id'))
1186
    ->fields('c', array('count'))
1187
    ->condition('fc.uid', $account->uid)
1188
    ->execute();
1189
1190
  foreach ($result as $flag_data) {
1191
    // Only decrement the flag count table if it's greater than 1.
1192
    if ($flag_data->count > 0) {
1193
      $flag_data->count--;
1194
      db_update('flag_counts')
1195
        ->fields(array(
1196
          'count' => $flag_data->count,
1197
        ))
1198
        ->condition('fid', $flag_data->fid)
1199
        ->condition('entity_id', $flag_data->entity_id)
1200
        ->execute();
1201
    }
1202
    elseif ($flag_data->count == 0) {
1203
      db_delete('flag_counts')
1204
        ->condition('fid', $flag_data->fid)
1205
        ->condition('entity_id', $flag_data->entity_id)
1206
        ->execute();
1207
    }
1208
  }
1209
  db_delete('flagging')
1210
    ->condition('uid', $account->uid)
1211
    ->execute();
1212
1213
  // Remove flags that have been done to this user.
1214
  _flag_entity_delete('user', $account->uid);
1215
}
1216
1217
/**
1218
 * Implements hook_user_view().
1219
 */
1220
function flag_user_view($account, $view_mode) {
1221
  $flags = flag_get_flags('user');
1222
  $flag_items = array();
1223
  foreach ($flags as $flag) {
1224
    if (!$flag->access($account->uid)) {
1225
      // User has no permission to use this flag.
1226
      continue;
1227
    }
1228
    if (!$flag->show_on_profile) {
1229
      // Flag not set to appear on profile.
1230
      continue;
1231
    }
1232
    $flag_items[$flag->name] = array(
1233
      '#type' => 'user_profile_item',
1234
      '#title' => $flag->get_title($account->uid),
1235
      '#markup' => $flag->theme($flag->is_flagged($account->uid) ? 'unflag' : 'flag', $account->uid),
1236
      '#attributes' => array('class' => array('flag-profile-' . $flag->name)),
1237
    );
1238
  }
1239
  if (!empty($flag_items)) {
1240
    $account->content['flags'] = $flag_items;
1241
    $account->content['flags'] += array(
1242
      '#type' => 'user_profile_category',
1243
      '#title' => t('Actions'),
1244
      '#attributes' => array('class' => array('flag-profile')),
1245
    );
1246
  }
1247
}
1248
1249
/**
1250
 * Implements hook_session_api_cleanup().
1251
 *
1252
 * Clear out anonymous user flaggings during Session API cleanup.
1253
 */
1254
function flag_session_api_cleanup($arg = 'run') {
1255
  // Session API 1.1 version:
1256
  if ($arg == 'run') {
1257
    $query = db_select('flagging', 'fc');
1258
    $query->leftJoin('session_api', 's', 'fc.sid = s.sid');
1259
    $result = $query
1260
      ->fields('fc', array('sid'))
1261
      ->condition('fc.sid', 0, '<>')
1262
      ->isNull('s.sid')
1263
      ->execute();
1264
    foreach ($result as $row) {
1265
      db_delete('flagging')
1266
        ->condition('sid', $row->sid)
1267
        ->execute();
1268
    }
1269
  }
1270
  // Session API 1.2+ version.
1271
  elseif (is_array($arg)) {
1272
    $outdated_sids = $arg;
1273
    db_delete('flagging')->condition('sid', $outdated_sids, 'IN')->execute();
1274
  }
1275
}
1276
1277
/**
1278
 * Implements hook_field_attach_delete_bundle().
1279
 *
1280
 * Delete any flags' applicability to the deleted bundle.
1281
 */
1282
function flag_field_attach_delete_bundle($entity_type, $bundle, $instances) {
1283
  // This query can't use db_delete() because that doesn't support a
1284
  // subquery: see http://drupal.org/node/1267508.
1285
  db_query("DELETE FROM {flag_types} WHERE type = :bundle AND fid IN (SELECT fid FROM {flag} WHERE entity_type = :entity_type)", array(
1286
    ':bundle' => $bundle,
1287
    ':entity_type' => $entity_type,
1288
  ));
1289
}
1290
1291
/**
1292
 * Flags or unflags an item.
1293
 *
1294
 * @param $action
1295
 *   Either 'flag' or 'unflag'.
1296
 * @param $flag_name
1297
 *   The name of the flag to use.
1298
 * @param $entity_id
1299
 *   The ID of the item to flag or unflag.
1300
 * @param $account
1301
 *   (optional) The user on whose behalf to flag. Omit for the current user.
1302
 * @param permissions_check
1303
 *   (optional) A boolean indicating whether to skip permissions.
1304
 *
1305
 * @return
1306
 *   FALSE if some error occured (e.g., user has no permission, flag isn't
1307
 *   applicable to the item, etc.), TRUE otherwise.
1308
 */
1309
function flag($action, $flag_name, $entity_id, $account = NULL, $permissions_check = FALSE) {
1310
  if (!($flag = flag_get_flag($flag_name))) {
1311
    // Flag does not exist.
1312
    return FALSE;
1313
  }
1314
  return $flag->flag($action, $entity_id, $account, $permissions_check);
1315
}
1316
1317
/**
1318
 * Implements hook_flag_flag().
1319
 */
1320
function flag_flag_flag($flag, $entity_id, $account, $flagging) {
1321
  if (module_exists('trigger')) {
1322
    flag_flag_trigger('flag', $flag, $entity_id, $account, $flagging);
1323
  }
1324
}
1325
1326
/**
1327
 * Implements hook_flag_unflag().
1328
 */
1329
function flag_flag_unflag($flag, $entity_id, $account, $flagging) {
1330
  if (module_exists('trigger')) {
1331
    flag_flag_trigger('unflag', $flag, $entity_id, $account, $flagging);
1332
  }
1333
}
1334
1335
/**
1336
 * Trigger actions if any are available. Helper for hook_flag_(un)flag().
1337
 *
1338
 * @param $op
1339
 *  The operation being performed: one of 'flag' or 'unflag'.
1340
 * @param $flag
1341
 *  The flag object.
1342
 * @param $entity_id
1343
 *  The id of the entity the flag is on.
1344
 * @param $account
1345
 *  The user account performing the action.
1346
 * @param $flagging_id
1347
 *  The flagging entity.
1348
 */
1349
function flag_flag_trigger($action, $flag, $entity_id, $account, $flagging) {
1350
  $context['hook'] = 'flag';
1351
  $context['account'] = $account;
1352
  $context['flag'] = $flag;
1353
  $context['op'] = $action;
1354
  // We add to the $context all the objects we know about:
1355
  $context = array_merge($flag->get_relevant_action_objects($entity_id), $context);
1356
  // The primary object the actions work on.
1357
  $object = $flag->fetch_entity($entity_id);
1358
1359
  // Generic "all flags" actions.
1360
  foreach (trigger_get_assigned_actions('flag_' . $action)  as $aid => $action_info) {
1361
    // The 'if ($aid)' is a safeguard against http://drupal.org/node/271460#comment-886564
1362
    if ($aid) {
1363
      actions_do($aid, $object, $context);
1364
    }
1365
  }
1366
  // Actions specifically for this flag.
1367
  foreach (trigger_get_assigned_actions('flag_' . $action . '_' . $flag->name) as $aid => $action_info) {
1368
    if ($aid) {
1369
      actions_do($aid, $object, $context);
1370
    }
1371
  }
1372
}
1373
1374
/**
1375
 * Implements hook_flag_access().
1376
 */
1377
function flag_flag_access($flag, $entity_id, $action, $account) {
1378
  // Do nothing if there is no restriction by authorship.
1379
  if (empty($flag->access_author)) {
1380
    return;
1381
  }
1382
1383
  // Restrict access by authorship. It's important that TRUE is never returned
1384
  // here, otherwise we'd grant permission even if other modules denied access.
1385
  if ($flag->entity_type == 'node') {
1386
    // For non-existent nodes (such as on the node add form), assume that the
1387
    // current user is creating the content.
1388
    if (empty($entity_id) || !($node = $flag->fetch_entity($entity_id))) {
1389
      return $flag->access_author == 'others' ? FALSE : NULL;
1390
    }
1391
1392
    if ($flag->access_author == 'own' && $node->uid != $account->uid) {
1393
      return FALSE;
1394
    }
1395
    elseif ($flag->access_author == 'others' && $node->uid == $account->uid) {
1396
      return FALSE;
1397
    }
1398
  }
1399
1400
  // Restrict access by comment authorship.
1401
  if ($flag->entity_type == 'comment') {
1402
    // For non-existent comments (such as on the comment add form), assume that
1403
    // the current user is creating the content.
1404
    if (empty($entity_id) || !($comment = $flag->fetch_entity($entity_id))) {
1405
      return $flag->access_author == 'comment_others' ? FALSE : NULL;
1406
    }
1407
1408
    $node = node_load($comment->nid);
1409
    if ($flag->access_author == 'node_own' && $node->uid != $account->uid) {
1410
      return FALSE;
1411
    }
1412
    elseif ($flag->access_author == 'node_others' && $node->uid == $account->uid) {
1413
      return FALSE;
1414
    }
1415
    elseif ($flag->access_author == 'comment_own' && $comment->uid != $account->uid) {
1416
      return FALSE;
1417
    }
1418
    elseif ($flag->access_author == 'comment_others' && $comment->uid == $account->uid) {
1419
      return FALSE;
1420
    }
1421
  }
1422
}
1423
1424
/**
1425
 * Implements hook_flag_access_multiple().
1426
 */
1427
function flag_flag_access_multiple($flag, $entity_ids, $account) {
1428
  $access = array();
1429
1430
  // Do nothing if there is no restriction by authorship.
1431
  if (empty($flag->access_author)) {
1432
    return $access;
1433
  }
1434
1435
  if ($flag->entity_type == 'node') {
1436
    // Restrict access by authorship. This is similar to flag_flag_access()
1437
    // above, but returns an array of 'nid' => $access values. Similarly, we
1438
    // should never return TRUE in any of these access values, only FALSE if we
1439
    // want to deny access, or use the current access value provided by Flag.
1440
    $result = db_select('node', 'n')
1441
      ->fields('n', array('nid', 'uid'))
1442
      ->condition('nid', array_keys($entity_ids), 'IN')
1443
      ->condition('type', $flag->types, 'IN')
1444
      ->execute();
1445
    foreach ($result as $row) {
1446
      if ($flag->access_author == 'own') {
1447
        $access[$row->nid] = $row->uid != $account->uid ? FALSE : NULL;
1448
      }
1449
      elseif ($flag->access_author == 'others') {
1450
        $access[$row->nid] = $row->uid == $account->uid ? FALSE : NULL;
1451
      }
1452
    }
1453
  }
1454
1455
  if ($flag->entity_type == 'comment') {
1456
    // Restrict access by comment ownership.
1457
    $query = db_select('comment', 'c');
1458
    $query->leftJoin('node', 'n', 'c.nid = n.nid');
1459
    $query
1460
      ->fields('c', array('cid', 'nid', 'uid'))
1461
      ->condition('c.cid', $entity_ids, 'IN');
1462
    $query->addField('c', 'uid', 'comment_uid');
1463
    $result = $query->execute();
1464
1465
    foreach ($result as $row) {
1466
      if ($flag->access_author == 'node_own') {
1467
        $access[$row->cid] = $row->node_uid != $account->uid ? FALSE : NULL;
1468
      }
1469
      elseif ($flag->access_author == 'node_others') {
1470
        $access[$row->cid] = $row->node_uid == $account->uid ? FALSE : NULL;
1471
      }
1472
      elseif ($flag->access_author == 'comment_own') {
1473
        $access[$row->cid] = $row->comment_uid != $account->uid ? FALSE : NULL;
1474
      }
1475
      elseif ($flag->access_author == 'comment_others') {
1476
        $access[$row->cid] = $row->comment_uid == $account->uid ? FALSE : NULL;
1477
      }
1478
    }
1479
  }
1480
1481
  // Always return an array (even if empty) of accesses.
1482
  return $access;
1483
}
1484
1485
/**
1486
 * Implements hook_theme().
1487
 */
1488
function flag_theme() {
1489
  $path = drupal_get_path('module', 'flag') . '/theme';
1490
1491
  return array(
1492
    'flag' => array(
1493
      'variables' => array(
1494
        'flag' => NULL,
1495
        'action' => NULL,
1496
        'entity_id' => NULL,
1497
        'after_flagging' => FALSE,
1498
        'needs_wrapping_element' => FALSE,
1499
        'errors' => array(),
1500
      ),
1501
      'template' => 'flag',
1502
      'pattern' => 'flag__',
1503
      'path' => $path,
1504
    ),
1505
    'flag_tokens_browser' => array(
1506
      'variables' => array(
1507
        'types' => array('all'),
1508
        'global_types' => TRUE,
1509
      ),
1510
      'file' => 'flag.tokens.inc',
1511
    ),
1512
    'flag_admin_listing' => array(
1513
      'render element' => 'form',
1514
      'file' => 'includes/flag.admin.inc',
1515
    ),
1516
    'flag_admin_listing_disabled' => array(
1517
      'variables' => array(
1518
        'flags' => NULL,
1519
        'default_flags' => NULL,
1520
      ),
1521
      'file' => 'includes/flag.admin.inc',
1522
    ),
1523
    'flag_admin_page' => array(
1524
      'variables' => array(
1525
        'flags' => NULL,
1526
        'default_flags' => NULL,
1527
        'flag_admin_listing' => NULL,
1528
      ),
1529
      'file' => 'includes/flag.admin.inc',
1530
    ),
1531
    'flag_form_roles' => array(
1532
      'render element' => 'element',
1533
      'file' => 'includes/flag.admin.inc',
1534
    ),
1535
  );
1536
}
1537
1538
/**
1539
 * A preprocess function for our theme('flag'). It generates the
1540
 * variables needed there.
1541
 *
1542
 * The $variables array initially contains the following arguments:
1543
 * - $flag
1544
 * - $action
1545
 * - $entity_id
1546
 * - $after_flagging
1547
 * - $errors
1548
 * - $needs_wrapping_element
1549
 *
1550
 * See 'flag.tpl.php' for their documentation.
1551
 */
1552
function template_preprocess_flag(&$variables) {
1553
  global $user;
1554
  $initialized = &drupal_static(__FUNCTION__, array());
1555
1556
  // Some typing shotcuts:
1557
  $flag =& $variables['flag'];
1558
  $action = $variables['action'];
1559
  $entity_id = $variables['entity_id'];
1560
  $errors = join('<br />', $variables['errors']);
1561
  $flag_css_name = str_replace('_', '-', $flag->name);
1562
1563
  // Generate the link URL.
1564
  $link_type = $flag->get_link_type();
1565
  $link = module_invoke($link_type['module'], 'flag_link', $flag, $action, $entity_id);
1566
  if (isset($link['title']) && empty($link['html'])) {
1567
    $link['title'] = check_plain($link['title']);
1568
  }
1569
1570
  // Replace the link with the access denied text if unable to flag.
1571
  if ($action == 'unflag' && !$flag->access($entity_id, 'unflag')) {
1572
    $link['title'] = $flag->get_label('unflag_denied_text', $entity_id);
1573
    unset($link['href']);
1574
  }
1575
1576
  // Anonymous users always need the JavaScript to maintain their flag state.
1577
  if ($user->uid == 0) {
1578
    $link_type['uses standard js'] = TRUE;
1579
  }
1580
1581
  // Load the JavaScript/CSS, if the link type requires it.
1582
  if (!isset($initialized[$link_type['name']])) {
1583
    if ($link_type['uses standard css']) {
1584
      drupal_add_css(drupal_get_path('module', 'flag') . '/theme/flag.css');
1585
    }
1586
    if ($link_type['uses standard js']) {
1587
      drupal_add_js(drupal_get_path('module', 'flag') . '/theme/flag.js');
1588
    }
1589
    $initialized[$link_type['name']] = TRUE;
1590
  }
1591
1592
  $variables['link'] = $link;
1593
  $variables['link_href'] = isset($link['href']) ? check_url(url($link['href'], $link)) : FALSE;
1594
  $variables['link_text'] = isset($link['title']) ? $link['title'] : $flag->get_label($action . '_short', $entity_id);
1595
  $variables['link_title'] = isset($link['attributes']['title']) ? check_plain($link['attributes']['title']) : check_plain(strip_tags($flag->get_label($action . '_long', $entity_id)));
1596
  $variables['status'] = ($action == 'flag' ? 'unflagged' : 'flagged');
1597
  $variables['flag_name_css'] = $flag_css_name;
1598
1599
  $variables['flag_wrapper_classes_array'] = array();
1600
  $variables['flag_wrapper_classes_array'][] = 'flag-wrapper';
1601
  $variables['flag_wrapper_classes_array'][] = 'flag-' . $flag_css_name;
1602
  $variables['flag_wrapper_classes_array'][] = 'flag-' . $flag_css_name . '-' . $entity_id;
1603
1604
  $variables['flag_classes_array'] = array();
1605
  $variables['flag_classes_array'][] = 'flag';
1606
  if (isset($link['href'])) {
1607
    $variables['flag_classes_array'][] = $variables['action'] . '-action';
1608
    $variables['flag_classes_array'][] = 'flag-link-' . $flag->link_type;
1609
  }
1610
  else {
1611
    $variables['flag_classes_array'][] = $variables['action'] . '-disabled';
1612
  }
1613
  if (isset($link['attributes']['class'])) {
1614
    $link['attributes']['class'] = is_string($link['attributes']['class']) ? array_filter(explode(' ', $link['attributes']['class'])) : $link['attributes']['class'];
1615
    $variables['flag_classes_array'] = array_merge($variables['flag_classes_array'], $link['attributes']['class']);
1616
  }
1617
  $variables['message_classes_array'] = array();
1618
  if ($variables['after_flagging']) {
1619
    $variables['message_classes_array'][] = 'flag-message';
1620
    if ($errors) {
1621
      $variables['message_classes_array'][] = 'flag-failure-message';
1622
      $variables['message_text'] = $errors;
1623
    }
1624
    else {
1625
      $inverse_action = ($action == 'flag' ? 'unflag' : 'flag');
1626
      $variables['message_classes_array'][] = 'flag-success-message';
1627
      $variables['message_classes_array'][] = 'flag-' . $variables['status'] . '-message';
1628
      $variables['message_text'] = $flag->get_label($inverse_action . '_message', $entity_id);
1629
      $variables['flag_classes_array'][] = $variables['status'];
1630
      // By default we make our JS code remove, after a few seconds, only success messages.
1631
      $variables['message_classes_array'][] = 'flag-auto-remove';
1632
    }
1633
  }
1634
  else {
1635
    $variables['message_text'] = '';
1636
  }
1637
}
1638
1639
/**
1640
 * Theme processor for flag.tpl.php.
1641
 *
1642
 * @param array &$variables
1643
 *  An array of variables for the template. See 'flag.tpl.php' for their
1644
 *  documentation.
1645
 */
1646
function template_process_flag(&$variables) {
1647
  // Convert class arrays to strings.
1648
  $variables['flag_wrapper_classes'] = implode(' ', $variables['flag_wrapper_classes_array']);
1649
  $variables['flag_classes'] = implode(' ', $variables['flag_classes_array']);
1650
  $variables['message_classes'] = implode(' ', $variables['message_classes_array']);
1651
}
1652
1653
/**
1654
 * Return an array of flag names keyed by fid.
1655
 */
1656
function _flag_get_flag_names() {
1657
  $flags = flag_get_flags();
1658
  $flag_names = array();
1659
  foreach ($flags as $flag) {
1660
    $flag_names[$flag->fid] = $flag->name;
1661
  }
1662
  return $flag_names;
1663
}
1664
1665
/**
1666
 * Return an array of flag link types suitable for a select list or radios.
1667
 */
1668
function _flag_link_type_options() {
1669
  $options = array();
1670
  $types = flag_get_link_types();
1671
  foreach ($types as $type_name => $type) {
1672
    $options[$type_name] = $type['title'];
1673
  }
1674
  return $options;
1675
}
1676
1677
/**
1678
 * Return an array of flag link type descriptions.
1679
 */
1680
function _flag_link_type_descriptions() {
1681
  $options = array();
1682
  $types = flag_get_link_types();
1683
  foreach ($types as $type_name => $type) {
1684
    $options[$type_name] = $type['description'];
1685
  }
1686
  return $options;
1687
}
1688
1689
// ---------------------------------------------------------------------------
1690
// Non-Views public API
1691
1692
/**
1693 018e218c Assos Assos
 * Get the count of flags for a particular entity type.
1694 85ad3d82 Assos Assos
 *
1695
 * @param $flag
1696
 *   The flag.
1697
 * @param $entity_type
1698 018e218c Assos Assos
 *   The entity type. For example, 'node'.
1699 85ad3d82 Assos Assos
 *
1700
 * @return
1701
 *   The flag count with the flag name and entity type as the array key.
1702
 */
1703
function flag_get_entity_flag_counts($flag, $entity_type) {
1704
  $counts = &drupal_static(__FUNCTION__);
1705
1706
  // We check to see if the flag count is already in the cache,
1707
  // if it's not, run the query.
1708
  if (!isset($counts[$flag->name][$entity_type])) {
1709
    $counts[$flag->name][$entity_type] = array();
1710
    $result = db_select('flagging', 'f')
1711
      ->fields('f', array('fid'))
1712
      ->condition('fid', $flag->fid)
1713
      ->condition('entity_type', $entity_type)
1714
      ->countQuery()
1715
      ->execute()
1716
      ->fetchField();
1717
    $counts[$flag->name][$entity_type] =  $result;
1718
  }
1719
1720
  return $counts[$flag->name][$entity_type];
1721
}
1722
1723
/**
1724
 * Get the user's flag count.
1725
 *
1726
 * @param $flag
1727
 *   The flag.
1728
 * @param $user
1729
 *   The user object.
1730
 *
1731
 * @return
1732
 *   The flag count with the flag name and the uid as the array key.
1733
 */
1734
function flag_get_user_flag_counts($flag, $user) {
1735
  $counts = &drupal_static(__FUNCTION__);
1736
1737
  // We check to see if the flag count is already in the cache,
1738
  // if it's not, run the query.
1739
  if (!isset($counts[$flag->name][$user->uid])) {
1740
    $counts[$flag->name][$user->uid] = array();
1741
    $result = db_select('flagging', 'f')
1742
      ->fields('f', array('fid'))
1743
      ->condition('fid', $flag->fid)
1744
      ->condition('uid', $user->uid)
1745
      ->countQuery()
1746
      ->execute()
1747
      ->fetchField();
1748
    $counts[$flag->name][$user->uid] = $result;
1749
  }
1750
1751
  return $counts[$flag->name][$user->uid];
1752
}
1753
1754
/**
1755
 * Get flag counts for all flags on a node.
1756
 *
1757
 * @param $entity_type
1758
 *   The entity type (usually 'node').
1759
 * @param $entity_id
1760
 *   The entity ID (usually the node ID).
1761
 *
1762
 * @return
1763
 *   The flag count with the entity type and id as array keys.
1764
 */
1765
function flag_get_counts($entity_type, $entity_id) {
1766
  $counts = &drupal_static(__FUNCTION__);
1767
1768
  if (!isset($counts[$entity_type][$entity_id])) {
1769
    $counts[$entity_type][$entity_id] = array();
1770
    $query = db_select('flag', 'f');
1771
    $query->leftJoin('flag_counts', 'fc', 'f.fid = fc.fid');
1772
    $result = $query
1773
      ->fields('f', array('name'))
1774
      ->fields('fc', array('count'))
1775
      ->condition('fc.entity_type', $entity_type)
1776
      ->condition('fc.entity_id', $entity_id)
1777
      ->execute();
1778
    foreach ($result as $row) {
1779
      $counts[$entity_type][$entity_id][$row->name] = $row->count;
1780
    }
1781
  }
1782
1783
  return $counts[$entity_type][$entity_id];
1784
}
1785
1786
/**
1787
 * Get the total count of items flagged within a flag.
1788
 *
1789
 * @param $flag_name
1790
 *   The flag name for which to retrieve a flag count.
1791
 * @param $reset
1792
 *   (optional) Reset the internal cache and execute the SQL query another time.
1793
 */
1794
function flag_get_flag_counts($flag_name, $reset = FALSE) {
1795
  $counts = &drupal_static(__FUNCTION__);
1796
1797
  if ($reset) {
1798
    $counts = array();
1799
  }
1800
  if (!isset($counts[$flag_name])) {
1801
    $flag = flag_get_flag($flag_name);
1802
    $counts[$flag_name] = db_select('flag_counts', 'fc')
1803
      ->fields('fc', array('flagging_id'))
1804
      ->condition('fid', $flag->fid)
1805
      ->countQuery()
1806
      ->execute()
1807
      ->fetchField();
1808
  }
1809
1810
  return $counts[$flag_name];
1811
}
1812
1813
/**
1814
 * Load a single flag either by name or by flag ID.
1815
 *
1816
 * @param $name
1817
 *  (optional) The flag name.
1818
 * @param $fid
1819
 *  (optional) The the flag id.
1820
 *
1821
 * @return
1822
 *  The flag object, or FALSE if no matching flag was found.
1823
 */
1824
function flag_get_flag($name = NULL, $fid = NULL) {
1825
  $flags = flag_get_flags();
1826
  if (isset($name)) {
1827
    if (isset($flags[$name])) {
1828
      return $flags[$name];
1829
    }
1830
  }
1831
  elseif (isset($fid)) {
1832
    foreach ($flags as $flag) {
1833
      if ($flag->fid == $fid) {
1834
        return $flag;
1835
      }
1836
    }
1837
  }
1838
  return FALSE;
1839
}
1840
1841
/**
1842
 * List all flags available.
1843
 *
1844
 * If node type or account are entered, a list of all possible flags will be
1845
 * returned.
1846
 *
1847
 * @param $entity_type
1848
 *   (optional) The type of entity for which to load the flags. Usually 'node'.
1849
 * @param $content_subtype
1850
 *   (optional) The node type for which to load the flags.
1851
 * @param $account
1852
 *   (optional) The user accont to filter available flags. If not set, all
1853
 *   flags for will this node will be returned.
1854
 *
1855
 * @return
1856
 *   An array of the structure [fid] = flag_object.
1857
 */
1858
function flag_get_flags($entity_type = NULL, $content_subtype = NULL, $account = NULL) {
1859
  $flags = &drupal_static(__FUNCTION__);
1860
1861
  // Retrieve a list of all flags, regardless of the parameters.
1862
  if (!isset($flags)) {
1863
    $flags = array();
1864
1865
    // Database flags.
1866
    $query = db_select('flag', 'f');
1867
    $query->leftJoin('flag_types', 'fn', 'fn.fid = f.fid');
1868
    $result = $query
1869
      ->fields('f', array('fid', 'entity_type', 'name', 'title', 'global', 'options'))
1870
      ->fields('fn', array('type'))
1871
      ->execute();
1872
    foreach ($result as $row) {
1873
      if (!isset($flags[$row->name])) {
1874
        $flags[$row->name] = flag_flag::factory_by_row($row);
1875
      }
1876
      else {
1877
        $flags[$row->name]->types[] = $row->type;
1878
      }
1879
    }
1880
1881
    // Add code-based flags provided by modules.
1882
    $default_flags = flag_get_default_flags();
1883
    foreach ($default_flags as $name => $default_flag) {
1884
      // Insert new enabled flags into the database to give them an FID.
1885
      if ($default_flag->status && !isset($flags[$name])) {
1886
        $default_flag->save();
1887
        $flags[$name] = $default_flag;
1888
      }
1889
1890
      if (isset($flags[$name])) {
1891
        // Ensure overridden flags are associated with their parent module.
1892
        $flags[$name]->module = $default_flag->module;
1893
1894
        // Update the flag with any properties that are "locked" by the code version.
1895
        if (isset($default_flag->locked)) {
1896
          $flags[$name]->locked = $default_flag->locked;
1897
          foreach ($default_flag->locked as $property) {
1898
            $flags[$name]->$property = $default_flag->$property;
1899
          }
1900
        }
1901
      }
1902
    }
1903
1904
    // Sort the list of flags by weight.
1905
    uasort($flags, '_flag_compare_weight');
1906
1907
    foreach ($flags as $flag) {
1908
      // Allow modules implementing hook_flag_alter(&$flag) to modify each flag.
1909
      drupal_alter('flag', $flag);
1910
    }
1911
  }
1912
1913
  // Make a variable copy to filter types and account.
1914
  $filtered_flags = $flags;
1915
1916
  // Filter out flags based on type and subtype.
1917
  if (isset($entity_type) || isset($content_subtype)) {
1918
    foreach ($filtered_flags as $name => $flag) {
1919
      if (!$flag->access_entity_enabled($entity_type, $content_subtype)) {
1920
        unset($filtered_flags[$name]);
1921
      }
1922
    }
1923
  }
1924
1925
  // Filter out flags based on account permissions.
1926
  if (isset($account) && $account->uid != 1) {
1927
    foreach ($filtered_flags as $name => $flag) {
1928
      // We test against the 'flag' action, which is the minimum permission to
1929
      // use a flag.
1930
      if (!$flag->user_access('flag', $account)) {
1931
        unset($filtered_flags[$name]);
1932
      }
1933
    }
1934
  }
1935
1936
  return $filtered_flags;
1937
}
1938
1939
/**
1940
 * Comparison function for uasort().
1941
 */
1942
function _flag_compare_weight($flag1, $flag2) {
1943
  if ($flag1->weight == $flag2->weight) {
1944
    return 0;
1945
  }
1946
  return $flag1->weight < $flag2->weight ? -1 : 1;
1947
}
1948
1949
/**
1950
 * Retrieve a list of flags defined by modules.
1951
 *
1952
 * @param $include_disabled
1953
 *   (optional) Unless specified, only enabled flags will be returned.
1954
 *
1955
 * @return
1956
 *   An array of flag prototypes, not usable for flagging. Use flag_get_flags()
1957
 *   if needing to perform a flagging with any enabled flag.
1958
 */
1959
function flag_get_default_flags($include_disabled = FALSE) {
1960
  $default_flags = array();
1961
  $flag_status = variable_get('flag_default_flag_status', array());
1962
1963
  foreach (module_implements('flag_default_flags') as $module) {
1964
    $function = $module . '_flag_default_flags';
1965
    foreach ($function() as $flag_name => $flag_info) {
1966
      // Backward compatibility: old exported default flags have their names
1967
      // in $flag_info instead, so we use the += operator to not overwrite it.
1968
      $flag_info += array(
1969
        'name' => $flag_name,
1970
        'module' => $module,
1971
      );
1972
      $flag = flag_flag::factory_by_array($flag_info);
1973
1974
      // Disable flags that are not at the current API version.
1975
      if (!$flag->is_compatible()) {
1976
        $flag->status = FALSE;
1977
      }
1978
1979
      // Add flags that have been enabled.
1980
      if ((!isset($flag_status[$flag->name]) && (!isset($flag->status) || $flag->status)) || !empty($flag_status[$flag->name])) {
1981
        $flag->status = TRUE;
1982
        $default_flags[$flag->name] = $flag;
1983
      }
1984
      // Add flags that have been disabled.
1985
      elseif ($include_disabled) {
1986
        $flag->status = FALSE;
1987
        $default_flags[$flag->name] = $flag;
1988
      }
1989
    }
1990
  }
1991
1992
  return $default_flags;
1993
}
1994
1995
/**
1996
 * Get all flagged entities in a flag.
1997
 *
1998
 * @param $flag_name
1999
 *   The flag name for which to retrieve flagged entites.
2000
 *
2001
 * @return
2002
 *   An array of flagging data, keyed by the flagging ID.
2003
 */
2004
function flag_get_flag_flagging_data($flag_name) {
2005
  $return = array();
2006
  $flag = flag_get_flag($flag_name);
2007
  $result = db_select('flagging', 'fc')
2008
    ->fields('fc')
2009
    ->condition('fid', $flag->fid)
2010
    ->execute();
2011
  return $result->fetchAllAssoc('flagging_id');
2012
}
2013
2014
/**
2015
 * Find what a user has flagged, either a single entity or on the entire site.
2016
 *
2017
 * @param $entity_type
2018
 *   The type of entity that will be retrieved. Usually 'node'.
2019
 * @param $entity_id
2020
 *   (optional) The entity ID to check for flagging. If none given, all
2021
 *   entities flagged by this user will be returned.
2022
 * @param $uid
2023
 *   (optional) The user ID whose flags we're checking. If none given, the
2024
 *   current user will be used.
2025
 * @param $sid
2026
 *   (optional) The user SID (provided by Session API) whose flags we're
2027
 *   checking. If none given, the current user will be used. The SID is 0 for
2028
 *   logged in users.
2029
 *
2030
 * @return
2031
 *   If returning a single item's flags (that is, when $entity_id isn't NULL),
2032
 *   an array of the structure
2033
 *   [flag_name] => (flagging_id => [flagging_id], uid => [uid], entity_id => [entity_id], timestamp => [timestamp], ...)
2034
 *
2035
 *   If returning all items' flags, an array of arrays for each flag:
2036
 *   [flag_name] => [entity_id] => Object from above.
2037
 *
2038
 */
2039
function flag_get_user_flags($entity_type, $entity_id = NULL, $uid = NULL, $sid = NULL) {
2040
  $flagged_content = &drupal_static(__FUNCTION__);
2041
2042
  $uid = !isset($uid) ? $GLOBALS['user']->uid : $uid;
2043
  $sid = !isset($sid) ? flag_get_sid($uid) : $sid;
2044
2045
  if (isset($entity_id)) {
2046
    if (!isset($flagged_content[$uid][$sid][$entity_type][$entity_id])) {
2047
      $flag_names = _flag_get_flag_names();
2048
      $flagged_content[$uid][$sid][$entity_type][$entity_id] = array();
2049
      $result = db_select('flagging', 'fc')
2050
        ->fields('fc')
2051
        ->condition('entity_type', $entity_type)
2052
        ->condition('entity_id', $entity_id)
2053
        ->condition(db_or()
2054
          ->condition('uid', $uid)
2055
          ->condition('uid', 0)
2056
        )
2057
        ->condition('sid', $sid)
2058
        ->execute();
2059
2060
      foreach ($result as $flagging_data) {
2061
        $flagged_content[$uid][$sid][$entity_type][$entity_id][$flag_names[$flagging_data->fid]] = $flagging_data;
2062
      }
2063
    }
2064
    return $flagged_content[$uid][$sid][$entity_type][$entity_id];
2065
  }
2066
2067
  else {
2068
    if (!isset($flagged_content[$uid][$sid][$entity_type]['all'])) {
2069
      $flag_names = _flag_get_flag_names();
2070
      $flagged_content[$uid][$sid][$entity_type]['all'] = array();
2071
      $result = db_select('flagging', 'fc')
2072
        ->fields('fc')
2073
        ->condition('entity_type', $entity_type)
2074
        ->condition(db_or()
2075
          ->condition('uid', $uid)
2076
          ->condition('uid', 0)
2077
        )
2078
        ->condition('sid', $sid)
2079
        ->execute();
2080
      foreach ($result as $flagging_data) {
2081
        $flagged_content[$uid][$sid][$entity_type]['all'][$flag_names[$flagging_data->fid]][$flagging_data->entity_id] = $flagging_data;
2082
      }
2083
    }
2084
    return $flagged_content[$uid][$sid][$entity_type]['all'];
2085
  }
2086
2087
}
2088
2089
/**
2090
 * Return a list of users who have flagged an entity.
2091
 *
2092
 * @param $entity_type
2093
 *   The type of entity that will be retrieved. Usually 'node'.
2094
 * @param $entity_id
2095
 *   The entity ID to check for flagging.
2096
 * @param $flag_name
2097
 *   (optional) The name of a flag if wanting a list specific to a single flag.
2098
 *
2099
 * @return
2100
 *   If no flag name is given, an array of flagging data, keyed by the user
2101
 *   ID that flagged the entity. Each flagging array is structured as
2102
 *   an array of flag information for each flag, keyed by the flag name. If
2103
 *   a flag name is specified, only the information for that flag is returned.
2104
 *   If no flags were found an empty array is returned.
2105
 */
2106
function flag_get_entity_flags($entity_type, $entity_id, $flag_name = NULL) {
2107
  $entity_flags = &drupal_static(__FUNCTION__, array());
2108
2109
  if (!isset($entity_flags[$entity_type][$entity_id])) {
2110
    $flag_names = _flag_get_flag_names();
2111
    $result = db_select('flagging', 'fc')
2112
      ->fields('fc')
2113
      ->condition('entity_type', $entity_type)
2114
      ->condition('entity_id', $entity_id)
2115
      ->orderBy('timestamp', 'DESC')
2116
      ->execute();
2117
    $entity_flags[$entity_type][$entity_id] = array();
2118
    foreach ($result as $flagging_data) {
2119
      // Build a list of flaggings for all flags by user.
2120
      $entity_flags[$entity_type][$entity_id]['users'][$flagging_data->uid][$flag_names[$flagging_data->fid]] = $flagging_data;
2121
      // Build a list of flaggings for each individual flag.
2122
      $entity_flags[$entity_type][$entity_id]['flags'][$flag_names[$flagging_data->fid]][$flagging_data->uid] = $flagging_data;
2123
    }
2124
  }
2125
  if (empty($entity_flags[$entity_type][$entity_id])) {
2126
    return array();
2127
  }
2128
  if (isset($flag_name)) {
2129
    if (isset($entity_flags[$entity_type][$entity_id]['flags'][$flag_name])) {
2130
      return $entity_flags[$entity_type][$entity_id]['flags'][$flag_name];
2131
    }
2132
    return array();
2133
  }
2134
  return $entity_flags[$entity_type][$entity_id]['users'];
2135
}
2136
2137
/**
2138
 * A utility function for outputting a flag link.
2139
 *
2140
 * You should call this function from your template when you want to put the
2141
 * link on the page yourself. For example, you could call this function from
2142
 * your theme preprocessor for node.tpl.php:
2143
 * @code
2144
 * $variables['my_flag_link'] = flag_create_link('bookmarks', $node->nid);
2145
 * @endcode
2146
 *
2147
 * @param $flag_name
2148
 *   The "machine readable" name of the flag; e.g. 'bookmarks'.
2149
 * @param $entity_id
2150
 *   The entity ID to check for flagging, for example a node ID.
2151 018e218c Assos Assos
 * @param $variables
2152
 *  An array of further variables to pass to theme('flag'). For the full list
2153
 *  of parameters, see flag.tpl.php. Of particular interest:
2154
 *  - after_flagging: Set to TRUE if this flag link is being displayed as the
2155
 *    result of a flagging action.
2156
 *  - errors: An array of error messages.
2157 85ad3d82 Assos Assos
 *
2158
 * @return
2159
 *   The HTML for the themed flag link.
2160
 */
2161 018e218c Assos Assos
function flag_create_link($flag_name, $entity_id, $variables = array()) {
2162 85ad3d82 Assos Assos
  $flag = flag_get_flag($flag_name);
2163
  if (!$flag) {
2164
    // Flag does not exist.
2165
    return;
2166
  }
2167
  if (!$flag->access($entity_id) && (!$flag->is_flagged($entity_id) || !$flag->access($entity_id, 'flag'))) {
2168
    // User has no permission to use this flag.
2169
    return;
2170
  }
2171 018e218c Assos Assos
  return $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id, $variables);
2172 85ad3d82 Assos Assos
}
2173
2174
/**
2175
 * Trim a flag to a certain size.
2176
 *
2177
 * @param $fid
2178
 *   The flag object.
2179
 * @param $account
2180
 *   The user object on behalf the trimming will occur.
2181
 * @param $cutoff_size
2182
 *   The number of flaggings allowed. Any flaggings beyond that will be trimmed.
2183
 * @param $trim_newest
2184
 *   An optional boolean indicating whether to trim the newest flags.
2185
 * @param $permissions_check
2186
 *   (optional) A boolean indicating whether to skip permissions.
2187
 *   This will trim the flag if $permissions_check is TRUE even if the user
2188
 *   doesn't have the permission to flag/unflag.
2189
 */
2190
function flag_trim_flag($flag, $account, $cutoff_size, $trim_newest, $permissions_check = FALSE) {
2191
  $query = db_select('flagging', 'fc')
2192
    ->fields('fc')
2193
    ->condition('fid', $flag->fid)
2194
    ->condition(db_or()->condition('uid', $account->uid)->condition('uid', 0))
2195
    // Account for session ID (in the case of anonymous users).
2196
    ->condition('sid', flag_get_sid($account->uid));
2197
  // If $trim_newest is TRUE, then, we should order by 'ASC' as we should trim
2198
  // the newest flags.
2199
  if ($trim_newest) {
2200
    $query->orderBy('timestamp', 'ASC');
2201
  }
2202
  else {
2203
    $query->orderBy('timestamp', 'DESC') ;
2204
  }
2205
2206
  // Execute the query
2207
  $result = $query->execute();
2208
2209
  $i = 1;
2210
  foreach ($result as $row) {
2211
    if ($i++ > $cutoff_size) {
2212
      flag('unflag', $flag->name, $row->entity_id, $account, $permissions_check);
2213
    }
2214
  }
2215
}
2216
2217
/**
2218
 * Remove all flagged entities from a flag.
2219
 *
2220
 * @param $flag
2221
 *   The flag object.
2222
 * @param $entity_id
2223
 *   (optional) The entity ID on which all flaggings will be removed. If left
2224
 *   empty, this will remove all of this flag's entities.
2225
 */
2226
function flag_reset_flag($flag, $entity_id = NULL) {
2227
  $query = db_select('flagging', 'fc')
2228
    ->fields('fc')
2229
    ->condition('fid', $flag->fid);
2230
2231
  if ($entity_id) {
2232
    $query->condition('entity_id', $entity_id);
2233
  }
2234
2235
  $result = $query->execute()->fetchAllAssoc('flagging_id', PDO::FETCH_ASSOC);
2236
  $rows = array();
2237
  foreach ($result as $row) {
2238
    $rows[] = $row;
2239
  }
2240
  module_invoke_all('flag_reset', $flag, $entity_id, $rows);
2241
2242
  $query = db_delete('flagging')->condition('fid' , $flag->fid);
2243
  // Update the flag_counts table.
2244
  $count_query = db_delete('flag_counts')->condition('fid', $flag->fid);
2245
  if ($entity_id) {
2246
    $query->condition('entity_id', $entity_id);
2247
    $count_query->condition('entity_id', $entity_id);
2248
  }
2249
  $count_query->execute();
2250
  return $query->execute();
2251
}
2252
2253
/**
2254
 * Return an array of link types provided by modules.
2255
 *
2256
 * @return
2257
 *  An array of link types as defined by hook_flag_link_type_info(). These are
2258
 *  keyed by the type name, and each value is an array of properties. In
2259
 *  addition to those defined in hook_flag_link_type_info(), the following
2260
 *  properties are set:
2261
 *  - 'module': The providing module.
2262
 *  - 'name': The machine name of the type.
2263
 *
2264
 * @see hook_flag_link_type_info()
2265
 * @see hook_flag_link_type_info_alter()
2266
 */
2267
function flag_get_link_types() {
2268
  $link_types = &drupal_static(__FUNCTION__);
2269
2270
  if (!isset($link_types)) {
2271
    if ($cache = cache_get('flag_link_type_info')) {
2272
      $link_types = $cache->data;
2273
    }
2274
    // In some rare edge cases cache_get() can return an empty result. If it
2275
    // does, we make sure to fetch the link types again.
2276
    if (empty($link_types)) {
2277
      $link_types = array();
2278
      foreach (module_implements('flag_link_type_info') as $module) {
2279
        $module_types = module_invoke($module, 'flag_link_type_info');
2280
        foreach ($module_types as $type_name => $info) {
2281
          $link_types[$type_name] = $info + array(
2282
            'module' => $module,
2283
            'name' => $type_name,
2284
            'title' => '',
2285
            'description' => '',
2286
            'options' => array(),
2287
            'uses standard js' => TRUE,
2288
            'uses standard css' => TRUE,
2289
            'provides form' => FALSE,
2290
          );
2291
        }
2292
      }
2293
      drupal_alter('flag_link_type_info', $link_types);
2294
2295
      cache_set('flag_link_type_info', $link_types);
2296
    }
2297
  }
2298
2299
  return $link_types;
2300
}
2301
2302
/**
2303
 * Get a private token used to protect links from spoofing - CSRF.
2304
 */
2305
function flag_get_token($entity_id) {
2306
  // Anonymous users get a less secure token, since it must be the same for all
2307
  // anonymous users on the entire site to work with page caching.
2308
  return ($GLOBALS['user']->uid) ? drupal_get_token($entity_id) : md5(drupal_get_private_key() . $entity_id);
2309
}
2310
2311
/**
2312
 * Check to see if a token value matches the specified node.
2313
 */
2314
function flag_check_token($token, $entity_id) {
2315
  return flag_get_token($entity_id) == $token;
2316
}
2317
2318
/**
2319
 * Set the Session ID for a user. Utilizes the Session API module.
2320
 *
2321
 * Creates a Session ID for an anonymous user and returns it. It will always
2322
 * return 0 for registered users.
2323
 *
2324
 * @param int $uid
2325
 *   (optional) The user ID to create a session ID for. Defaults to the
2326
 *   current user.
2327
 * @param bool $create
2328
 *   (optional) Determines whether a session should be created if it doesn't
2329
 *   exist yet. Defaults to TRUE.
2330
 *
2331
 * @return
2332
 *   The session ID, if a session was created. If not, the return value is 0.
2333
 *
2334
 * @see flag_get_sid()
2335
 */
2336
function flag_set_sid($uid = NULL, $create = TRUE) {
2337
  $sids = &drupal_static(__FUNCTION__, array());
2338
2339
  if (!isset($uid)) {
2340
    $uid = $GLOBALS['user']->uid;
2341
  }
2342
2343
  // Set the sid if none has been set yet. If the caller specified to create an
2344
  // sid and we have an invalid one (-1), create it.
2345
  if (!isset($sids[$uid]) || ($sids[$uid] == -1 && $create)) {
2346
    if (module_exists('session_api') && session_api_available() && $uid == 0) {
2347
      // This returns one of the following:
2348
      // - -1. This indicates that no session exists and none was created.
2349
      // - A positive integer with the Session ID when it does exist.
2350
      $sids[$uid] = session_api_get_sid($create);
2351
    }
2352
    else {
2353
      $sids[$uid] = 0;
2354
    }
2355
  }
2356
2357
  // Keep the -1 case internal and let the outside world only distinguish two
2358
  // cases: (1) there is an SID; (2) there is no SID (-> 0).
2359
  return $sids[$uid] == -1 ? 0 : $sids[$uid];
2360
}
2361
2362
/**
2363
 * Get the Session ID for a user. Utilizes the Session API module.
2364
 *
2365
 * Gets the Session ID for an anonymous user. It will always return 0 for
2366
 * registered users.
2367
 *
2368
 * @param int $uid
2369
 *   (optional) The user ID to return the session ID for. Defaults to the
2370
 *   current user.
2371
 * @param bool $create
2372
 *   (optional) Determines whether a session should be created if it doesn't
2373
 *   exist yet. Defaults to FALSE.
2374
 *
2375
 * @return
2376
 *   The session ID, if the session exists. If not, the return value is 0.
2377
 *
2378
 * @see flag_set_sid()
2379
 */
2380
function flag_get_sid($uid = NULL, $create = FALSE) {
2381
  return flag_set_sid($uid, $create);
2382
}
2383
2384
// ---------------------------------------------------------------------------
2385
// Drupal Core operations
2386
2387
/**
2388
 * Implements hook_node_operations().
2389
 *
2390
 * Add additional options on the admin/build/node page.
2391
 */
2392
function flag_node_operations() {
2393
  global $user;
2394
2395
  $flags = flag_get_flags('node', NULL, $user);
2396
  $operations = array();
2397
2398
  foreach ($flags as $flag) {
2399
    $operations['flag_' . $flag->name] = array(
2400
      'label' => $flag->get_label('flag_short'),
2401
      'callback' => 'flag_nodes',
2402
      'callback arguments' => array('flag', $flag->name),
2403
      'behavior' => array(),
2404
    );
2405
    $operations['unflag_' . $flag->name] = array(
2406
      'label' => $flag->get_label('unflag_short'),
2407
      'callback' => 'flag_nodes',
2408
      'callback arguments' => array('unflag', $flag->name),
2409
      'behavior' => array(),
2410
    );
2411
  }
2412
  return $operations;
2413
}
2414
2415
/**
2416
 * Callback function for hook_node_operations().
2417
 */
2418
function flag_nodes($nodes, $action, $flag_name) {
2419
  $performed = FALSE;
2420
  foreach ($nodes as $nid) {
2421
    $performed |= flag($action, $flag_name, $nid);
2422
  }
2423
  if ($performed) {
2424
    drupal_set_message(t('The update has been performed.'));
2425
  }
2426
}
2427
2428
/**
2429
 * Implements hook_user_operations().
2430
 */
2431
function flag_user_operations() {
2432
  global $user;
2433
2434
  $flags = flag_get_flags('user', NULL, $user);
2435
  $operations = array();
2436
2437
  foreach ($flags as $flag) {
2438
    $operations['flag_' . $flag->name] = array(
2439
      'label' => $flag->get_label('flag_short'),
2440
      'callback' => 'flag_users',
2441
      'callback arguments' => array('flag', $flag->name),
2442
    );
2443
    $operations['unflag_' . $flag->name] = array(
2444
      'label' => $flag->get_label('unflag_short'),
2445
      'callback' => 'flag_users',
2446
      'callback arguments' => array('unflag', $flag->name),
2447
    );
2448
  }
2449
  return $operations;
2450
}
2451
/**
2452
 * Callback function for hook_user_operations().
2453
 */
2454
function flag_users($users, $action, $flag_name) {
2455
  foreach ($users as $uid) {
2456
    flag($action, $flag_name, $uid);
2457
  }
2458
}
2459
2460
// ---------------------------------------------------------------------------
2461
// Contrib integration hooks
2462
2463
/**
2464
 * Implements hook_views_api().
2465
 */
2466
function flag_views_api() {
2467
  return array(
2468
    'api' => 3.0,
2469
    'path' => drupal_get_path('module', 'flag') . '/includes/views',
2470
  );
2471
}
2472
2473
/**
2474
 * Implements hook_features_api().
2475
 */
2476
function flag_features_api() {
2477
  return array(
2478
    'flag' => array(
2479
      'name' => t('Flag'),
2480
      'feature_source' => TRUE,
2481
      'default_hook' => 'flag_default_flags',
2482
      'file' => drupal_get_path('module', 'flag') . '/includes/flag.features.inc',
2483
    ),
2484
  );
2485
}
2486
2487
/**
2488
 * Implements hook_ctools_plugin_directory().
2489
 */
2490
function flag_ctools_plugin_directory($module, $plugin) {
2491
  if ($module == 'ctools' && !empty($plugin)) {
2492
    return "plugins/$plugin";
2493
  }
2494
}
2495
2496
// ---------------------------------------------------------------------------
2497
// Entity Metadata callbacks
2498
2499
/**
2500
 * Getter callback that returns whether the given entity is flagged.
2501
 */
2502
function flag_properties_get_flagging_boolean($entity, array $options, $name, $entity_type, $property_info) {
2503
  list($entity_id,) = entity_extract_ids($entity_type, $entity);
2504
2505
  $flagging_data = flag_get_user_flags($entity_type, $entity_id);
2506
  return isset($flagging_data[$property_info['flag_name']]);
2507
}
2508
2509
/**
2510
 * Getter callback that returns entities the given user flagged.
2511
 */
2512
function flag_properties_get_flagged_entities($entity, array $options, $name, $entity_type, $property_info) {
2513
  // Need the entity type the flag applies to.
2514
  $flag_entity_type = $property_info['flag_entity_type'];
2515
2516
  $flagging_data = flag_get_user_flags($flag_entity_type, NULL, $entity->uid);
2517
2518
  $flag_name = $property_info['flag_name'];
2519
  if (isset($flagging_data[$flag_name])) {
2520
    return array_keys($flagging_data[$flag_name]);
2521
  }
2522
  return array();
2523
}
2524
2525
/**
2526
 * Getter callback that returns users who flagged the given entity.
2527
 */
2528
function flag_properties_get_flagging_users($entity, array $options, $name, $entity_type, $property_info) {
2529
  list($entity_id,) = entity_extract_ids($entity_type, $entity);
2530
2531
  $flagging_data = flag_get_entity_flags($entity_type, $entity_id, $property_info['flag_name']);
2532
2533
  return array_keys($flagging_data);
2534
}
2535
2536
/**
2537
 * Getter callback that returns the SID of the user that is being retrieved.
2538
 *
2539
 * Callback for hook_entity_property_info_alter().
2540
 *
2541
 * @param stdobj $entity
2542
 *  The entity object representing a user for which we are getting inforamtion for.
2543
 *
2544
 * @param array $options
2545
 *  Options reguarding the nature of the entity. Language, etc.
2546
 *
2547
 * @param string $name
2548
 *  The name of the property we are running this callback for.
2549
 *
2550
 * @param string $entity_type
2551
 *  The type that the stdobj $entity is supposed to be.
2552
 *
2553
 * @param $property_info
2554
 *  The ifnromatin that represents the property we are providing a result for.
2555
 *
2556
 * @return an integer representing the user's sid field from the session_api table
2557
 *
2558
 * @ingroup callbacks
2559
 */
2560
function flag_properties_get_user_sid($entity, array $options, $name, $entity_type, $property_info) {
2561
  $sid =  flag_get_sid($entity->uid, FALSE);
2562
  return $sid;
2563
}