Projet

Général

Profil

Paste
Télécharger (49,2 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / flag / includes / flag / flag_flag.inc @ b08d2851

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. This may be
680
   *   used either of the following cases:
681
   *   - to save field data on a new Flagging entity at the same time as
682
   *     flagging an entity. In this case, using Entity API's entity_create()
683
   *     is recommended, although the Flagging entity may also be created
684
   *     directly as a new stdClass object.
685
   *   - to update field data an existing flagging. The $action parameter should
686
   *     be set to 'flag'. The Flagging entity will need to be loaded first with
687
   *     flagging_load().
688
   *
689
   * @return
690
   *   FALSE if some error occured (e.g., user has no permission, flag isn't
691
   *   applicable to the item, etc.), TRUE otherwise.
692
   */
693
  function flag($action, $entity_id, $account = NULL, $skip_permission_check = FALSE, $flagging = NULL) {
694
    // Get the user.
695
    if (!isset($account)) {
696
      $account = $GLOBALS['user'];
697
    }
698
    if (!$account) {
699
      return FALSE;
700
    }
701

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

    
719
    if (($this->errors = module_invoke_all('flag_validate', $action, $this, $entity_id, $account, $skip_permission_check, $flagging))) {
720
      return FALSE;
721
    }
722

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

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

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

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

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

    
797
      // By definition there is no flagging entity yet, but we may have been
798
      // passed one in to save.
799
      if (!isset($flagging)) {
800
        // Construct a new flagging object if we weren't passed one.
801
        $flagging = $this->new_flagging($entity_id, $uid, $sid);
802
      }
803

    
804
      // Invoke hook_entity_presave().
805
      // In the case that we have been passed in a flagging entity to update,
806
      // this is technically still a presave, even though the {flagging} table
807
      // itself is not changed.
808
      module_invoke_all('entity_presave', $flagging, 'flagging');
809

    
810
      if (!$flagged) {
811
        // The entity is unflagged.
812

    
813
        // Save the flagging entity (just our table).
814
        $this->_flag($flagging);
815
        $this->_increase_count($entity_id);
816
        // We're writing out a flagging entity even when we aren't passed one
817
        // (e.g., when flagging via JavaScript toggle links); in this case
818
        // Field API will assign the fields their default values.
819
        $this->_insert_flagging($flagging);
820
        module_invoke_all('flag_flag', $this, $entity_id, $account, $flagging);
821
        // Invoke Rules event.
822
        if (module_exists('rules')) {
823
          $event_name = 'flag_flagged_' . $this->name;
824
          // We only support flags on entities.
825
          if (entity_get_info($this->entity_type)) {
826
            $variables = array(
827
              'flag' => $this,
828
              'flagged_' . $this->entity_type => $entity_id,
829
              'flagging_user' => $account,
830
              'flagging' => $this->get_flagging($entity_id, $account->uid),
831
            );
832
            rules_invoke_event_by_args($event_name, $variables);
833
          }
834
        }
835
      }
836
      else {
837
        // Nothing to do. Item is already flagged.
838
        //
839
        // Except in the case a $flagging object is one that was passed in: in
840
        // this case we're being requested to update the flagging. This occurs,
841
        // for example, when the user updates the flagging entity form.
842
        if (!empty($flagging->flagging_id)) {
843
          $this->_update_flagging($flagging);
844
        }
845
      }
846
    }
847

    
848
    return TRUE;
849
  }
850

    
851
  /**
852
   * The entity CRUD methods _{insert,update,delete}_flagging() are for private
853
   * use by the flag() method.
854
   *
855
   * The reason programmers should not call them directly is because a flagging
856
   * operation is also accompanied by some bookkeeping (calling hooks, updating
857
   * counters) or access control. These tasks are handled by the flag() method.
858
   */
859
  private function _insert_flagging($flagging) {
860
    field_attach_presave('flagging', $flagging);
861
    field_attach_insert('flagging', $flagging);
862
    // Invoke hook_entity_insert().
863
    module_invoke_all('entity_insert', $flagging, 'flagging');
864
  }
