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 @ 76e2e7c3

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
145
    return $flag;
146
  }
147

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

    
158
    foreach ($config as $option => $value) {
159
      $flag->$option = $value;
160
    }
161

    
162
    if (isset($config['locked']) && is_array($config['locked'])) {
163
      $flag->locked = drupal_map_assoc($config['locked']);
164
    }
165

    
166
    return $flag;
167
  }
168

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

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

    
201
    // Merge in options from the current link type.
202
    $link_type = $this->get_link_type();
203
    $options = array_merge($options, $link_type['options']);
204

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

    
217
  /**
218
   * Default constructor. Loads the default options.
219
   */
220
  function construct() {
221
    $options = $this->options();
222
    foreach ($options as $option => $value) {
223
      $this->$option = $value;
224
    }
225
  }
226

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

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

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

    
277
  /**
278
   * Validates that the current flag's name is valid.
279
   *
280
   * @return
281
   *   A list of errors encountered while validating this flag's name.
282
   */
283
  function validate_name() {
284
    $errors = array();
285

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

    
302
    return $errors;
303
  }
304

    
305
  /**
306
   * Validates that the current flag's access settings are valid.
307
   */
308
  function validate_access() {
309
    $errors = array();
310

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

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

    
335
    return $errors;
336
  }
337

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

    
353
  /**
354
   * Loads an entity this flag works with.
355
   * Derived classes must implement this.
356
   *
357
   * @abstract
358
   * @private
359
   * @static
360
   */
361
  function _load_entity($entity_id) {
362
    return NULL;
363
  }
364

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

    
382
  /**
383
   * @defgroup access Access control
384
   * @{
385
   */
386

    
387
  /**
388
   * Returns TRUE if the flag applies to the given entity.
389
   *
390
   * Derived classes must implement this.
391
   *
392
   * @abstract
393
   */
394
  function applies_to_entity($entity) {
395
    return FALSE;
396
  }
397

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

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

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

    
449
    // Anonymous user can't use this system unless Session API is installed.
450
    if ($account->uid == 0 && !module_exists('session_api')) {
451
      return FALSE;
452
    }
453

    
454
    $permission_string = "$action $this->name";
455
    return user_access($permission_string, $account);
456
  }
457

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

    
482
    if (isset($entity_id) && !$this->applies_to_entity_id($entity_id)) {
483
      // Flag does not apply to this entity.
484
      return FALSE;
485
    }
486

    
487
    if (!isset($action)) {
488
      $uid = $account->uid;
489
      $sid = flag_get_sid($uid);
490
      $action = $this->is_flagged($entity_id, $uid, $sid) ? 'unflag' : 'flag';
491
    }
492

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

    
496
    // Check for additional access rules provided by sub-classes.
497
    $child_access = $this->type_access($entity_id, $action, $account);
498
    if (isset($child_access)) {
499
      $access = $child_access;
500
    }
501

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

    
517
    return $access;
518
  }
519

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

    
550
    // First check basic user access for this action.
551
    foreach ($entity_ids as $entity_id => $action) {
552
      $access[$entity_id] = $this->user_access($entity_ids[$entity_id], $account);
553
    }
554

    
555
    // Check for additional access rules provided by sub-classes.
556
    $child_access = $this->type_access_multiple($entity_ids, $account);
557
    if (isset($child_access)) {
558
      foreach ($child_access as $entity_id => $entity_access) {
559
        if (isset($entity_access)) {
560
          $access[$entity_id] = $entity_access;
561
        }
562
      }
563
    }
564

    
565
    // Merge in module-defined access.
566
    foreach (module_implements('flag_access_multiple') as $module) {
567
      $module_access = module_invoke($module, 'flag_access_multiple', $this, $entity_ids, $account);
568
      foreach ($module_access as $entity_id => $entity_access) {
569
        if (isset($entity_access)) {
570
          $access[$entity_id] = $entity_access;
571
        }
572
      }
573
    }
574

    
575
    return $access;
576
  }
577

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

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

    
606
  /**
607
   * @} End of "defgroup access".
608
   */
609

    
610
  /**
611
   * Given an entity, returns its ID.
612
   * Derived classes must implement this.
613
   *
614
   * @abstract
615
   */
616
  function get_entity_id($entity) {
617
    return NULL;
618
  }
619

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

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

    
663
  /**
664
   * Returns TRUE if this flag requires anonymous user cookies.
665
   */
666
  function uses_anonymous_cookies() {
667
    global $user;
668
    return $user->uid == 0 && variable_get('cache', 0);
669
  }
