Projet

Général

Profil

Paste
Télécharger (47,4 ko) Statistiques
| Branche: | Révision:

root / htmltest / sites / all / modules / flag / includes / flag / flag_flag.inc @ 018e218c

1
<?php
2

    
3
/**
4
 * @file
5
 *  Contains the flag_flag class.
6
 *  Flag type classes use an object oriented style inspired by that
7
 *  of Views 2.
8
 */
9

    
10
/**
11
 * This abstract class represents a flag, or, in Views 2 terminology, "a handler".
12
 *
13
 * This is the base class for all flag implementations. Notable derived
14
 * classes are flag_node and flag_comment.
15
 *
16
 * There are several ways to obtain a flag handler, operating at different
17
 * levels.
18
 *
19
 * To load an existing flag that's defined in database or code, use one of:
20
 * - flag_get_flag(), the main flag API function.
21
 * - flag_load(), the loader for hook_menu().
22
 * - flag_get_flags(), the main API function for loading all flags. This calls
23
 *   flag_get_default_flags() to get flags in code.
24
 *
25
 * The above all use factory methods to instantiate the object for the flag and
26
 * load in its settings from configuration. The factory methods are:
27
 * - flag_flag::factory_by_row(), creates a flag handler from a database row.
28
 *   This is used by all the API functions above.
29
 * - flag_flag::factory_by_array(), creates a flag handler from a configuration
30
 *   array. This is used by flag_get_default_flags() and the flag import form.
31
 * - flag_flag::factory_by_entity_type(), creates an empty flag handler for the
32
 *   given entity type. This is used when a new or dummy flag handler is required
33
 *   and there is no configuration yet.
34
 *
35
 * The factory methods in turn all call the low-level function
36
 * flag_create_handler(), which obtains the correct handler for the flag, or if
37
 * that can't be found, the special handler flag_broken. Finally, this calls
38
 * $flag->construct() on the new handler object.
39
 */