865
  private function _update_flagging($flagging) {
866
    field_attach_presave('flagging', $flagging);
867
    field_attach_update('flagging', $flagging);
868
    // Update the cache.
869
    entity_get_controller('flagging')->resetCache();
870
    // Invoke hook_entity_update().
871
    // Since there are no fields on the {flagging} table that can be
872
    // meaningfully changed, we don't perform an update on it. However, this
873
    // technically still counts as updating the flagging entity, since we update
874
    // its fields.
875
    module_invoke_all('entity_update', $flagging, 'flagging');
876
  }
877
  private function _delete_flagging($flagging) {
878
    field_attach_delete('flagging', $flagging);
879
    // Remove from the cache.
880
    entity_get_controller('flagging')->resetCache();
881
    // Invoke hook_entity_delete().
882
    module_invoke_all('entity_delete', $flagging, 'flagging');
883
  }
884

    
885
  /**
886
   * Construct a new, empty flagging entity object.
887
   *
888
   * @param mixed $entity_id
889
   *   The unique identifier of the object being flagged.
890
   * @param int $uid
891
   *   (optional) The user id of the user doing the flagging.
892
   * @param mixed $sid
893
   *   (optional) The user SID (provided by Session API) who is doing the
894
   *   flagging. The SID is 0 for logged in users.
895
   *
896
   * @return stdClass
897
   *   The returned object has at least the 'flag_name' property set, which
898
   *   enables Field API to figure out the bundle, but it's your responsibility
899
   *   to eventually populate 'entity_id' and 'flagging_id'.
900
   */
901
  function new_flagging($entity_id = NULL, $uid = NULL, $sid = NULL) {
902
    return (object) array(
903
      'flagging_id' => NULL,
904
      'flag_name' => $this->name,
905
      'fid' => $this->fid,
906
      'entity_type' => $this->entity_type,
907
      'entity_id' => $entity_id,
908
      'uid' => $uid,
909
      'sid' => $sid,
910
      // The timestamp is not set until this is saved.
911
    );
912
  }
913

    
914
  /**
915
   * Determines if a certain user has flagged this object.
916
   *
917
   * Thanks to using a cache, inquiring several different flags about the same
918
   * item results in only one SQL query.
919
   *
920
   * @param $uid
921
   *   (optional) The user ID whose flags we're checking. If none given, the
922
   *   current user will be used.
923
   *
924
   * @return
925
   *   TRUE if the object is flagged, FALSE otherwise.
926
   */
927
  function is_flagged($entity_id, $uid = NULL, $sid = NULL) {
928
    return (bool) $this->get_flagging_record($entity_id, $uid, $sid);
929
  }
930

    
931
  /**
932
   * Returns the flagging record.
933
   *
934
   * This method returns the "flagging record": the {flagging} record that
935
   * exists for each flagged item (for a certain user). If the item isn't
936
   * flagged, returns NULL. This method could be useful, for example, when you
937
   * want to find out the 'flagging_id' or 'timestamp' values.
938
   *
939
   * Thanks to using a cache, inquiring several different flags about the same
940
   * item results in only one SQL query.
941
   *
942
   * Parameters are the same as is_flagged()'s.
943
   */
944
  function get_flagging_record($entity_id, $uid = NULL, $sid = NULL) {
945
    $uid = $this->global ? 0 : (!isset($uid) ? $GLOBALS['user']->uid : $uid);
946
    $sid = $this->global ? 0 : (!isset($sid) ? flag_get_sid($uid) : $sid);
947

    
948
    // flag_get_user_flags() does caching.
949
    $user_flags = flag_get_user_flags($this->entity_type, $entity_id, $uid, $sid);
950
    return isset($user_flags[$this->name]) ? $user_flags[$this->name] : NULL;
951
  }