670

    
671
  /**
672
   * Flags, or unflags, an item.
673
   *
674
   * @param $action
675
   *   Either 'flag' or 'unflag'.
676
   * @param $entity_id
677
   *   The ID of the item to flag or unflag.
678
   * @param $account
679
   *   The user on whose behalf to flag. Leave empty for the current user.
680
   * @param $skip_permission_check
681
   *   Flag the item even if the $account user don't have permission to do so.
682
   * @param $flagging
683
   *   (optional) This method works in tandem with Drupal's Field subsystem.
684
   *   Pass in a Flagging entity if you want operate on it as well. This may be
685
   *   used either of the following cases:
686
   *   - to save field data on a new Flagging entity at the same time as
687
   *     flagging an entity. In this case, using Entity API's entity_create()
688
   *     is recommended, although the Flagging entity may also be created
689
   *     directly as a new stdClass object.
690
   *   - to update field data an existing flagging. The $action parameter should
691
   *     be set to 'flag'. The Flagging entity will need to be loaded first with
692
   *     flagging_load().
693
   *  As with Drupal core API functions for saving entities, no validation of
694
   *  Field API fields is performed here. It is the responsibility of the caller
695
   *  to take care of Field API validation, using either
696
   *  field_attach_form_validate() or field_attach_validate().
697
   *
698
   * @return
699
   *   FALSE if some error occured (e.g., user has no permission, flag isn't
700
   *   applicable to the item, etc.), TRUE otherwise.
701
   */
702
  function flag($action, $entity_id, $account = NULL, $skip_permission_check = FALSE, $flagging = NULL) {
703
    // Get the user.
704
    if (!isset($account)) {
705
      $account = $GLOBALS['user'];
706
    }
707

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

    
725
    if (($this->errors = module_invoke_all('flag_validate', $action, $this, $entity_id, $account, $skip_permission_check, $flagging))) {
726
      return FALSE;
727
    }
728

    
729
    // Find out which user id to use.
730
    $uid = $this->global ? 0 : $account->uid;
731

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

    
745
    // @todo: Discuss: Core wraps everything in a try { }, should we?
746

    
747
    $existing_flagging_id = $this->_is_flagged($entity_id, $uid, $sid);
748
    $flagged = (bool) $existing_flagging_id;
749

    
750
    // Ensure we have a Flagging entity and it is correctly formed.
751
    if (isset($flagging)) {
752
      // We were given a Flagging entity.
753
      // Ensure that it has the uid and sid that we were also given.
754
      $flagging->uid = $uid;
755
      $flagging->sid = $sid;
756

    
757
      // This is an ugly hack to preserve previous behaviour.
758
      $flagging->given_as_parameter = TRUE;
759
    }
760
    else {
761
      // We were not given a Flagging entity.
762
      if ($flagged) {
763
        // Load the existing Flagging entity.
764
        $flagging = flagging_load($existing_flagging_id);
765
      }
766
      else {
767
        // Construct a new Flagging entity to flag with.
768
        $flagging = $this->new_flagging($entity_id, $uid, $sid);
769
      }
770
    }
771

    
772
    // Perform the flagging or unflagging of this flag.
773
    if ($action == 'unflag') {
774
      if ($flagged) {
775
        $this->flagging_delete($flagging, $entity_id, $account);
776
      }
777
      // We do nothing in the case of an attempt to unflag something that isn't
778
      // actually flagged.
779
    }
780
    elseif ($action == 'flag') {
781
      if (!$flagged) {
782
        $this->flagging_insert($flagging, $entity_id, $account);
783
      }
784
      else {
785
        $this->flagging_update($flagging, $entity_id, $account);
786
      }
787
    }
788

    
789
    return TRUE;
790
  }
791

    
792
  /**
793
   * Create a new Flagging to flag an entity.
794
   *
795
   * @param $flagging
796
   *  The flagging entity that is to be removed.
797
   * @param $entity_id
798
   *  The entity ID of entity being unflagged.
799
   * @param $account
800
   *  The account performing the unflagging.
801
   */
802
  private function flagging_insert($flagging, $entity_id, $account) {
803
    if ($this->uses_anonymous_cookies()) {
804
      $this->_flag_anonymous($entity_id);
805
    }
806

    
807
    // Invoke presave hooks.
808
    field_attach_presave('flagging', $flagging);
809
    // Invoke hook_entity_presave().
810
    module_invoke_all('entity_presave', $flagging, 'flagging');
811

    
812
    // Set the timestamp.
813
    $flagging->timestamp = REQUEST_TIME;
814

    
815
    // Save the flagging entity.
816
    drupal_write_record('flagging', $flagging);
817

    
818
    // Clear various caches; we don't want code running after us to report
819
    // wrong counts or false flaggings.
820
    drupal_static_reset('flag_get_user_flags');
821
    drupal_static_reset('flag_get_entity_flags');
822
    // Despite being named in the same pattern as the count API functions, these
823
    // query the {flagging} table, so are reset here.
824
    drupal_static_reset('flag_get_entity_flag_counts');
825
    drupal_static_reset('flag_get_user_flag_counts');
826

    
827
    $this->_increase_count($entity_id);
828
    // We're writing out a flagging entity even when we aren't passed one
829
    // (e.g., when flagging via JavaScript toggle links); in this case
830
    // Field API will assign the fields their default values.
831

    
832
    // Invoke insert hooks.
833
    field_attach_insert('flagging', $flagging);
834
    // Invoke hook_entity_insert().
835
    module_invoke_all('entity_insert', $flagging, 'flagging');
836

    
837
    module_invoke_all('flag_flag', $this, $entity_id, $account, $flagging);
838
    // Invoke Rules event.
839
    if (module_exists('rules')) {
840
      $this->invoke_rules_event('flag', $flagging, $entity_id, $account);
841
    }
842
  }