40
class flag_flag {
41

    
42
  /**
43
   * The database ID.
44
   *
45
   * NULL for flags that haven't been saved to the database yet.
46
   *
47
   * @var integer
48
   */
49
  var $fid = NULL;
50

    
51
  /**
52
   * The entity type this flag works with.
53
   *
54
   * @var string
55
   */
56
  var $entity_type = NULL;
57

    
58
  /**
59
   * The flag's "machine readable" name.
60
   *
61
   * @var string
62
   */
63
  var $name = '';
64

    
65
  /**
66
   * The human-readable title for this flag.
67
   *
68
   * @var string
69
   */
70
  var $title = '';
71

    
72
  /**
73
   * Whether this flag state should act as a single toggle to all users.
74
   *
75
   * @var bool
76
   */
77
  var $global = FALSE;
78

    
79
  /**
80
   * The sub-types, AKA bundles, this flag applies to.
81
   *
82
   * This may be an empty array to indicate all types apply.
83
   *
84
   * @var array
85
   */
86
  var $types = array();
87

    
88
  /**
89
   * The roles array. This can be populated by fetch_roles() when needed.
90
   */
91
  var $roles = array(
92
    'flag' => array(),
93
    'unflag' => array(),
94
  );
95

    
96
  /**
97
   * An associative array containing textual errors that may be created during validation.
98
   *
99
   * The array keys should reflect the type of error being set. At this time,
100
   * the only "special" behavior related to the array keys is that
101
   * drupal_access_denied() is called when the key is 'access-denied' and
102
   * javascript is disabled.
103
   *
104
   * @var array
105
   */
106
  var $errors = array();
107

    
108
  /**
109
   * Creates a flag from a database row. Returns it.
110
   *
111
   * This is static method.
112
   *
113
   * The reason this isn't a non-static instance method --like Views's init()--
114
   * is because the class to instantiate changes according to the 'entity_type'
115
   * database column. This design pattern is known as the "Single Table
116
   * Inheritance".
117
   */
118
  static function factory_by_row($row) {
119
    $flag = flag_create_handler($row->entity_type);
120

    
121
    // Lump all data unto the object...
122
    foreach ($row as $field => $value) {
123
      $flag->$field = $value;
124
    }
125
    // ...but skip the following two.
126
    unset($flag->options, $flag->type);
127

    
128
    // Populate the options with the defaults.
129
    $options = (array) unserialize($row->options);
130
    $options += $flag->options();
131

    
132
    // Make the unserialized options accessible as normal properties.
133
    foreach ($options as $option => $value) {
134
      $flag->$option = $value;
135
    }
136

    
137
    if (!empty($row->type)) {
138
      // The loop loading from the database should further populate this property.
139
      $flag->types[] = $row->type;
140
    }
141

    
142
    return $flag;
143
  }
144

    
145
  /**
146
   * Create a complete flag (except an FID) from an array definition.
147
   */
148
  static function factory_by_array($config) {
149
    // Allow for flags with a missing entity type.
150
    $config += array(
151
      'entity_type' => FALSE,
152
    );
153
    $flag = flag_create_handler($config['entity_type']);
154

    
155
    foreach ($config as $option => $value) {
156
      $flag->$option = $value;
157
    }
158

    
159
    if (isset($config['locked']) && is_array($config['locked'])) {
160
      $flag->locked = drupal_map_assoc($config['locked']);
161
    }
162

    
163
    return $flag;
164
  }
165

    
166
  /**
167
   * Another factory method. Returns a new, "empty" flag; e.g., one suitable for
168
   * the "Add new flag" page.
169
   */
170
  static function factory_by_entity_type($entity_type) {
171
    return flag_create_handler($entity_type);
172
  }
173

    
174
  /**
175
   * Declares the options this flag supports, and their default values.
176
   *
177
   * Derived classes should want to override this.
178
   */
179
  function options() {
180
    $options = array(
181
      // The text for the "flag this" link for this flag.
182
      'flag_short' => '',
183
      // The description of the "flag this" link.
184
      'flag_long' => '',
185
      // Message displayed after flagging an entity.
186
      'flag_message' => '',
187
      // Likewise but for unflagged.
188
      'unflag_short' => '',
189
      'unflag_long' => '',
190
      'unflag_message' => '',
191
      'unflag_denied_text' => '',
192
      // The link type used by the flag, as defined in hook_flag_link_type_info().
193
      'link_type' => 'toggle',
194
      'weight' => 0,
195
    );
196

    
197
    // Merge in options from the current link type.
198
    $link_type = $this->get_link_type();
199
    $options = array_merge($options, $link_type['options']);
200

    
201
    // Allow other modules to change the flag options.
202
    drupal_alter('flag_options', $options, $this);
203
    return $options;
204
  }
205
  /**
206
   * Provides a form for setting options.
207
   *
208
   * Derived classes should want to override this.
209
   */
210
  function options_form(&$form) {
211
  }
212

    
213
  /**
214
   * Default constructor. Loads the default options.
215
   */
216
  function construct() {
217
    $options = $this->options();
218
    foreach ($options as $option => $value) {
219
      $this->$option = $value;
220
    }
221
  }
222

    
223
  /**
224
   * Load this flag's role data from permissions.
225
   *
226
   * Loads an array of roles into the flag, where each key is an action ('flag'
227
   * and 'unflag'), and each value is a flat array of role ids which may perform
228
   * that action.
229
   *
230
   * This should only be used when a complete overview of a flag's permissions
231
   * is needed. Use $flag->access or $flag->user_access() instead.
232
   */
233
  function fetch_roles() {
234
    $actions = array('flag', 'unflag');
235
    foreach ($actions as $action) {
236
      // Build the permission string.
237
      $permission = "$action $this->name";
238
      // We want a flat array of rids rather than $rid => $role_name.
239
      $this->roles[$action] = array_keys(user_roles(FALSE, $permission));
240
    }
241
  }
242

    
243
  /**
244
   * Update the flag with settings entered in a form.
245
   */
246
  function form_input($form_values) {
247
    // Load the form fields indiscriminately unto the flag (we don't care about
248
    // stray FormAPI fields because we aren't touching unknown properties anyway.
249
    foreach ($form_values as $field => $value) {
250
      $this->$field = $value;
251
    }
252
    $this->types = array_values(array_filter($this->types));
253
    // Clear internal titles cache:
254
    $this->get_title(NULL, TRUE);
255
  }
256

    
257
  /**
258
   * Validates this flag's options.
259
   *
260
   * @return
261
   *   A list of errors encountered while validating this flag's options.
262
   */
263
  function validate() {
264
    // TODO: It might be nice if this used automatic method discovery rather
265
    // than hard-coding the list of validate functions.
266
    return array_merge_recursive(
267
      $this->validate_name(),
268
      $this->validate_access()
269
    );
270
  }
271

    
272
  /**
273
   * Validates that the current flag's name is valid.
274
   *
275
   * @return
276
   *   A list of errors encountered while validating this flag's name.
277
   */
278
  function validate_name() {
279
    $errors = array();
280

    
281
    // Ensure a safe machine name.
282
    if (!preg_match('/^[a-z_][a-z0-9_]*$/', $this->name)) {
283
      $errors['name'][] = array(
284
        'error' => 'flag_name_characters',
285
        'message' => t('The flag name may only contain lowercase letters, underscores, and numbers.'),
286
      );
287
    }
288
    // Ensure the machine name is unique.
289
    $flag = flag_get_flag($this->name);
290
    if (!empty($flag) && (!isset($this->fid) || $flag->fid != $this->fid)) {
291
      $errors['name'][] = array(
292
        'error' => 'flag_name_unique',
293
        'message' => t('Flag names must be unique. This flag name is already in use.'),
294
      );
295
    }
296

    
297
    return $errors;
298
  }
299

    
300
  /**
301
   * Validates that the current flag's access settings are valid.
302
   */
303
  function validate_access() {
304
    $errors = array();
305

    
306
    // Require an unflag access denied message a role is not allowed to unflag.
307
    if (empty($this->unflag_denied_text)) {
308
      foreach ($this->roles['flag'] as $key => $rid) {
309
        if ($rid && empty($this->roles['unflag'][$key])) {
310
          $errors['unflag_denied_text'][] = array(
311
            'error' => 'flag_denied_text_required',
312
            'message' => t('The "Unflag not allowed text" is required if any user roles are not allowed to unflag.'),
313
          );
314
          break;
315
        }
316
      }
317
    }
318

    
319
    // Do not allow unflag access without flag access.
320
    foreach ($this->roles['unflag'] as $key => $rid) {
321
      if ($rid && empty($this->roles['flag'][$key])) {
322
        $errors['roles'][] = array(
323
          'error' => 'flag_roles_unflag',
324
          'message' => t('Any user role that has the ability to unflag must also have the ability to flag.'),
325
        );
326
        break;
327
      }
328
    }
329

    
330
    return $errors;
331
  }
332

    
333
  /**
334
   * Fetches, possibly from some cache, an entity this flag works with.
335
   */
336
  function fetch_entity($entity_id, $object_to_remember = NULL) {
337
    static $cache = array();
338
    if (isset($object_to_remember)) {
339
      $cache[$entity_id] = $object_to_remember;
340
    }
341
    if (!array_key_exists($entity_id, $cache)) {
342
      $entity = $this->_load_entity($entity_id);
343
      $cache[$entity_id] = $entity ? $entity : NULL;
344
    }
345
    return $cache[$entity_id];
346
  }
347

    
348
  /**
349
   * Loads an entity this flag works with.
350
   * Derived classes must implement this.
351
   *
352
   * @abstract
353
   * @private
354
   * @static
355
   */
356
  function _load_entity($entity_id) {
357
    return NULL;
358
  }
359

    
360
  /**
361
   * Store an object in the flag handler's cache.
362
   *
363
   * This is needed because otherwise fetch_object() loads the object from the
364
   * database (by calling _load_entity()), whereas sometimes we want to fetch
365
   * an object that hasn't yet been saved to the database. Subsequent calls to
366
   * fetch_entity() return the remembered object.
367
   *
368
   * @param $entity_id
369
   *  The ID of the object to cache.
370
   * @param $object
371
   *  The object to cache.
372
   */
373
  function remember_entity($entity_id, $object) {
374
    $this->fetch_entity($entity_id, $object);
375
  }
376

    
377
  /**
378
   * @defgroup access Access control
379
   * @{
380
   */
381

    
382
  /**
383
   * Returns TRUE if the flag applies to the given entity.
384
   *
385
   * Derived classes must implement this.
386
   *
387
   * @abstract
388
   */
389
  function applies_to_entity($entity) {
390
    return FALSE;
391
  }
392

    
393
  /**
394
   * Returns TRUE if the flag applies to the entity with the given ID.
395
   *
396
   * This is a convenience method that simply loads the object and calls
397
   * applies_to_entity(). If you already have the object, don't call
398
   * this function: call applies_to_entity() directly.
399
   */
400
  function applies_to_entity_id($entity_id) {
401
    return $this->applies_to_entity($this->fetch_entity($entity_id));
402
  }
403

    
404
  /**
405
   * Provides permissions for this flag.
406
   *
407
   * @return
408
   *  An array of permissions for hook_permission().
409
   */
410
  function get_permissions() {
411
    return array(
412
      "flag $this->name" => array(
413
        'title' => t('Flag %flag_title', array(
414
          '%flag_title' => $this->title,
415
        )),
416
      ),
417
      "unflag $this->name" => array(
418
        'title' => t('Unflag %flag_title', array(
419
          '%flag_title' => $this->title,
420
        )),
421
      ),
422
    );
423
  }
424

    
425
  /**
426
   * Determines whether the user has the permission to use this flag.
427
   *
428
   * @param $action
429
   *   (optional) The action to test, either "flag" or "unflag". If none given,
430
   *   "flag" will be tested, which is the minimum permission to use a flag.
431
   * @param $account
432
   *   (optional) The user object. If none given, the current user will be used.
433
   *
434
   * @return
435
   *   Boolean TRUE if the user is allowed to flag/unflag. FALSE otherwise.
436
   *
437
   * @see flag_permission()
438
   */
439
  function user_access($action = 'flag', $account = NULL) {
440
    if (!isset($account)) {
441
      $account = $GLOBALS['user'];
442
    }
443

    
444
    // Anonymous user can't use this system unless Session API is installed.
445
    if ($account->uid == 0 && !module_exists('session_api')) {
446
      return FALSE;
447
    }
448

    
449
    $permission_string = "$action $this->name";
450
    return user_access($permission_string, $account);
451
  }
452

    
453
  /**
454
   * Determines whether the user may flag, or unflag, the given entity.
455
   *
456
   * This method typically should not be overridden by child classes. Instead
457
   * they should implement type_access(), which is called by this method.
458
   *
459
   * @param $entity_id
460
   *   The entity ID to flag/unflag.
461
   * @param $action
462
   *   The action to test. Either 'flag' or 'unflag'. Leave NULL to determine
463
   *   by flag status.
464
   * @param $account
465
   *   The user on whose behalf to test the flagging action. Leave NULL for the
466
   *   current user.
467
   *
468
   * @return
469
   *   Boolean TRUE if the user is allowed to flag/unflag the given entity.
470
   *   FALSE otherwise.
471
   */
472
  function access($entity_id, $action = NULL, $account = NULL) {
473
    if (!isset($account)) {
474
      $account = $GLOBALS['user'];
475
    }
476

    
477
    if (isset($entity_id) && !$this->applies_to_entity_id($entity_id)) {
478
      // Flag does not apply to this entity.
479
      return FALSE;
480
    }
481

    
482
    if (!isset($action)) {
483
      $uid = $account->uid;
484
      $sid = flag_get_sid($uid);
485
      $action = $this->is_flagged($entity_id, $uid, $sid) ? 'unflag' : 'flag';
486
    }
487

    
488
    // Base initial access on the user's basic permission to use this flag.
489
    $access = $this->user_access($action, $account);
490

    
491
    // Check for additional access rules provided by sub-classes.
492
    $child_access = $this->type_access($entity_id, $action, $account);
493
    if (isset($child_access)) {
494
      $access = $child_access;
495
    }
496

    
497
    // Allow modules to disallow (or allow) access to flagging.
498
    // We grant access to the flag if both of the following conditions are met:
499
    // - No modules say to deny access.
500
    // - At least one module says to grant access.
501
    // If no module specified either allow or deny, we fall back to the
502
    // default access check above.
503
    $module_access = module_invoke_all('flag_access', $this, $entity_id, $action, $account);
504
    if (in_array(FALSE, $module_access, TRUE)) {
505
      $access = FALSE;
506
    }
507
    elseif (in_array(TRUE, $module_access, TRUE)) {
508
      // WARNING: This allows modules to bypass the default access check!
509
      $access = TRUE;
510
    }
511

    
512
    return $access;
513
  }
514

    
515
  /**
516
   * Determine access to multiple objects.
517
   *
518
   * Similar to user_access() but works on multiple IDs at once. Called in the
519
   * pre_render() stage of the 'Flag links' field within Views to find out where
520
   * that link applies. The reason we do a separate DB query, and not lump this
521
   * test in the Views query, is to make 'many to one' tests possible without
522
   * interfering with the rows, and also to reduce the complexity of the code.
523
   *
524
   * This method typically should not be overridden by child classes. Instead
525
   * they should implement type_access_multiple(), which is called by this
526
   * method.
527
   *
528
   * @param $entity_ids
529
   *   The array of entity IDs to check. The keys are the entity IDs, the
530
   *   values are the actions to test: either 'flag' or 'unflag'.
531
   * @param $account
532
   *   (optional) The account for which the actions will be compared against.
533
   *   If left empty, the current user will be used.
534
   *
535
   * @return
536
   *   An array whose keys are the object IDs and values are booleans indicating
537
   *   access.
538
   *
539
   * @see hook_flag_access_multiple()
540
   */
541
  function access_multiple($entity_ids, $account = NULL) {
542
    $account = isset($account) ? $account : $GLOBALS['user'];
543
    $access = array();
544

    
545
    // First check basic user access for this action.
546
    foreach ($entity_ids as $entity_id => $action) {
547
      $access[$entity_id] = $this->user_access($entity_ids[$entity_id], $account);
548
    }
549

    
550
    // Check for additional access rules provided by sub-classes.
551
    $child_access = $this->type_access_multiple($entity_ids, $account);
552
    if (isset($child_access)) {
553
      foreach ($child_access as $entity_id => $entity_access) {
554
        if (isset($entity_access)) {
555
          $access[$entity_id] = $entity_access;
556
        }
557
      }
558
    }
559

    
560
    // Merge in module-defined access.
561
    foreach (module_implements('flag_access_multiple') as $module) {
562
      $module_access = module_invoke($module, 'flag_access_multiple', $this, $entity_ids, $account);
563
      foreach ($module_access as $entity_id => $entity_access) {
564
        if (isset($entity_access)) {
565
          $access[$entity_id] = $entity_access;
566
        }
567
      }
568
    }
569

    
570
    return $access;
571
  }
572

    
573
  /**
574
   * Implements access() implemented by each child class.
575
   *
576
   * @abstract
577
   *
578
   * @return
579
   *  FALSE if access should be denied, or NULL if there is no restriction to
580
   *  be made. This should NOT return TRUE.
581
   */
582
  function type_access($entity_id, $action, $account) {
583
    return NULL;
584
  }
585

    
586
  /**
587
   * Implements access_multiple() implemented by each child class.
588
   *
589
   * @abstract
590
   *
591
   * @return
592
   *  An array keyed by entity ids, whose values represent the access to the
593
   *  corresponding entity. The access value may be FALSE if access should be
594
   *  denied, or NULL (or not set) if there is no restriction to  be made. It
595
   *  should NOT be TRUE.
596
   */
597
  function type_access_multiple($entity_ids, $account) {
598
    return array();
599
  }
600

    
601
  /**
602
   * @} End of "defgroup access".
603
   */
604

    
605
  /**
606
   * Given an entity, returns its ID.
607
   * Derived classes must implement this.
608
   *
609
   * @abstract
610
   */
611
  function get_entity_id($entity) {
612
    return NULL;
613
  }
614

    
615
  /**
616
   * Utility function: Checks whether a flag applies to a certain type, and
617
   * possibly subtype, of entity.
618
   *
619
   * @param $entity_type
620
   *   The type of entity being checked, such as "node".
621
   * @param $content_subtype
622
   *   The subtype being checked. For entities this will be the bundle name (the
623
   *   node type in the case of nodes).
624
   *
625
   * @return
626
   *   TRUE if the flag is enabled for this type and subtype.
627
   */
628
  function access_entity_enabled($entity_type, $content_subtype = NULL) {
629
   $entity_type_matches = ($this->entity_type == $entity_type);
630
   $sub_type_matches = FALSE;
631
   if (!isset($content_subtype) || !count($this->types)) {
632
     // Subtype automatically matches if we're not asked about it,
633
     // or if the flag applies to all subtypes.
634
     $sub_type_matches = TRUE;
635
   }
636
   else {
637
     $sub_type_matches = in_array($content_subtype, $this->types);
638
   }
639
   return $entity_type_matches && $sub_type_matches;
640
 }
641

    
642
  /**
643
   * Determine whether the flag should show a flag link in entity links.
644
   *
645
   * Derived classes are likely to implement this.
646
   *
647
   * @param $view_mode
648
   *   The view mode of the entity being displayed.
649
   *
650
   * @return
651
   *   A boolean indicating whether the flag link is to be shown in entity
652
   *   links.
653
   */
654
  function shows_in_entity_links($view_mode) {
655
    return FALSE;
656
  }
657

    
658
  /**
659
   * Returns TRUE if this flag requires anonymous user cookies.
660
   */
661
  function uses_anonymous_cookies() {
662
    global $user;
663
    return $user->uid == 0 && variable_get('cache', 0);
664
  }
665

    
666
  /**
667
   * Flags, or unflags, an item.
668
   *
669
   * @param $action
670
   *   Either 'flag' or 'unflag'.
671
   * @param $entity_id
672
   *   The ID of the item to flag or unflag.
673
   * @param $account
674
   *   The user on whose behalf to flag. Leave empty for the current user.
675
   * @param $skip_permission_check
676
   *   Flag the item even if the $account user don't have permission to do so.
677
   * @param $flagging
678
   *   (optional) This method works in tandem with Drupal's Field subsystem.
679
   *   Pass in a flagging entity if you want operate on it as well.
680
   *
681
   * @return
682
   *   FALSE if some error occured (e.g., user has no permission, flag isn't
683
   *   applicable to the item, etc.), TRUE otherwise.
684
   */
685
  function flag($action, $entity_id, $account = NULL, $skip_permission_check = FALSE, $flagging = NULL) {
686
    // Get the user.
687
    if (!isset($account)) {
688
      $account = $GLOBALS['user'];
689
    }
690
    if (!$account) {
691
      return FALSE;
692
    }
693

    
694
    // Check access and applicability.
695
    if (!$skip_permission_check) {
696
      if (!$this->access($entity_id, $action, $account)) {
697
        $this->errors['access-denied'] = t('You are not allowed to flag, or unflag, this content.');
698
        // User has no permission to flag/unflag this object.
699
        return FALSE;
700
      }
701
    }
702
    else {
703
      // We are skipping permission checks. However, at a minimum we must make
704
      // sure the flag applies to this entity type:
705
      if (!$this->applies_to_entity_id($entity_id)) {
706
        $this->errors['entity-type'] = t('This flag does not apply to this entity type.');
707
        return FALSE;
708
      }
709
    }
710

    
711
    if (($this->errors = module_invoke_all('flag_validate', $action, $this, $entity_id, $account, $skip_permission_check, $flagging))) {
712
      return FALSE;
713
    }
714

    
715
    // Clear various caches; We don't want code running after us to report
716
    // wrong counts or false flaggings.
717
    drupal_static_reset('flag_get_counts');
718
    drupal_static_reset('flag_get_user_flags');
719
    drupal_static_reset('flag_get_entity_flags');
720

    
721
    // Find out which user id to use.
722
    $uid = $this->global ? 0 : $account->uid;
723

    
724
    // Find out which session id to use.
725
    if ($this->global) {
726
      $sid = 0;
727
    }
728
    else {
729
      $sid = flag_get_sid($uid, TRUE);
730
      // Anonymous users must always have a session id.
731
      if ($sid == 0 && $account->uid == 0) {
732
        $this->errors['session'] = t('Internal error: You are anonymous but you have no session ID.');
733
        return FALSE;
734
      }
735
    }
736

    
737
    // Set our uid and sid to the flagging object.
738
    if (isset($flagging)) {
739
      $flagging->uid = $uid;
740
      $flagging->sid = $sid;
741
    }
742

    
743
    // @todo: Discuss: Should we call field_attach_validate()? None of the
744
    // entities in core does this (fields entered through forms are already
745
    // validated).
746
    //
747
    // @todo: Discuss: Core wraps everything in a try { }, should we?
748

    
749
    // Perform the flagging or unflagging of this flag.
750
    $existing_flagging_id = $this->_is_flagged($entity_id, $uid, $sid);
751
    $flagged = (bool) $existing_flagging_id;
752
    if ($action == 'unflag') {
753
      if ($this->uses_anonymous_cookies()) {
754
        $this->_unflag_anonymous($entity_id);
755
      }
756
      if ($flagged) {
757
        if (!isset($flagging)) {
758
          $flagging = flagging_load($existing_flagging_id);
759
        }
760
        $transaction = db_transaction();
761
        try {
762
          // Note the order: We decrease the count first so hooks have accurate
763
          // data, then invoke hooks, then delete the flagging entity.
764
          $this->_decrease_count($entity_id);
765
          module_invoke_all('flag_unflag', $this, $entity_id, $account, $flagging);
766
          // Invoke Rules event.
767
          if (module_exists('rules')) {
768
            $event_name = 'flag_unflagged_' . $this->name;
769
            // We only support flags on entities.
770
            if (entity_get_info($this->entity_type)) {
771
              $variables = array(
772
                'flag' => $this,
773
                'flagged_' . $this->entity_type => $entity_id,
774
                'flagging_user' => $account,
775
                'flagging' => $flagging,
776
              );
777
              rules_invoke_event_by_args($event_name, $variables);
778
            }
779
          }
780
          $this->_delete_flagging($flagging);
781
          $this->_unflag($entity_id, $flagging->flagging_id);
782
        }
783
        catch (Exception $e) {
784
          $transaction->rollback();
785
          watchdog_exception('flag', $e);
786
          throw $e;
787
        }
788
      }
789
    }
790
    elseif ($action == 'flag') {
791
      // Invoke hook_entity_presave().
792
      module_invoke_all('entity_presave', $flagging, 'flagging');
793

    
794
      if ($this->uses_anonymous_cookies()) {
795
        $this->_flag_anonymous($entity_id);
796
      }
797
      if (!$flagged) {
798
        // The entity is unflagged. By definition there is no flagging entity,
799
        // but we may have been passed one in to save.
800
        if (!isset($flagging)) {
801
          // Construct a new flagging object if we don't have one.
802
          $flagging = $this->new_flagging($entity_id, $uid, $sid);
803
        }
804
        // Save the flagging entity (just our table).
805
        $flagging_id = $this->_flag($entity_id, $uid, $sid);
806
        // The _flag() method is a plain DB record writer, so it's a bit
807
        // antiquated. We have to explicitly get our new ID out.
808
        $flagging->flagging_id = $flagging_id;
809
        $this->_increase_count($entity_id);
810
        // We're writing out a flagging entity even when we aren't passed one
811
        // (e.g., when flagging via JavaScript toggle links); in this case
812
        // Field API will assign the fields their default values.
813
        $this->_insert_flagging($flagging);
814
        module_invoke_all('flag_flag', $this, $entity_id, $account, $flagging);
815
        // Invoke Rules event.
816
        if (module_exists('rules')) {
817
          $event_name = 'flag_flagged_' . $this->name;
818
          // We only support flags on entities.
819
          if (entity_get_info($this->entity_type)) {
820
            $variables = array(
821
              'flag' => $this,
822
              'flagged_' . $this->entity_type => $entity_id,
823
              'flagging_user' => $account,
824
              'flagging' => $this->get_flagging($entity_id, $account->uid),
825
            );
826
            rules_invoke_event_by_args($event_name, $variables);
827
          }
828
        }
829
      }
830
      else {
831
        // Nothing to do. Item is already flagged.
832
        //
833
        // Except in the case a $flagging object is passed in: in this case
834
        // we're, for example, arriving from an editing form and need to update
835
        // the entity.
836
        if ($flagging) {
837
          $this->_update_flagging($flagging);
838
        }
839
      }
840
    }
841

    
842
    return TRUE;
843
  }
844

    
845
  /**
846
   * The entity CRUD methods _{insert,update,delete}_flagging() are for private
847
   * use by the flag() method.
848
   *
849
   * The reason programmers should not call them directly is because a flagging
850
   * operation is also accompanied by some bookkeeping (calling hooks, updating
851
   * counters) or access control. These tasks are handled by the flag() method.
852
   */
853
  private function _insert_flagging($flagging) {
854
    field_attach_presave('flagging', $flagging);
855
    field_attach_insert('flagging', $flagging);
856
    // Invoke hook_entity_insert().
857
    module_invoke_all('entity_insert', $flagging, 'flagging');
858
  }
859
  private function _update_flagging($flagging) {
860
    field_attach_presave('flagging', $flagging);
861
    field_attach_update('flagging', $flagging);
862
    // Update the cache.
863
    entity_get_controller('flagging')->resetCache();
864
    // Invoke hook_entity_update().
865
    module_invoke_all('entity_update', $flagging, 'flagging');
866
  }
867
  private function _delete_flagging($flagging) {
868
    field_attach_delete('flagging', $flagging);
869
    // Remove from the cache.
870
    entity_get_controller('flagging')->resetCache();
871
    // Invoke hook_entity_delete().
872
    module_invoke_all('entity_delete', $flagging, 'flagging');
873
  }
874

    
875
  /**
876
   * Construct a new, empty flagging entity object.
877
   *
878
   * @param mixed $entity_id
879
   *   The unique identifier of the object being flagged.
880
   * @param int $uid
881
   *   (optional) The user id of the user doing the flagging.
882
   * @param mixed $sid
883
   *   (optional) The user SID (provided by Session API) who is doing the
884
   *   flagging. The SID is 0 for logged in users.
885
   *
886
   * @return stdClass
887
   *   The returned object has at least the 'flag_name' property set, which
888
   *   enables Field API to figure out the bundle, but it's your responsibility
889
   *   to eventually populate 'entity_id' and 'flagging_id'.
890
   */
891
  function new_flagging($entity_id = NULL, $uid = NULL, $sid = NULL) {
892
    return (object) array(
893
      'flagging_id' => NULL,
894
      'flag_name' => $this->name,
895
      'entity_id' => $entity_id,
896
      'uid' => $uid,
897
      'sid' => $sid,
898
    );
899
  }
900

    
901
  /**
902
   * Determines if a certain user has flagged this object.
903
   *
904
   * Thanks to using a cache, inquiring several different flags about the same
905
   * item results in only one SQL query.
906
   *
907
   * @param $uid
908
   *   (optional) The user ID whose flags we're checking. If none given, the
909
   *   current user will be used.
910
   *
911
   * @return
912
   *   TRUE if the object is flagged, FALSE otherwise.
913
   */
914
  function is_flagged($entity_id, $uid = NULL, $sid = NULL) {
915
    return (bool) $this->get_flagging_record($entity_id, $uid, $sid);
916
  }
917

    
918
  /**
919
   * Returns the flagging record.
920
   *
921
   * This method returns the "flagging record": the {flagging} record that
922
   * exists for each flagged item (for a certain user). If the item isn't
923
   * flagged, returns NULL. This method could be useful, for example, when you
924
   * want to find out the 'flagging_id' or 'timestamp' values.
925
   *
926
   * Thanks to using a cache, inquiring several different flags about the same
927
   * item results in only one SQL query.
928
   *
929
   * Parameters are the same as is_flagged()'s.
930
   */
931
  function get_flagging_record($entity_id, $uid = NULL, $sid = NULL) {
932
    $uid = $this->global ? 0 : (!isset($uid) ? $GLOBALS['user']->uid : $uid);
933
    $sid = $this->global ? 0 : (!isset($sid) ? flag_get_sid($uid) : $sid);
934

    
935
    // flag_get_user_flags() does caching.
936
    $user_flags = flag_get_user_flags($this->entity_type, $entity_id, $uid, $sid);
937
    return isset($user_flags[$this->name]) ? $user_flags[$this->name] : NULL;
938
  }
939

    
940
  /**
941
   * Similar to is_flagged() excepts it returns the flagging entity.
942
   */
943
  function get_flagging($entity_id, $uid = NULL, $sid = NULL) {
944
    if (($record = $this->get_flagging_record($entity_id, $uid, $sid))) {
945
      return flagging_load($record->flagging_id);
946
    }
947
  }
948

    
949
  /**
950
   * Determines if a certain user has flagged this object.
951
   *
952
   * You probably shouldn't call this raw private method: call the
953
   * is_flagged() method instead.
954
   *
955
   * This method is similar to is_flagged() except that it does direct SQL and
956
   * doesn't do caching. Use it when you want to not affect the cache, or to
957
   * bypass it.
958
   *
959
   * @return
960
   *   If the object is flagged, returns the value of the 'flagging_id' column.
961
   *   Else, returns FALSE.
962
   *
963
   * @private
964
   */
965
  function _is_flagged($entity_id, $uid, $sid) {
966
    return db_select('flagging', 'fc')
967
      ->fields('fc', array('flagging_id'))
968
      ->condition('fid', $this->fid)
969
      ->condition('uid', $uid)
970
      ->condition('sid', $sid)
971
      ->condition('entity_id', $entity_id)
972
      ->execute()
973
      ->fetchField();
974
  }
975

    
976
  /**
977
   * A low-level method to flag an object.
978
   *
979
   * You probably shouldn't call this raw private method: call the flag()
980
   * function instead.
981
   *
982
   * @return
983
   *   The 'flagging_id' column of the new {flagging} record.
984
   *
985
   * @private
986
   */
987
  function _flag($entity_id, $uid, $sid) {
988
    $flagging_id = db_insert('flagging')
989
      ->fields(array(
990
        'fid' => $this->fid,
991
        'entity_type' => $this->entity_type,
992
        'entity_id' => $entity_id,
993
        'uid' => $uid,
994
        'sid' => $sid,
995
        'timestamp' => REQUEST_TIME,
996
      ))
997
      ->execute();
998
    return $flagging_id;
999
  }
1000

    
1001
  /**
1002
   * A low-level method to unflag an object.
1003
   *
1004
   * You probably shouldn't call this raw private method: call the flag()
1005
   * function instead.
1006
   *
1007
   * @private
1008
   */
1009
  function _unflag($entity_id, $flagging_id) {
1010
    db_delete('flagging')->condition('flagging_id', $flagging_id)->execute();
1011
  }
1012

    
1013
  /**
1014
   * Increases the flag count for an object.
1015
   *
1016
   * @param $entity_id
1017
   *   For which item should the count be increased.
1018
   * @param $number
1019
   *   The amount of counts to increasing. Defaults to 1.
1020
   *
1021
   * @private
1022
  */
1023
  function _increase_count($entity_id, $number = 1) {
1024
    db_merge('flag_counts')
1025
      ->key(array(
1026
        'fid' => $this->fid,
1027
        'entity_id' => $entity_id,
1028
      ))
1029
      ->fields(array(
1030
        'entity_type' => $this->entity_type,
1031
        'count' => $number,
1032
        'last_updated' => REQUEST_TIME,
1033
      ))
1034
      ->updateFields(array(
1035
        'last_updated' => REQUEST_TIME,
1036
      ))
1037
      ->expression('count', 'count + :inc', array(':inc' => $number))
1038
      ->execute();
1039
  }
1040

    
1041
  /**
1042
   * Decreases the flag count for an object.
1043
   *
1044
   * @param $entity_id
1045
   *   For which item should the count be descreased.
1046
   * @param $number
1047
   *   The amount of counts to decrease. Defaults to 1.
1048
   *
1049
   * @private
1050
  */
1051
  function _decrease_count($entity_id, $number = 1) {
1052
    // Delete rows with count 0, for data consistency and space-saving.
1053
    // Done before the db_update() to prevent out-of-bounds errors on "count".
1054
    db_delete('flag_counts')
1055
      ->condition('fid', $this->fid)
1056
      ->condition('entity_id', $entity_id)
1057
      ->condition('count', $number, '<=')
1058
      ->execute();
1059

    
1060
    // Update the count with the new value otherwise.
1061
    db_update('flag_counts')
1062
      ->expression('count', 'count - :inc', array(':inc' => $number))
1063
      ->fields(array(
1064
        'last_updated' => REQUEST_TIME,
1065
      ))
1066
      ->condition('fid', $this->fid)
1067
      ->condition('entity_id', $entity_id)
1068
      ->execute();
1069
  }
1070

    
1071
  /**
1072
   * Set a cookie for anonymous users to record their flagging.
1073
   *
1074
   * @private
1075
   */
1076
  function _flag_anonymous($entity_id) {
1077
    $storage = FlagCookieStorage::factory($this);
1078
    $storage->flag($entity_id);
1079
  }
1080

    
1081
  /**
1082
   * Remove the cookie for anonymous users to record their unflagging.
1083
   *
1084
   * @private
1085
   */
1086
  function _unflag_anonymous($entity_id) {
1087
    $storage = FlagCookieStorage::factory($this);
1088
    $storage->unflag($entity_id);
1089
  }
1090

    
1091
  /**
1092
   * Returns the number of times an item is flagged.
1093
   *
1094
   * Thanks to using a cache, inquiring several different flags about the same
1095
   * item results in only one SQL query.
1096
   */
1097
  function get_count($entity_id) {
1098
    $counts = flag_get_counts($this->entity_type, $entity_id);
1099
    return isset($counts[$this->name]) ? $counts[$this->name] : 0;
1100
  }
1101

    
1102
  /**
1103
   * Returns the number of items a user has flagged.
1104
   *
1105
   * For global flags, pass '0' as the user ID and session ID.
1106
   */
1107
  function get_user_count($uid, $sid = NULL) {
1108
    if (!isset($sid)) {
1109
      $sid = flag_get_sid($uid);
1110
    }
1111
    return db_select('flagging', 'fc')->fields('fc', array('flagging_id'))
1112
      ->condition('fid', $this->fid)
1113
      ->condition('uid', $uid)
1114
      ->condition('sid', $sid)
1115
      ->countQuery()
1116
      ->execute()
1117
      ->fetchField();
1118
  }
1119

    
1120
  /**
1121
   * Processes a flag label for display. This means language translation and
1122
   * token replacements.
1123
   *
1124
   * You should always call this function and not get at the label directly.
1125
   * E.g., do `print $flag->get_label('title')` instead of `print
1126
   * $flag->title`.
1127
   *
1128
   * @param $label
1129
   *   The label to get, e.g. 'title', 'flag_short', 'unflag_short', etc.
1130
   * @param $entity_id
1131
   *   The ID in whose context to interpret tokens. If not given, only global
1132
   *   tokens will be substituted.
1133
   * @return
1134
   *   The processed label.
1135
   */
1136
  function get_label($label, $entity_id = NULL) {
1137
    if (!isset($this->$label)) {
1138
      return;
1139
    }
1140
    $label = t($this->$label);
1141
    if (strpos($label, '[') !== FALSE) {
1142
      $label = $this->replace_tokens($label, array(), array('sanitize' => FALSE), $entity_id);
1143
    }
1144
    return filter_xss_admin($label);
1145
  }
1146

    
1147
  /**
1148
   * Get the link type for this flag.
1149
   */
1150
  function get_link_type() {
1151
    $link_types = flag_get_link_types();
1152
    return (isset($this->link_type) && isset($link_types[$this->link_type])) ? $link_types[$this->link_type] : $link_types['normal'];
1153
  }
1154

    
1155
  /**
1156
   * Replaces tokens in a label. Only the 'global' token context is recognized
1157
   * by default, so derived classes should override this method to add all
1158
   * token contexts they understand.
1159
   */
1160
  function replace_tokens($label, $contexts, $options, $entity_id) {
1161
    if (strpos($label , 'flagging:') !== FALSE) {
1162
      if (($flagging = $this->get_flagging($entity_id))) {
1163
        $contexts['flagging'] = $flagging;
1164
      }
1165
    }
1166
    return token_replace($label, $contexts, $options);
1167
  }
1168

    
1169
  /**
1170
   * Returns the token types this flag understands in labels. These are used
1171
   * for narrowing down the token list shown in the help box to only the
1172
   * relevant ones.
1173
   *
1174
   * Derived classes should override this.
1175
   */
1176
  function get_labels_token_types() {
1177
    return array('flagging');
1178
  }
1179

    
1180
  /**
1181
   * A convenience method for getting the flag title.
1182
   *
1183
   * `$flag->get_title()` is shorthand for `$flag->get_label('title')`.
1184
   */
1185
  function get_title($entity_id = NULL, $reset = FALSE) {
1186
    static $titles = array();
1187
    if ($reset) {
1188
      $titles = array();
1189
    }
1190
    $slot = intval($entity_id); // Convert NULL to 0.
1191
    if (!isset($titles[$this->fid][$slot])) {
1192
      $titles[$this->fid][$slot] = $this->get_label('title', $entity_id);
1193
    }
1194
    return $titles[$this->fid][$slot];
1195
  }
1196

    
1197
  /**
1198
   * Returns a 'flag action' object. It exists only for the sake of its
1199
   * informative tokens. Currently, it's utilized only for the 'mail' action.
1200
   *
1201
   * Derived classes should populate the 'content_title' and 'content_url'
1202
   * slots.
1203
   */
1204
  function get_flag_action($entity_id) {
1205
    $flag_action = new stdClass();
1206
    $flag_action->flag = $this->name;
1207
    $flag_action->entity_type = $this->entity_type;
1208
    $flag_action->entity_id = $entity_id;
1209
    return $flag_action;
1210
  }
1211

    
1212
  /**
1213
   * Returns an array of errors set during validation.
1214
   */
1215
  function get_errors() {
1216
    return $this->errors;
1217
  }
1218

    
1219
  /**
1220
   * @addtogroup actions
1221
   * @{
1222
   * Methods that can be overridden to support Actions.
1223
   */
1224

    
1225
  /**
1226
   * Returns an array of all actions that are executable with this flag.
1227
   */
1228
  function get_valid_actions() {
1229
    $actions = module_invoke_all('action_info');
1230
    foreach ($actions as $callback => $action) {
1231
      if ($action['type'] != $this->entity_type && !in_array('any', $action['triggers'])) {
1232
        unset($actions[$callback]);
1233
      }
1234
    }
1235
    return $actions;
1236
  }
1237

    
1238
  /**
1239
   * Returns objects the action may possibly need. This method should return at
1240
   * least the 'primary' object the action operates on.
1241
   *
1242
   * This method is needed because get_valid_actions() returns actions that
1243
   * don't necessarily operate on an object of a type this flag manages. For
1244
   * example, flagging a comment may trigger an 'Unpublish post' action on a
1245
   * node; So the comment flag needs to tell the action about some node.
1246
   *
1247
   * Derived classes must implement this.
1248
   *
1249
   * @abstract
1250
   */
1251
  function get_relevant_action_objects($entity_id) {
1252
    return array();
1253
  }
1254

    
1255
  /**
1256
   * @} End of "addtogroup actions".
1257
   */
1258

    
1259
  /**
1260
   * @addtogroup views
1261
   * @{
1262
   * Methods that can be overridden to support the Views module.
1263
   */
1264

    
1265
  /**
1266
   * Returns information needed for Views integration. E.g., the Views table
1267
   * holding the flagged object, its primary key, and various labels. See
1268
   * derived classes for examples.
1269
   *
1270
   * @static
1271
   */
1272
  function get_views_info() {
1273
    return array();
1274
  }
1275

    
1276
  /**
1277
   * @} End of "addtogroup views".
1278
   */
1279

    
1280
  /**
1281
   * Saves a flag to the database. It is a wrapper around update() and insert().
1282
   */
1283
  function save() {
1284
    // Allow the 'global' property to be a boolean, particularly when defined in
1285
    // hook_flag_default_flags(). Without this, a value of FALSE gets casted to
1286
    // an empty string which violates our schema. Other boolean properties are
1287
    // fine, as they are serialized.
1288
    $this->global = (int) $this->global;
1289

    
1290
    if (isset($this->fid)) {
1291
      $this->update();
1292
      $this->is_new = FALSE;
1293
    }
1294
    else {
1295
      $this->insert();
1296
      $this->is_new = TRUE;
1297
    }
1298
    // Clear the page cache for anonymous users.
1299
    cache_clear_all('*', 'cache_page', TRUE);
1300
  }
1301

    
1302
  /**
1303
   * Saves an existing flag to the database. Better use save().
1304
   */
1305
  function update() {
1306
    db_update('flag')->fields(array(
1307
      'name' => $this->name,
1308
      'title' => $this->title,
1309
      'global' => $this->global,
1310
      'options' => $this->get_serialized_options()))
1311
      ->condition('fid', $this->fid)
1312
      ->execute();
1313
    db_delete('flag_types')->condition('fid', $this->fid)->execute();
1314
    foreach ($this->types as $type) {
1315
      db_insert('flag_types')->fields(array(
1316
        'fid' => $this->fid,
1317
        'type' => $type))
1318
        ->execute();
1319
    }
1320
  }
1321

    
1322
  /**
1323
   * Saves a new flag to the database. Better use save().
1324
   */
1325
  function insert() {
1326
    $this->fid = db_insert('flag')
1327
      ->fields(array(
1328
        'entity_type' => $this->entity_type,
1329
        'name' => $this->name,
1330
        'title' => $this->title,
1331
        'global' => $this->global,
1332
        'options' => $this->get_serialized_options(),
1333
      ))
1334
      ->execute();
1335
    foreach ($this->types as $type) {
1336
      db_insert('flag_types')
1337
        ->fields(array(
1338
          'fid' => $this->fid,
1339
          'type' => $type,
1340
        ))
1341
        ->execute();
1342
    }
1343
  }
1344

    
1345
  /**
1346
   * Options are stored serialized in the database.
1347
   */
1348
  function get_serialized_options() {
1349
    $option_names = array_keys($this->options());
1350
    $options = array();
1351
    foreach ($option_names as $option) {
1352
      $options[$option] = $this->$option;
1353
    }
1354
    return serialize($options);
1355
  }
1356

    
1357
  /**
1358
   * Deletes a flag from the database.
1359
   */
1360
  function delete() {
1361
    db_delete('flag')->condition('fid', $this->fid)->execute();
1362
    db_delete('flagging')->condition('fid', $this->fid)->execute();
1363
    db_delete('flag_types')->condition('fid', $this->fid)->execute();
1364
    db_delete('flag_counts')->condition('fid', $this->fid)->execute();
1365
    module_invoke_all('flag_delete', $this);
1366
  }
1367

    
1368
  /**
1369
   * Returns TRUE if this flag's declared API version is compatible with this
1370
   * module.
1371
   *
1372
   * An "incompatible" flag is one exported (and now being imported or exposed
1373
   * via hook_flag_default_flags()) by a different version of the Flag module.
1374
   * An incompatible flag should be treated as a "black box": it should not be
1375
   * saved or exported because our code may not know to handle its internal
1376
   * structure.
1377
   */
1378
  function is_compatible() {
1379
    if (isset($this->fid)) {
1380
      // Database flags are always compatible.
1381
      return TRUE;
1382
    }
1383
    else {
1384
      if (!isset($this->api_version)) {
1385
        $this->api_version = 1;
1386
      }
1387
      return $this->api_version == FLAG_API_VERSION;
1388
    }
1389
  }
1390

    
1391
  /**
1392
   * Finds the "default flag" corresponding to this flag.
1393
   *
1394
   * Flags defined in code ("default flags") can be overridden. This method
1395
   * returns the default flag that is being overridden by $this. Returns NULL
1396
   * if $this overrides no default flag.
1397
   */
1398
  function find_default_flag() {
1399
    if ($this->fid) {
1400
      $default_flags = flag_get_default_flags(TRUE);
1401
      if (isset($default_flags[$this->name])) {
1402
        return $default_flags[$this->name];
1403
      }
1404
    }
1405
  }
1406

    
1407
  /**
1408
   * Reverts an overriding flag to its default state.
1409
   *
1410
   * Note that $this isn't altered. To see the reverted flag you'll have to
1411
   * call flag_get_flag($this->name) again.
1412
   *
1413
   * @return
1414
   *   TRUE if the flag was reverted successfully; FALSE if there was an error;
1415
   *   NULL if this flag overrides no default flag.
1416
   */
1417
  function revert() {
1418
    if (($default_flag = $this->find_default_flag())) {
1419
      if ($default_flag->is_compatible()) {
1420
        $default_flag = clone $default_flag;
1421
        $default_flag->fid = $this->fid;
1422
        $default_flag->save();
1423
        drupal_static_reset('flag_get_flags');
1424
        return TRUE;
1425
      }
1426
      else {
1427
        return FALSE;
1428
      }
1429
    }
1430
  }
1431

    
1432
  /**
1433
   * Disable a flag provided by a module.
1434
   */
1435
  function disable() {
1436
    if (isset($this->module)) {
1437
      $flag_status = variable_get('flag_default_flag_status', array());
1438
      $flag_status[$this->name] = FALSE;
1439
      variable_set('flag_default_flag_status', $flag_status);
1440
    }
1441
  }
1442

    
1443
  /**
1444
   * Enable a flag provided by a module.
1445
   */
1446
  function enable() {
1447
    if (isset($this->module)) {
1448
      $flag_status = variable_get('flag_default_flag_status', array());
1449
      $flag_status[$this->name] = TRUE;
1450
      variable_set('flag_default_flag_status', $flag_status);
1451
    }
1452
  }
1453

    
1454
  /**
1455
   * Returns administrative menu path for carrying out some action.
1456
   */
1457
  function admin_path($action) {
1458
    if ($action == 'edit') {
1459
      // Since 'edit' is the default tab, we omit the action.
1460
      return FLAG_ADMIN_PATH . '/manage/' . $this->name;
1461
    }
1462
    else {
1463
      return FLAG_ADMIN_PATH . '/manage/' . $this->name . '/' . $action;
1464
    }
1465
  }
1466

    
1467
  /**
1468
   * Renders a flag/unflag link.
1469
   *
1470
   * This is a wrapper around theme('flag') that channels the call to the right
1471
   * template file.
1472
   *
1473
   * @param $action
1474
   *  The action the link is about to carry out, either "flag" or "unflag".
1475
   * @param $entity_id
1476
   *  The ID of the object to flag.
1477
   * @param $variables = array()
1478
   *  An array of further variables to pass to theme('flag'). For the full list
1479
   *  of parameters, see flag.tpl.php. Of particular interest:
1480
   *  - after_flagging: Set to TRUE if this flag link is being displayed as the result
1481
   *    of a flagging action.
1482
   *  - errors: An array of error messages.
1483
   *
1484
   * @return
1485
   *  The HTML for the flag link.
1486
   */
1487
  function theme($action, $entity_id, $variables = array()) {
1488
    static $js_added = array();
1489
    global $user;
1490

    
1491
    $after_flagging = !empty($variables['after_flagging']);
1492

    
1493
    // If the flagging user is anonymous, set a boolean for the benefit of
1494
    // JavaScript code. Currently, only our "anti-crawlers" mechanism uses it.
1495
    if ($user->uid == 0 && !isset($js_added['anonymous'])) {
1496
      $js_added['anonymous'] = TRUE;
1497
      drupal_add_js(array('flag' => array('anonymous' => TRUE)), 'setting');
1498
    }
1499

    
1500
    // If the flagging user is anonymous and the page cache is enabled, we
1501
    // update the links through JavaScript.
1502
    if ($this->uses_anonymous_cookies() && !$after_flagging) {
1503
      if ($this->global) {
1504
        // In case of global flags, the JavaScript template is to contain
1505
        // the opposite of the current state.
1506
        $js_action = ($action == 'flag' ? 'unflag' : 'flag');
1507
      }
1508
      else {
1509
        // In case of non-global flags, we always show the "flag!" link,
1510
        // and then replace it with the "unflag!" link through JavaScript.
1511
        $js_action = 'unflag';
1512
        $action = 'flag';
1513
      }
1514
      if (!isset($js_added[$this->name . '_' . $entity_id])) {
1515
        $js_added[$this->name . '_' . $entity_id] = TRUE;
1516
        $js_template = theme($this->theme_suggestions(), array(
1517
          'flag' => $this,
1518
          'action' => $js_action,
1519
          'entity_id' => $entity_id,
1520
          'after_flagging' => $after_flagging,
1521
        ));
1522
        drupal_add_js(array('flag' => array('templates' => array($this->name . '_' . $entity_id => $js_template))), 'setting');
1523
      }
1524
    }
1525

    
1526
    return theme($this->theme_suggestions(), array(
1527
      'flag' => $this,
1528
      'action' => $action,
1529
      'entity_id' => $entity_id,
1530
    ) + $variables);
1531
  }
1532

    
1533
  /**
1534
   * Provides an array of possible themes to try for a given flag.
1535
   */
1536
  function theme_suggestions() {
1537
    $suggestions = array();
1538
    $suggestions[] = 'flag__' . $this->name;
1539
    $suggestions[] = 'flag__' . $this->link_type;
1540
    $suggestions[] = 'flag';
1541
    return $suggestions;
1542
  }
1543

    
1544
  /**
1545
   * A shortcut function to output the link URL.
1546
   */
1547
  function _flag_url($path, $fragment = NULL, $absolute = TRUE) {
1548
    return url($path, array('fragment' => $fragment, 'absolute' => $absolute));
1549
  }
1550
}
1551

    
1552
/**
1553
 * A dummy flag to be used where the real implementation can't be found.
1554
 */
1555
class flag_broken extends flag_flag {
1556
  function options_form(&$form) {
1557
    drupal_set_message(t("The module providing this flag wasn't found, or this flag type, %type, isn't valid.", array('%type' => $this->entity_type)), 'error');
1558
    $form = array();
1559
  }
1560
}