952

    
953
  /**
954
   * Similar to is_flagged() excepts it returns the flagging entity.
955
   */
956
  function get_flagging($entity_id, $uid = NULL, $sid = NULL) {
957
    if (($record = $this->get_flagging_record($entity_id, $uid, $sid))) {
958
      return flagging_load($record->flagging_id);
959
    }
960
  }
961

    
962
  /**
963
   * Determines if a certain user has flagged this object.
964
   *
965
   * You probably shouldn't call this raw private method: call the
966
   * is_flagged() method instead.
967
   *
968
   * This method is similar to is_flagged() except that it does direct SQL and
969
   * doesn't do caching. Use it when you want to not affect the cache, or to
970
   * bypass it.
971
   *
972
   * @return
973
   *   If the object is flagged, returns the value of the 'flagging_id' column.
974
   *   Else, returns FALSE.
975
   *
976
   * @private
977
   */
978
  function _is_flagged($entity_id, $uid, $sid) {
979
    return db_select('flagging', 'fc')
980
      ->fields('fc', array('flagging_id'))
981
      ->condition('fid', $this->fid)
982
      ->condition('uid', $uid)
983
      ->condition('sid', $sid)
984
      ->condition('entity_id', $entity_id)
985
      ->execute()
986
      ->fetchField();
987
  }
988

    
989
  /**
990
   * A low-level method to flag an object.
991
   *
992
   * You probably shouldn't call this raw private method: call the flag()
993
   * function instead.
994
   *
995
   * @param
996
   *   The flagging record.
997
   *
998
   * @private
999
   */
1000
  function _flag($flagging) {
1001
    // Set the timestamp.
1002
    $flagging->timestamp = REQUEST_TIME;
1003

    
1004
    drupal_write_record('flagging', $flagging);
1005

    
1006
    // Clear various caches; we don't want code running after us to report
1007
    // wrong counts or false flaggings.
1008
    drupal_static_reset('flag_get_user_flags');
1009
    drupal_static_reset('flag_get_entity_flags');
1010
    // Despite being named in the same pattern as the count API functions, these
1011
    // query the {flagging} table, so are reset here.
1012
    drupal_static_reset('flag_get_entity_flag_counts');
1013
    drupal_static_reset('flag_get_user_flag_counts');
1014
  }
1015

    
1016
  /**
1017
   * A low-level method to unflag an object.
1018
   *
1019
   * You probably shouldn't call this raw private method: call the flag()
1020
   * function instead.
1021
   *
1022
   * @private
1023
   */
1024
  function _unflag($entity_id, $flagging_id) {
1025
    db_delete('flagging')->condition('flagging_id', $flagging_id)->execute();
1026

    
1027
    // Clear various caches; we don't want code running after us to report
1028
    // wrong counts or false flaggings.
1029
    drupal_static_reset('flag_get_user_flags');
1030
    drupal_static_reset('flag_get_entity_flags');
1031
    // Despite being named in the same pattern as the count API functions, these
1032
    // query the {flagging} table, so are reset here.
1033
    drupal_static_reset('flag_get_entity_flag_counts');
1034
    drupal_static_reset('flag_get_user_flag_counts');
1035
  }
1036

    
1037
  /**
1038
   * Increases the flag count for an object and clears the static counts cache.
1039
   *
1040
   * @param $entity_id
1041
   *   For which item should the count be increased.
1042
   * @param $number
1043
   *   The amount of counts to increasing. Defaults to 1.
1044
   *
1045
   * @private
1046
  */
1047
  function _increase_count($entity_id, $number = 1) {
1048
    db_merge('flag_counts')
1049
      ->key(array(
1050
        'fid' => $this->fid,
1051
        'entity_id' => $entity_id,
1052
      ))
1053
      ->fields(array(
1054
        'entity_type' => $this->entity_type,
1055
        'count' => $number,
1056
        'last_updated' => REQUEST_TIME,
1057
      ))
1058
      ->updateFields(array(
1059
        'last_updated' => REQUEST_TIME,
1060
      ))
1061
      ->expression('count', 'count + :inc', array(':inc' => $number))
1062
      ->execute();
1063

    
1064
    // Reset the static cache of flag counts, so code running after this gets
1065
    // correct counts.
1066
    drupal_static_reset('flag_get_counts');
1067
    drupal_static_reset('flag_get_flag_counts');
1068
  }