843

    
844
  /**
845
   * Invoke a Rules event in reaction to a flagging or unflagging.
846
   *
847
   * @param $action
848
   *   Either 'flag' or 'unflag'.
849
   * @param $flagging
850
   *  The flagging entity that is to be removed.
851
   * @param $entity_id
852
   *  The entity ID of entity being unflagged.
853
   * @param $account
854
   *  The account performing the unflagging.
855
   */
856
  protected function invoke_rules_event($action, $flagging, $entity_id, $account) {
857
    // We only support flags on entities: do nothing in this class.
858
    // See flag_entity::invoke_rules_event().
859
    return;
860
  }
861

    
862
  /**
863
   * Update a Flagging.
864
   *
865
   * @param $flagging
866
   *  The flagging entity that is to be removed.
867
   * @param $entity_id
868
   *  The entity ID of entity being unflagged.
869
   * @param $account
870
   *  The account performing the unflagging.
871
   */
872
  private function flagging_update($flagging, $entity_id, $account) {
873
    // Invoke presave hooks.
874
    // This is technically still a presave, even though the {flagging} table
875
    // itself is not changed.
876
    field_attach_presave('flagging', $flagging);
877
    // Invoke hook_entity_presave().
878
    module_invoke_all('entity_presave', $flagging, 'flagging');
879

    
880
    // This check exists solely to preserve previous behaviour with re-flagging.
881
    // TODO: consider removing it.
882
    if (!empty($flagging->given_as_parameter)) {
883
      field_attach_update('flagging', $flagging);
884
      // Update the cache.
885
      entity_get_controller('flagging')->resetCache();
886
      // Invoke hook_entity_update().
887
      // Since there are no fields on the {flagging} table that can be
888
      // meaningfully changed, we don't perform an update on it. However, this
889
      // technically still counts as updating the flagging entity, since we update
890
      // its fields.
891
      module_invoke_all('entity_update', $flagging, 'flagging');
892
    }
893
  }
894

    
895
  /**
896
   * Unflag an entity by deleting a Flagging.
897
   *
898
   * @param $flagging
899
   *  The flagging entity that is to be removed.
900
   * @param $entity_id
901
   *  The entity ID of entity being unflagged.
902
   * @param $account
903
   *  The account performing the unflagging.
904
   */
905
  private function flagging_delete($flagging, $entity_id, $account) {
906
    if ($this->uses_anonymous_cookies()) {
907
      $this->_unflag_anonymous($entity_id);
908
    }
909

    
910
    $transaction = db_transaction();
911
    try {
912
      // Note the order: We decrease the count first so hooks have accurate
913
      // data, then invoke hooks, then delete the flagging entity.
914
      $this->_decrease_count($entity_id);
915
      module_invoke_all('flag_unflag', $this, $entity_id, $account, $flagging);
916
      // Invoke Rules event.
917
      if (module_exists('rules')) {
918
        $this->invoke_rules_event('unflag', $flagging, $entity_id, $account);
919
      }
920

    
921
      // Invoke hook_entity_delete().
922
      module_invoke_all('entity_delete', $flagging, 'flagging');
923
      // Delete field data.
924
      field_attach_delete('flagging', $flagging);
925

    
926
      // Delete the flagging entity.
927
      db_delete('flagging')->condition('flagging_id', $flagging->flagging_id)->execute();
928

    
929
      // Remove from the cache.
930
      entity_get_controller('flagging')->resetCache();
931

    
932
      // Clear various caches; we don't want code running after us to report
933
      // wrong counts or false flaggings.
934
      drupal_static_reset('flag_get_user_flags');
935
      drupal_static_reset('flag_get_entity_flags');
936
      // Despite being named in the same pattern as the count API functions, these
937
      // query the {flagging} table, so are reset here.
938
      drupal_static_reset('flag_get_entity_flag_counts');
939
      drupal_static_reset('flag_get_user_flag_counts');
940
    }
941
    catch (Exception $e) {
942
      $transaction->rollback();
943
      watchdog_exception('flag', $e);
944
      throw $e;
945
    }
946
  }