1069

    
1070
  /**
1071
   * Decreases the flag count for an object and clears the static counts cache.
1072
   *
1073
   * @param $entity_id
1074
   *   For which item should the count be descreased.
1075
   * @param $number
1076
   *   The amount of counts to decrease. Defaults to 1.
1077
   *
1078
   * @private
1079
  */
1080
  function _decrease_count($entity_id, $number = 1) {
1081
    // Delete rows with count 0, for data consistency and space-saving.
1082
    // Done before the db_update() to prevent out-of-bounds errors on "count".
1083
    db_delete('flag_counts')
1084
      ->condition('fid', $this->fid)
1085
      ->condition('entity_id', $entity_id)
1086
      ->condition('count', $number, '<=')
1087
      ->execute();
1088

    
1089
    // Update the count with the new value otherwise.
1090
    db_update('flag_counts')
1091
      ->expression('count', 'count - :inc', array(':inc' => $number))
1092
      ->fields(array(
1093
        'last_updated' => REQUEST_TIME,
1094
      ))
1095
      ->condition('fid', $this->fid)
1096
      ->condition('entity_id', $entity_id)
1097
      ->execute();
1098

    
1099
    // Reset the static cache of flag counts, so code running after this gets
1100
    // correct counts.
1101
    drupal_static_reset('flag_get_counts');
1102
    drupal_static_reset('flag_get_flag_counts');
1103
  }
1104

    
1105
  /**
1106
   * Set a cookie for anonymous users to record their flagging.
1107
   *
1108
   * @private
1109
   */
1110
  function _flag_anonymous($entity_id) {
1111
    $storage = FlagCookieStorage::factory($this);
1112
    $storage->flag($entity_id);
1113
  }
1114

    
1115
  /**
1116
   * Remove the cookie for anonymous users to record their unflagging.
1117
   *
1118
   * @private
1119
   */
1120
  function _unflag_anonymous($entity_id) {
1121
    $storage = FlagCookieStorage::factory($this);
1122
    $storage->unflag($entity_id);
1123
  }
1124

    
1125
  /**
1126
   * Returns the number of times an item is flagged.
1127
   *
1128
   * Thanks to using a cache, inquiring several different flags about the same
1129
   * item results in only one SQL query.
1130
   */
1131
  function get_count($entity_id) {
1132
    $counts = flag_get_counts($this->entity_type, $entity_id);
1133
    return isset($counts[$this->name]) ? $counts[$this->name] : 0;
1134
  }
1135

    
1136
  /**
1137
   * Returns the number of items a user has flagged.
1138
   *
1139
   * For global flags, pass '0' as the user ID and session ID.
1140
   */
1141
  function get_user_count($uid, $sid = NULL) {
1142
    if (!isset($sid)) {
1143
      $sid = flag_get_sid($uid);
1144
    }
1145
    return db_select('flagging', 'fc')->fields('fc', array('flagging_id'))
1146
      ->condition('fid', $this->fid)
1147
      ->condition('uid', $uid)
1148
      ->condition('sid', $sid)
1149
      ->countQuery()
1150
      ->execute()
1151
      ->fetchField();
1152
  }
1153

    
1154
  /**
1155
   * Processes a flag label for display. This means language translation and
1156
   * token replacements.
1157
   *
1158
   * You should always call this function and not get at the label directly.
1159
   * E.g., do `print $flag->get_label('title')` instead of `print
1160
   * $flag->title`.
1161
   *
1162
   * @param $label
1163
   *   The label to get, e.g. 'title', 'flag_short', 'unflag_short', etc.
1164
   * @param $entity_id
1165
   *   The ID in whose context to interpret tokens. If not given, only global
1166
   *   tokens will be substituted.
1167
   * @return
1168
   *   The processed label.
1169
   */
1170
  function get_label($label, $entity_id = NULL) {
1171
    if (!isset($this->$label)) {
1172
      return;
1173
    }
1174
    $label = t($this->$label);
1175
    if (strpos($label, '[') !== FALSE) {
1176
      $label = $this->replace_tokens($label, array(), array('sanitize' => FALSE), $entity_id);
1177
    }
1178
    return filter_xss_admin($label);
1179
  }
1180

    
1181
  /**
1182
   * Get the link type for this flag.
1183
   */
1184
  function get_link_type() {
1185
    $link_types = flag_get_link_types();
1186
    return (isset($this->link_type) && isset($link_types[$this->link_type])) ? $link_types[$this->link_type] : $link_types['normal'];
1187
  }
1188

    
1189
  /**
1190
   * Replaces tokens in a label. Only the 'global' token context is recognized
1191
   * by default, so derived classes should override this method to add all
1192
   * token contexts they understand.
1193
   */
1194
  function replace_tokens($label, $contexts, $options, $entity_id) {
1195
    if (strpos($label , 'flagging:') !== FALSE) {
1196
      if (($flagging = $this->get_flagging($entity_id))) {
1197
        $contexts['flagging'] = $flagging;
1198
      }
1199
    }
1200
    return token_replace($label, $contexts, $options);
1201
  }
1202

    
1203
  /**
1204
   * Returns the token types this flag understands in labels. These are used
1205
   * for narrowing down the token list shown in the help box to only the
1206
   * relevant ones.
1207
   *
1208
   * Derived classes should override this.
1209
   */
1210
  function get_labels_token_types() {
1211
    return array('flagging');
1212
  }
1213

    
1214
  /**
1215
   * A convenience method for getting the flag title.
1216
   *
1217
   * `$flag->get_title()` is shorthand for `$flag->get_label('title')`.
1218
   */
1219
  function get_title($entity_id = NULL, $reset = FALSE) {
1220
    static $titles = array();
1221
    if ($reset) {
1222
      $titles = array();
1223
    }
1224
    $slot = intval($entity_id); // Convert NULL to 0.
1225
    if (!isset($titles[$this->fid][$slot])) {
1226
      $titles[$this->fid][$slot] = $this->get_label('title', $entity_id);
1227
    }
1228
    return $titles[$this->fid][$slot];
1229
  }
1230

    
1231
  /**
1232
   * Returns a 'flag action' object. It exists only for the sake of its
1233
   * informative tokens. Currently, it's utilized only for the 'mail' action.
1234
   *
1235
   * Derived classes should populate the 'content_title' and 'content_url'
1236
   * slots.
1237
   */
1238
  function get_flag_action($entity_id) {
1239
    $flag_action = new stdClass();
1240
    $flag_action->flag = $this->name;
1241
    $flag_action->entity_type = $this->entity_type;
1242
    $flag_action->entity_id = $entity_id;
1243
    return $flag_action;
1244
  }
1245

    
1246
  /**
1247
   * Returns an array of errors set during validation.
1248
   */
1249
  function get_errors() {
1250
    return $this->errors;
1251
  }
1252

    
1253
  /**
1254
   * @addtogroup actions
1255
   * @{
1256
   * Methods that can be overridden to support Actions.
1257
   */
1258

    
1259
  /**
1260
   * Returns an array of all actions that are executable with this flag.
1261
   */
1262
  function get_valid_actions() {
1263
    $actions = module_invoke_all('action_info');
1264
    foreach ($actions as $callback => $action) {
1265
      if ($action['type'] != $this->entity_type && !in_array('any', $action['triggers'])) {
1266
        unset($actions[$callback]);
1267
      }
1268
    }
1269
    return $actions;
1270
  }