947

    
948
  /**
949
   * Construct a new, empty flagging entity object.
950
   *
951
   * @param mixed $entity_id
952
   *   The unique identifier of the object being flagged.
953
   * @param int $uid
954
   *   (optional) The user id of the user doing the flagging.
955
   * @param mixed $sid
956
   *   (optional) The user SID (provided by Session API) who is doing the
957
   *   flagging. The SID is 0 for logged in users.
958
   *
959
   * @return stdClass
960
   *   The returned object has at least the 'flag_name' property set, which
961
   *   enables Field API to figure out the bundle, but it's your responsibility
962
   *   to eventually populate 'entity_id' and 'flagging_id'.
963
   */
964
  function new_flagging($entity_id = NULL, $uid = NULL, $sid = NULL) {
965
    return (object) array(
966
      'flagging_id' => NULL,
967
      'flag_name' => $this->name,
968
      'fid' => $this->fid,
969
      'entity_type' => $this->entity_type,
970
      'entity_id' => $entity_id,
971
      'uid' => $uid,
972
      'sid' => $sid,
973
      // The timestamp is not set until this is saved.
974
    );
975
  }
976

    
977
  /**
978
   * Determines if a certain user has flagged this object.
979
   *
980
   * Thanks to using a cache, inquiring several different flags about the same
981
   * item results in only one SQL query.
982
   *
983
   * @param $uid
984
   *   (optional) The user ID whose flags we're checking. If none given, the
985
   *   current user will be used.
986
   *
987
   * @return
988
   *   TRUE if the object is flagged, FALSE otherwise.
989
   */
990
  function is_flagged($entity_id, $uid = NULL, $sid = NULL) {
991
    return (bool) $this->get_flagging_record($entity_id, $uid, $sid);
992
  }
993

    
994
  /**
995
   * Returns the flagging record.
996
   *
997
   * This method returns the "flagging record": the {flagging} record that
998
   * exists for each flagged item (for a certain user). If the item isn't
999
   * flagged, returns NULL. This method could be useful, for example, when you
1000
   * want to find out the 'flagging_id' or 'timestamp' values.
1001
   *
1002
   * Thanks to using a cache, inquiring several different flags about the same
1003
   * item results in only one SQL query.
1004
   *
1005
   * Parameters are the same as is_flagged()'s.
1006
   */
1007
  function get_flagging_record($entity_id, $uid = NULL, $sid = NULL) {
1008
    $uid = $this->global ? 0 : (!isset($uid) ? $GLOBALS['user']->uid : $uid);
1009
    $sid = $this->global ? 0 : (!isset($sid) ? flag_get_sid($uid) : $sid);
1010

    
1011
    // flag_get_user_flags() does caching.
1012
    $user_flags = flag_get_user_flags($this->entity_type, $entity_id, $uid, $sid);
1013
    return isset($user_flags[$this->name]) ? $user_flags[$this->name] : NULL;
1014
  }
1015

    
1016
  /**
1017
   * Similar to is_flagged() excepts it returns the flagging entity.
1018
   */
1019
  function get_flagging($entity_id, $uid = NULL, $sid = NULL) {
1020
    if (($record = $this->get_flagging_record($entity_id, $uid, $sid))) {
1021
      return flagging_load($record->flagging_id);
1022
    }
1023
  }
1024

    
1025
  /**
1026
   * Determines if a certain user has flagged this object.
1027
   *
1028
   * You probably shouldn't call this raw private method: call the
1029
   * is_flagged() method instead.
1030
   *
1031
   * This method is similar to is_flagged() except that it does direct SQL and
1032
   * doesn't do caching. Use it when you want to not affect the cache, or to
1033
   * bypass it.
1034
   *
1035
   * @return
1036
   *   If the object is flagged, returns the value of the 'flagging_id' column.
1037
   *   Else, returns FALSE.
1038
   *
1039
   * @private
1040
   */
1041
  function _is_flagged($entity_id, $uid, $sid) {
1042
    return db_select('flagging', 'fc')
1043
      ->fields('fc', array('flagging_id'))
1044
      ->condition('fid', $this->fid)
1045
      ->condition('uid', $uid)
1046
      ->condition('sid', $sid)
1047
      ->condition('entity_id', $entity_id)
1048
      ->execute()
1049
      ->fetchField();
1050
  }
1051

    
1052
  /**
1053
   * Increases the flag count for an object and clears the static counts cache.
1054
   *
1055
   * @param $entity_id
1056
   *   For which item should the count be increased.
1057
   * @param $number
1058
   *   The amount of counts to increasing. Defaults to 1.
1059
   *
1060
   * @private
1061
   */
1062
  function _increase_count($entity_id, $number = 1) {
1063
    db_merge('flag_counts')
1064
      ->key(array(
1065
        'fid' => $this->fid,
1066
        'entity_id' => $entity_id,
1067
      ))
1068
      ->fields(array(
1069
        'entity_type' => $this->entity_type,
1070
        'count' => $number,
1071
        'last_updated' => REQUEST_TIME,
1072
      ))
1073
      ->updateFields(array(
1074
        'last_updated' => REQUEST_TIME,
1075
      ))
1076
      ->expression('count', 'count + :inc', array(':inc' => $number))
1077
      ->execute();
1078

    
1079
    // Reset the static cache of flag counts, so code running after this gets
1080
    // correct counts.
1081
    drupal_static_reset('flag_get_counts');
1082
    drupal_static_reset('flag_get_flag_counts');
1083
  }