1271

    
1272
  /**
1273
   * Returns objects the action may possibly need. This method should return at
1274
   * least the 'primary' object the action operates on.
1275
   *
1276
   * This method is needed because get_valid_actions() returns actions that
1277
   * don't necessarily operate on an object of a type this flag manages. For
1278
   * example, flagging a comment may trigger an 'Unpublish post' action on a
1279
   * node; So the comment flag needs to tell the action about some node.
1280
   *
1281
   * Derived classes must implement this.
1282
   *
1283
   * @abstract
1284
   */
1285
  function get_relevant_action_objects($entity_id) {
1286
    return array();
1287
  }
1288

    
1289
  /**
1290
   * @} End of "addtogroup actions".
1291
   */
1292

    
1293
  /**
1294
   * @addtogroup views
1295
   * @{
1296
   * Methods that can be overridden to support the Views module.
1297
   */
1298

    
1299
  /**
1300
   * Returns information needed for Views integration. E.g., the Views table
1301
   * holding the flagged object, its primary key, and various labels. See
1302
   * derived classes for examples.
1303
   *
1304
   * @static
1305
   */
1306
  function get_views_info() {
1307
    return array();
1308
  }
1309

    
1310
  /**
1311
   * @} End of "addtogroup views".
1312
   */
1313

    
1314
  /**
1315
   * Saves a flag to the database. It is a wrapper around update() and insert().
1316
   */
1317
  function save() {
1318
    // Allow the 'global' property to be a boolean, particularly when defined in
1319
    // hook_flag_default_flags(). Without this, a value of FALSE gets casted to
1320
    // an empty string which violates our schema. Other boolean properties are
1321
    // fine, as they are serialized.
1322
    $this->global = (int) $this->global;
1323

    
1324
    if (isset($this->fid)) {
1325
      $this->update();
1326
      $this->is_new = FALSE;
1327
    }
1328
    else {
1329
      $this->insert();
1330
      $this->is_new = TRUE;
1331
    }
1332
    // Clear the page cache for anonymous users.
1333
    cache_clear_all('*', 'cache_page', TRUE);
1334
  }
1335

    
1336
  /**
1337
   * Saves an existing flag to the database. Better use save().
1338
   */
1339
  function update() {
1340
    db_update('flag')->fields(array(
1341
      'name' => $this->name,
1342
      'title' => $this->title,
1343
      'global' => $this->global,
1344
      'options' => $this->get_serialized_options()))
1345
      ->condition('fid', $this->fid)
1346
      ->execute();
1347
    db_delete('flag_types')->condition('fid', $this->fid)->execute();
1348
    foreach ($this->types as $type) {
1349
      db_insert('flag_types')->fields(array(
1350
        'fid' => $this->fid,
1351
        'type' => $type))
1352
        ->execute();
1353
    }
1354
  }
1355

    
1356
  /**
1357
   * Saves a new flag to the database. Better use save().
1358
   */
1359
  function insert() {
1360
    $this->fid = db_insert('flag')
1361
      ->fields(array(
1362
        'entity_type' => $this->entity_type,
1363
        'name' => $this->name,
1364
        'title' => $this->title,
1365
        'global' => $this->global,
1366
        'options' => $this->get_serialized_options(),
1367
      ))
1368
      ->execute();
1369
    foreach ($this->types as $type) {
1370
      db_insert('flag_types')
1371
        ->fields(array(
1372
          'fid' => $this->fid,
1373
          'type' => $type,
1374
        ))
1375
        ->execute();
1376
    }
1377
  }
1378

    
1379
  /**
1380
   * Options are stored serialized in the database.
1381
   */
1382
  function get_serialized_options() {
1383
    $option_names = array_keys($this->options());
1384
    $options = array();
1385
    foreach ($option_names as $option) {
1386
      $options[$option] = $this->$option;
1387
    }
1388
    return serialize($options);
1389
  }
1390

    
1391
  /**
1392
   * Deletes a flag from the database.
1393
   */
1394
  function delete() {
1395
    db_delete('flag')->condition('fid', $this->fid)->execute();
1396
    db_delete('flagging')->condition('fid', $this->fid)->execute();
1397
    db_delete('flag_types')->condition('fid', $this->fid)->execute();
1398
    db_delete('flag_counts')->condition('fid', $this->fid)->execute();
1399
    module_invoke_all('flag_delete', $this);
1400
  }
1401

    
1402
  /**
1403
   * Returns TRUE if this flag's declared API version is compatible with this
1404
   * module.
1405
   *
1406
   * An "incompatible" flag is one exported (and now being imported or exposed
1407
   * via hook_flag_default_flags()) by a different version of the Flag module.
1408
   * An incompatible flag should be treated as a "black box": it should not be
1409
   * saved or exported because our code may not know to handle its internal
1410
   * structure.
1411
   */
1412
  function is_compatible() {
1413
    if (isset($this->fid)) {
1414
      // Database flags are always compatible.
1415
      return TRUE;
1416
    }
1417
    else {
1418
      if (!isset($this->api_version)) {
1419
        $this->api_version = 1;
1420
      }
1421
      return $this->api_version == FLAG_API_VERSION;
1422
    }
1423
  }
1424

    
1425
  /**
1426
   * Finds the "default flag" corresponding to this flag.
1427
   *
1428
   * Flags defined in code ("default flags") can be overridden. This method
1429
   * returns the default flag that is being overridden by $this. Returns NULL
1430
   * if $this overrides no default flag.
1431
   */
1432
  function find_default_flag() {
1433
    if ($this->fid) {
1434
      $default_flags = flag_get_default_flags(TRUE);
1435
      if (isset($default_flags[$this->name])) {
1436
        return $default_flags[$this->name];
1437
      }
1438
    }
1439
  }
1440

    
1441
  /**
1442
   * Reverts an overriding flag to its default state.
1443
   *
1444
   * Note that $this isn't altered. To see the reverted flag you'll have to
1445
   * call flag_get_flag($this->name) again.
1446
   *
1447
   * @return
1448
   *   TRUE if the flag was reverted successfully; FALSE if there was an error;
1449
   *   NULL if this flag overrides no default flag.
1450
   */
1451
  function revert() {
1452
    if (($default_flag = $this->find_default_flag())) {
1453
      if ($default_flag->is_compatible()) {
1454
        $default_flag = clone $default_flag;
1455
        $default_flag->fid = $this->fid;
1456
        $default_flag->save();
1457
        drupal_static_reset('flag_get_flags');
1458
        return TRUE;
1459
      }
1460
      else {
1461
        return FALSE;
1462
      }
1463
    }
1464
  }
1465

    
1466
  /**
1467
   * Disable a flag provided by a module.
1468
   */
1469
  function disable() {
1470
    if (isset($this->module)) {
1471
      $flag_status = variable_get('flag_default_flag_status', array());
1472
      $flag_status[$this->name] = FALSE;
1473
      variable_set('flag_default_flag_status', $flag_status);
1474
    }
1475
  }
1476

    
1477
  /**
1478
   * Enable a flag provided by a module.
1479
   */
1480
  function enable() {
1481
    if (isset($this->module)) {
1482
      $flag_status = variable_get('flag_default_flag_status', array());
1483
      $flag_status[$this->name] = TRUE;
1484
      variable_set('flag_default_flag_status', $flag_status);
1485
    }
1486
  }
1487

    
1488
  /**
1489
   * Returns administrative menu path for carrying out some action.
1490
   */
1491
  function admin_path($action) {
1492
    if ($action == 'edit') {
1493
      // Since 'edit' is the default tab, we omit the action.
1494
      return FLAG_ADMIN_PATH . '/manage/' . $this->name;
1495
    }
1496
    else {
1497
      return FLAG_ADMIN_PATH . '/manage/' . $this->name . '/' . $action;
1498
    }
1499
  }