1084

    
1085
  /**
1086
   * Decreases the flag count for an object and clears the static counts cache.
1087
   *
1088
   * @param $entity_id
1089
   *   For which item should the count be descreased.
1090
   * @param $number
1091
   *   The amount of counts to decrease. Defaults to 1.
1092
   *
1093
   * @private
1094
   */
1095
  function _decrease_count($entity_id, $number = 1) {
1096
    // Delete rows with count 0, for data consistency and space-saving.
1097
    // Done before the db_update() to prevent out-of-bounds errors on "count".
1098
    db_delete('flag_counts')
1099
      ->condition('fid', $this->fid)
1100
      ->condition('entity_id', $entity_id)
1101
      ->condition('count', $number, '<=')
1102
      ->execute();
1103

    
1104
    // Update the count with the new value otherwise.
1105
    db_update('flag_counts')
1106
      ->expression('count', 'count - :inc', array(':inc' => $number))
1107
      ->fields(array(
1108
        'last_updated' => REQUEST_TIME,
1109
      ))
1110
      ->condition('fid', $this->fid)
1111
      ->condition('entity_id', $entity_id)
1112
      ->execute();
1113

    
1114
    // Reset the static cache of flag counts, so code running after this gets
1115
    // correct counts.
1116
    drupal_static_reset('flag_get_counts');
1117
    drupal_static_reset('flag_get_flag_counts');
1118
  }
1119

    
1120
  /**
1121
   * Set a cookie for anonymous users to record their flagging.
1122
   *
1123
   * @private
1124
   */
1125
  function _flag_anonymous($entity_id) {
1126
    $storage = FlagCookieStorage::factory($this);
1127
    $storage->flag($entity_id);
1128
  }
1129

    
1130
  /**
1131
   * Remove the cookie for anonymous users to record their unflagging.
1132
   *
1133
   * @private
1134
   */
1135
  function _unflag_anonymous($entity_id) {
1136
    $storage = FlagCookieStorage::factory($this);
1137
    $storage->unflag($entity_id);
1138
  }
1139

    
1140
  /**
1141
   * Returns the number of times an item is flagged.
1142
   *
1143
   * Thanks to using a cache, inquiring several different flags about the same
1144
   * item results in only one SQL query.
1145
   */
1146
  function get_count($entity_id) {
1147
    $counts = flag_get_counts($this->entity_type, $entity_id);
1148
    return isset($counts[$this->name]) ? $counts[$this->name] : 0;
1149
  }
1150

    
1151
  /**
1152
   * Returns the number of items a user has flagged.
1153
   *
1154
   * For global flags, pass '0' as the user ID and session ID.
1155
   */
1156
  function get_user_count($uid, $sid = NULL) {
1157
    if (!isset($sid)) {
1158
      $sid = flag_get_sid($uid);
1159
    }
1160
    return db_select('flagging', 'fc')->fields('fc', array('flagging_id'))
1161
      ->condition('fid', $this->fid)
1162
      ->condition('uid', $uid)
1163
      ->condition('sid', $sid)
1164
      ->countQuery()
1165
      ->execute()
1166
      ->fetchField();
1167
  }
1168

    
1169
  /**
1170
   * Processes a flag label for display. This means language translation and
1171
   * token replacements.
1172
   *
1173
   * You should always call this function and not get at the label directly.
1174
   * E.g., do `print $flag->get_label('title')` instead of `print
1175
   * $flag->title`.
1176
   *
1177
   * @param $label
1178
   *   The label to get, e.g. 'title', 'flag_short', 'unflag_short', etc.
1179
   * @param $entity_id
1180
   *   The ID in whose context to interpret tokens. If not given, only global
1181
   *   tokens will be substituted.
1182
   *
1183
   * @return
1184
   *   The processed label.
1185
   */
1186
  function get_label($label, $entity_id = NULL) {
1187
    if (!isset($this->$label)) {
1188
      return;
1189
    }
1190
    $label = t($this->$label);
1191
    if (strpos($label, '[') !== FALSE) {
1192
      $label = $this->replace_tokens($label, array(), array('sanitize' => FALSE), $entity_id);
1193
    }
1194
    return filter_xss_admin($label);
1195
  }
1196

    
1197
  /**
1198
   * Get the link type for this flag.
1199
   */
1200
  function get_link_type() {
1201
    $link_types = flag_get_link_types();
1202
    return (isset($this->link_type) && isset($link_types[$this->link_type])) ? $link_types[$this->link_type] : $link_types['normal'];
1203
  }