1500

    
1501
  /**
1502
   * Renders a flag/unflag link.
1503
   *
1504
   * This is a wrapper around theme('flag') that channels the call to the right
1505
   * template file.
1506
   *
1507
   * @param $action
1508
   *  The action the link is about to carry out, either "flag" or "unflag".
1509
   * @param $entity_id
1510
   *  The ID of the object to flag.
1511
   * @param $variables = array()
1512
   *  An array of further variables to pass to theme('flag'). For the full list
1513
   *  of parameters, see flag.tpl.php. Of particular interest:
1514
   *  - after_flagging: Set to TRUE if this flag link is being displayed as the result
1515
   *    of a flagging action.
1516
   *  - errors: An array of error messages.
1517
   *
1518
   * @return
1519
   *  The HTML for the flag link.
1520
   */
1521
  function theme($action, $entity_id, $variables = array()) {
1522
    static $js_added = array();
1523
    global $user;
1524

    
1525
    $after_flagging = !empty($variables['after_flagging']);
1526

    
1527
    // If the flagging user is anonymous, set a boolean for the benefit of
1528
    // JavaScript code. Currently, only our "anti-crawlers" mechanism uses it.
1529
    if ($user->uid == 0 && !isset($js_added['anonymous'])) {
1530
      $js_added['anonymous'] = TRUE;
1531
      drupal_add_js(array('flag' => array('anonymous' => TRUE)), 'setting');
1532
    }
1533

    
1534
    // If the flagging user is anonymous and the page cache is enabled, we
1535
    // update the links through JavaScript.
1536
    if ($this->uses_anonymous_cookies() && !$after_flagging) {
1537
      if ($this->global) {
1538
        // In case of global flags, the JavaScript template is to contain
1539
        // the opposite of the current state.
1540
        $js_action = ($action == 'flag' ? 'unflag' : 'flag');
1541
      }
1542
      else {
1543
        // In case of non-global flags, we always show the "flag!" link,
1544
        // and then replace it with the "unflag!" link through JavaScript.
1545
        $js_action = 'unflag';
1546
        $action = 'flag';
1547
      }
1548
      if (!isset($js_added[$this->name . '_' . $entity_id])) {
1549
        $js_added[$this->name . '_' . $entity_id] = TRUE;
1550
        $js_template = theme($this->theme_suggestions(), array(
1551
          'flag' => $this,
1552
          'action' => $js_action,
1553
          'entity_id' => $entity_id,
1554
          'after_flagging' => $after_flagging,
1555
        ));
1556
        drupal_add_js(array('flag' => array('templates' => array($this->name . '_' . $entity_id => $js_template))), 'setting');
1557
      }
1558
    }
1559

    
1560
    return theme($this->theme_suggestions(), array(
1561
      'flag' => $this,
1562
      'action' => $action,
1563
      'entity_id' => $entity_id,
1564
    ) + $variables);
1565
  }
1566

    
1567
  /**
1568
   * Provides an array of possible themes to try for a given flag.
1569
   */
1570
  function theme_suggestions() {
1571
    $suggestions = array();
1572
    $suggestions[] = 'flag__' . $this->name;
1573
    $suggestions[] = 'flag__' . $this->link_type;
1574
    $suggestions[] = 'flag';
1575
    return $suggestions;
1576
  }
1577

    
1578
  /**
1579
   * A shortcut function to output the link URL.
1580
   */
1581
  function _flag_url($path, $fragment = NULL, $absolute = TRUE) {
1582
    return url($path, array('fragment' => $fragment, 'absolute' => $absolute));
1583
  }
1584
}
1585

    
1586
/**
1587
 * A dummy flag to be used where the real implementation can't be found.
1588
 */
1589
class flag_broken extends flag_flag {
1590
  function options_form(&$form) {
1591
    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');
1592
    $form = array();
1593
  }
1594
}