1204

    
1205
  /**
1206
   * Replaces tokens in a label. Only the 'global' token context is recognized
1207
   * by default, so derived classes should override this method to add all
1208
   * token contexts they understand.
1209
   */
1210
  function replace_tokens($label, $contexts, $options, $entity_id) {
1211
    if (strpos($label , 'flagging:') !== FALSE) {
1212
      if (($flagging = $this->get_flagging($entity_id))) {
1213
        $contexts['flagging'] = $flagging;
1214
      }
1215
    }
1216
    return token_replace($label, $contexts, $options);
1217
  }
1218

    
1219
  /**
1220
   * Returns the token types this flag understands in labels. These are used
1221
   * for narrowing down the token list shown in the help box to only the
1222
   * relevant ones.
1223
   *
1224
   * Derived classes should override this.
1225
   */
1226
  function get_labels_token_types() {
1227
    return array('flagging');
1228
  }
1229

    
1230
  /**
1231
   * A convenience method for getting the flag title.
1232
   *
1233
   * `$flag->get_title()` is shorthand for `$flag->get_label('title')`.
1234
   */
1235
  function get_title($entity_id = NULL, $reset = FALSE) {
1236
    static $titles = array();
1237
    if ($reset) {
1238
      $titles = array();
1239
    }
1240
    $slot = intval($entity_id); // Convert NULL to 0.
1241
    if (!isset($titles[$this->fid][$slot])) {
1242
      $titles[$this->fid][$slot] = $this->get_label('title', $entity_id);
1243
    }
1244
    return $titles[$this->fid][$slot];
1245
  }
1246

    
1247
  /**
1248
   * Returns a 'flag action' object. It exists only for the sake of its
1249
   * informative tokens. Currently, it's utilized only for the 'mail' action.
1250
   *
1251
   * Derived classes should populate the 'content_title' and 'content_url'
1252
   * slots.
1253
   */
1254
  function get_flag_action($entity_id) {
1255
    $flag_action = new stdClass();
1256
    $flag_action->flag = $this->name;
1257
    $flag_action->entity_type = $this->entity_type;
1258
    $flag_action->entity_id = $entity_id;
1259
    return $flag_action;
1260
  }
1261

    
1262
  /**
1263
   * Returns an array of errors set during validation.
1264
   */
1265
  function get_errors() {
1266
    return $this->errors;
1267
  }
1268

    
1269
  /**
1270
   * @addtogroup actions
1271
   * @{
1272
   * Methods that can be overridden to support Actions.
1273
   */
1274

    
1275
  /**
1276
   * Returns an array of all actions that are executable with this flag.
1277
   */
1278
  function get_valid_actions() {
1279
    $actions = module_invoke_all('action_info');
1280
    foreach ($actions as $callback => $action) {
1281
      if ($action['type'] != $this->entity_type && !in_array('any', $action['triggers'])) {
1282
        unset($actions[$callback]);
1283
      }
1284
    }
1285
    return $actions;
1286
  }
1287

    
1288
  /**
1289
   * Returns objects the action may possibly need. This method should return at
1290
   * least the 'primary' object the action operates on.
1291
   *
1292
   * This method is needed because get_valid_actions() returns actions that
1293
   * don't necessarily operate on an object of a type this flag manages. For
1294
   * example, flagging a comment may trigger an 'Unpublish post' action on a
1295
   * node; So the comment flag needs to tell the action about some node.
1296
   *
1297
   * Derived classes must implement this.
1298
   *
1299
   * @abstract
1300
   */
1301
  function get_relevant_action_objects($entity_id) {
1302
    return array();
1303
  }
1304

    
1305
  /**
1306
   * @} End of "addtogroup actions".
1307
   */
1308

    
1309
  /**
1310
   * @addtogroup views
1311
   * @{
1312
   * Methods that can be overridden to support the Views module.
1313
   */
1314

    
1315
  /**
1316
   * Returns information needed for Views integration. E.g., the Views table
1317
   * holding the flagged object, its primary key, and various labels. See
1318
   * derived classes for examples.
1319
   *
1320
   * @static
1321
   */
1322
  function get_views_info() {
1323
    return array();
1324
  }
1325

    
1326
  /**
1327
   * @} End of "addtogroup views".
1328
   */
1329

    
1330
  /**
1331
   * Saves a flag to the database. It is a wrapper around update() and insert().
1332
   */
1333
  function save() {
1334
    // Allow the 'global' property to be a boolean, particularly when defined in
1335
    // hook_flag_default_flags(). Without this, a value of FALSE gets casted to
1336
    // an empty string which violates our schema. Other boolean properties are
1337
    // fine, as they are serialized.
1338
    $this->global = (int) $this->global;
1339

    
1340
    if (isset($this->fid)) {
1341
      $this->update();
1342
      $this->is_new = FALSE;
1343
    }
1344
    else {
1345
      $this->insert();
1346
      $this->is_new = TRUE;
1347
    }
1348
    // Clear the page cache for anonymous users.
1349
    cache_clear_all('*', 'cache_page', TRUE);
1350
  }
1351

    
1352
  /**
1353
   * Saves an existing flag to the database. Better use save().
1354
   */
1355
  function update() {
1356
    db_update('flag')->fields(array(
1357
      'name' => $this->name,
1358
      'title' => $this->title,
1359
      'global' => $this->global,
1360
      'options' => $this->get_serialized_options()))
1361
      ->condition('fid', $this->fid)
1362
      ->execute();
1363
    db_delete('flag_types')->condition('fid', $this->fid)->execute();
1364
    foreach ($this->types as $type) {
1365
      db_insert('flag_types')->fields(array(
1366
        'fid' => $this->fid,
1367
        'type' => $type))
1368
        ->execute();
1369
    }
1370
  }
1371

    
1372
  /**
1373
   * Saves a new flag to the database. Better use save().
1374
   */
1375
  function insert() {
1376
    $this->fid = db_insert('flag')
1377
      ->fields(array(
1378
        'entity_type' => $this->entity_type,
1379
        'name' => $this->name,
1380
        'title' => $this->title,
1381
        'global' => $this->global,
1382
        'options' => $this->get_serialized_options(),
1383
      ))
1384
      ->execute();
1385
    foreach ($this->types as $type) {
1386
      db_insert('flag_types')
1387
        ->fields(array(
1388
          'fid' => $this->fid,
1389
          'type' => $type,
1390
        ))
1391
        ->execute();
1392
    }
1393
  }
1394

    
1395
  /**
1396
   * Options are stored serialized in the database.
1397
   */
1398
  function get_serialized_options() {
1399
    $option_names = array_keys($this->options());
1400
    $options = array();
1401
    foreach ($option_names as $option) {
1402
      $options[$option] = $this->$option;
1403
    }
1404
    return serialize($options);
1405
  }
1406

    
1407
  /**
1408
   * Deletes a flag from the database.
1409
   */
1410
  function delete() {
1411
    db_delete('flag')->condition('fid', $this->fid)->execute();
1412
    db_delete('flagging')->condition('fid', $this->fid)->execute();
1413
    db_delete('flag_types')->condition('fid', $this->fid)->execute();
1414
    db_delete('flag_counts')->condition('fid', $this->fid)->execute();
1415
    module_invoke_all('flag_delete', $this);
1416
  }
1417

    
1418
  /**
1419
   * Returns TRUE if this flag's declared API version is compatible with this
1420
   * module.
1421
   *
1422
   * An "incompatible" flag is one exported (and now being imported or exposed
1423
   * via hook_flag_default_flags()) by a different version of the Flag module.
1424
   * An incompatible flag should be treated as a "black box": it should not be
1425
   * saved or exported because our code may not know to handle its internal
1426
   * structure.
1427
   */
1428
  function is_compatible() {
1429
    if (isset($this->fid)) {
1430
      // Database flags are always compatible.
1431
      return TRUE;
1432
    }
1433
    else {
1434
      if (!isset($this->api_version)) {
1435
        $this->api_version = 1;
1436
      }
1437
      return $this->api_version == FLAG_API_VERSION;
1438
    }
1439
  }
1440

    
1441
  /**
1442
   * Finds the "default flag" corresponding to this flag.
1443
   *
1444
   * Flags defined in code ("default flags") can be overridden. This method
1445
   * returns the default flag that is being overridden by $this. Returns NULL
1446
   * if $this overrides no default flag.
1447
   */
1448
  function find_default_flag() {
1449
    if ($this->fid) {
1450
      $default_flags = flag_get_default_flags(TRUE);
1451
      if (isset($default_flags[$this->name])) {
1452
        return $default_flags[$this->name];
1453
      }
1454
    }
1455
  }
1456

    
1457
  /**
1458
   * Reverts an overriding flag to its default state.
1459
   *
1460
   * Note that $this isn't altered. To see the reverted flag you'll have to
1461
   * call flag_get_flag($this->name) again.
1462
   *
1463
   * @return
1464
   *   TRUE if the flag was reverted successfully; FALSE if there was an error;
1465
   *   NULL if this flag overrides no default flag.
1466
   */
1467
  function revert() {
1468
    if (($default_flag = $this->find_default_flag())) {
1469
      if ($default_flag->is_compatible()) {
1470
        $default_flag = clone $default_flag;
1471
        $default_flag->fid = $this->fid;
1472
        $default_flag->save();
1473
        drupal_static_reset('flag_get_flags');
1474
        return TRUE;
1475
      }
1476
      else {
1477
        return FALSE;
1478
      }
1479
    }
1480
  }
1481

    
1482
  /**
1483
   * Disable a flag provided by a module.
1484
   */
1485
  function disable() {
1486
    if (isset($this->module)) {
1487
      $flag_status = variable_get('flag_default_flag_status', array());
1488
      $flag_status[$this->name] = FALSE;
1489
      variable_set('flag_default_flag_status', $flag_status);
1490
    }
1491
  }
1492

    
1493
  /**
1494
   * Enable a flag provided by a module.
1495
   */
1496
  function enable() {
1497
    if (isset($this->module)) {
1498
      $flag_status = variable_get('flag_default_flag_status', array());
1499
      $flag_status[$this->name] = TRUE;
1500
      variable_set('flag_default_flag_status', $flag_status);
1501
    }
1502
  }
1503

    
1504
  /**
1505
   * Returns administrative menu path for carrying out some action.
1506
   */
1507
  function admin_path($action) {
1508
    if ($action == 'edit') {
1509
      // Since 'edit' is the default tab, we omit the action.
1510
      return FLAG_ADMIN_PATH . '/manage/' . $this->name;
1511
    }
1512
    else {
1513
      return FLAG_ADMIN_PATH . '/manage/' . $this->name . '/' . $action;
1514
    }
1515
  }
1516

    
1517
  /**
1518
   * Renders a flag/unflag link.
1519
   *
1520
   * This is a wrapper around theme('flag') that channels the call to the right
1521
   * template file.
1522
   *
1523
   * @param $action
1524
   *  The action the link is about to carry out, either "flag" or "unflag".
1525
   * @param $entity_id
1526
   *  The ID of the object to flag.
1527
   * @param $variables = array()
1528
   *  An array of further variables to pass to theme('flag'). For the full list
1529
   *  of parameters, see flag.tpl.php. Of particular interest:
1530
   *  - after_flagging: Set to TRUE if this flag link is being displayed as the
1531
   *    result of a flagging action.
1532
   *  - errors: An array of error messages.
1533
   *
1534
   * @return
1535
   *  The HTML for the flag link.
1536
   */
1537
  function theme($action, $entity_id, $variables = array()) {
1538
    static $js_added = array();
1539
    global $user;
1540

    
1541
    $after_flagging = !empty($variables['after_flagging']);
1542

    
1543
    // If the flagging user is anonymous, set a boolean for the benefit of
1544
    // JavaScript code. Currently, only our "anti-crawlers" mechanism uses it.
1545
    if ($user->uid == 0 && !isset($js_added['anonymous'])) {
1546
      $js_added['anonymous'] = TRUE;
1547
      drupal_add_js(array('flag' => array('anonymous' => TRUE)), 'setting');
1548
    }
1549

    
1550
    // If the flagging user is anonymous and the page cache is enabled, we
1551
    // update the links through JavaScript.
1552
    if ($this->uses_anonymous_cookies() && !$after_flagging) {
1553
      if ($this->global) {
1554
        // In case of global flags, the JavaScript template is to contain
1555
        // the opposite of the current state.
1556
        $js_action = ($action == 'flag' ? 'unflag' : 'flag');
1557
      }
1558
      else {
1559
        // In case of non-global flags, we always show the "flag!" link,
1560
        // and then replace it with the "unflag!" link through JavaScript.
1561
        $js_action = 'unflag';
1562
        $action = 'flag';
1563
      }
1564
      if (!isset($js_added[$this->name . '_' . $entity_id])) {
1565
        $js_added[$this->name . '_' . $entity_id] = TRUE;
1566
        $js_template = theme($this->theme_suggestions(), array(
1567
          'flag' => $this,
1568
          'action' => $js_action,
1569
          'entity_id' => $entity_id,
1570
          'after_flagging' => $after_flagging,
1571
        ));
1572
        drupal_add_js(array('flag' => array('templates' => array($this->name . '_' . $entity_id => $js_template))), 'setting');
1573
      }
1574
    }
1575

    
1576
    return theme($this->theme_suggestions(), array(
1577
      'flag' => $this,
1578
      'action' => $action,
1579
      'entity_id' => $entity_id,
1580
    ) + $variables);
1581
  }
1582

    
1583
  /**
1584
   * Provides an array of possible themes to try for a given flag.
1585
   */
1586
  function theme_suggestions() {
1587
    $suggestions = array();
1588
    $suggestions[] = 'flag__' . $this->name;
1589
    $suggestions[] = 'flag__' . $this->link_type;
1590
    $suggestions[] = 'flag';
1591
    return $suggestions;
1592
  }
1593

    
1594
  /**
1595
   * A shortcut function to output the link URL.
1596
   */
1597
  function _flag_url($path, $fragment = NULL, $absolute = TRUE) {
1598
    return url($path, array('fragment' => $fragment, 'absolute' => $absolute));
1599
  }
1600
}
1601

    
1602
/**
1603
 * A dummy flag to be used where the real implementation can't be found.
1604
 */
1605
class flag_broken extends flag_flag {
1606
  function options_form(&$form) {
1607
    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');
1608
    $form = array();
1609
  }
1610
}