Projet

Général

Profil

Paste
Télécharger (31,1 ko) Statistiques
| Branche: | Révision:

root / htmltest / sites / all / modules / flag / includes / flag.admin.inc @ a5572547

1
<?php
2

    
3
/**
4
 * @file
5
 * Contains administrative pages for creating, editing, and deleting flags.
6
 */
7

    
8
/**
9
 * Flag administration page. Display a list of existing flags.
10
 */
11
function flag_admin_page() {
12
  $flags = flag_get_flags();
13
  $default_flags = flag_get_default_flags(TRUE);
14
  $flag_admin_listing = drupal_get_form('flag_admin_listing', $flags);
15
  return theme('flag_admin_page', array(
16
    'flags' => $flags,
17
    'default_flags' => $default_flags,
18
    'flag_admin_listing' => $flag_admin_listing,
19
  ));
20
}
21

    
22
/**
23
 * A form for ordering the weights of all the active flags in the system.
24
 */
25
function flag_admin_listing($form, &$form_state, $flags) {
26
  $form['#flags'] = $flags;
27
  $form['#tree'] = TRUE;
28

    
29
  foreach ($flags as $flag) {
30
    $form['flags'][$flag->name]['weight'] = array(
31
      '#type' => 'weight',
32
      '#delta' => count($flags) + 5,
33
      '#default_value' => $flag->weight,
34
      '#attributes' => array('class' => array('flag-weight')),
35
    );
36
  }
37

    
38
  $form['actions'] = array(
39
    '#type' => 'actions',
40
  );
41

    
42
  if (count($flags) == 1) {
43
    // Don't show weights with only one flag.
44
    unset($form['flags'][$flag->name]['weight']);
45
  }
46
  elseif (count($flags) > 1) {
47
    // Only show the form button if there are several flags.
48
    $form['actions']['submit'] = array(
49
      '#type' => 'submit',
50
      '#value' => t('Save flag order'),
51
    );
52
  }
53

    
54
  return $form;
55
}
56

    
57
/**
58
 * Submit handler for the flag_admin_listing form. Save flag weight ordering.
59
 */
60
function flag_admin_listing_submit($form, &$form_state) {
61
  foreach ($form['#flags'] as $flag) {
62
    if ($flag->weight != $form_state['values']['flags'][$flag->name]['weight']) {
63
      $flag->weight = $form_state['values']['flags'][$flag->name]['weight'];
64
      $flag->save();
65
    }
66
  }
67
}
68

    
69
/**
70
 * Theme the output of the normal, database flags into a table.
71
 */
72
function theme_flag_admin_listing($variables) {
73
  $form = $variables['form'];
74
  $flags = $form['#flags'];
75

    
76
  $output = '';
77

    
78
  foreach ($flags as $flag) {
79
    $ops = array(
80
      'flags_edit' =>  array('title' => t('edit'), 'href' => $flag->admin_path('edit')),
81
      'flags_fields' =>  array('title' => t('manage fields'), 'href' => $flag->admin_path('fields')),
82
      'flags_delete' =>  array('title' => t('delete'), 'href' => $flag->admin_path('delete')),
83
      'flags_export' =>  array('title' => t('export'), 'href' => $flag->admin_path('export')),
84
    );
85
    if (!module_exists('field_ui')) {
86
      unset($ops['flags_fields']);
87
    }
88
    $permission = "flag $flag->name";
89
    $roles = user_roles(FALSE, $permission);
90
    $row = array();
91
    $row[] = check_plain($flag->title) . ' <small>(' . t('Machine name: @name', array('@name' => $flag->name)) . ')</small>';
92
    if (count($flags) > 1) {
93
      $row[] = drupal_render($form['flags'][$flag->name]['weight']);
94
    }
95
    $row[] = $flag->entity_type;
96
    $row[] = empty($roles) ? '<em>' . t('No roles') . '</em>' : implode(', ', $roles);
97
    $row[] = $flag->types ? implode(', ', $flag->types) : '-';
98
    $row[] = $flag->global ? t('Yes') : t('No');
99
    $row[] = theme('links', array('links' => $ops));
100

    
101
    $rows[] = array(
102
      'data' => $row,
103
      'class' => array('draggable'),
104
    );
105
  }
106
  if (!$flags) {
107
    $rows[] = array(
108
      array('data' => t('No flags are currently defined.'), 'colspan' => 7),
109
    );
110
  }
111
  elseif (count($flags) > 1) {
112
    drupal_add_tabledrag('flag-admin-listing-table', 'order', 'sibling', 'flag-weight');
113
  }
114

    
115
  $header = array(t('Flag'));
116
  if (count($flags) > 1) {
117
    $header[] = t('Weight');
118
  }
119
  $header = array_merge($header, array(t('Flag type'), t('Roles'), t('Entity bundles'), t('Global?'), t('Operations')));
120
  $output .= theme('table', array(
121
    'header' => $header,
122
    'rows' => $rows,
123
    'attributes' => array('id' => 'flag-admin-listing-table'),
124
  ));
125
  $output .= drupal_render_children($form);
126

    
127
  return $output;
128
}
129

    
130
/**
131
 * Theme the list of disabled flags into a table.
132
 */
133
function theme_flag_admin_listing_disabled($variables) {
134
  $flags = $variables['flags'];
135
  $default_flags = $variables['default_flags'];
136
  $output = '';
137

    
138
  // Build a list of disabled, module-based flags.
139
  $rows = array();
140
  foreach ($default_flags as $name => $flag) {
141
    if (!isset($flags[$name])) {
142
      $ops = array();
143
      if (!$flag->is_compatible()) {
144
        $flag_updates_needed = TRUE;
145
        $ops['flags_update'] = array('title' => '<strong>' . t('update code') . '</strong>', 'href' => $flag->admin_path('update'), 'html' => TRUE);
146
      }
147
      else {
148
        $ops['flags_enable'] = array('title' => t('enable'), 'href' => $flag->admin_path('edit'));
149
      }
150
      // $flag->roles['flag'] not exist on older flags.
151
      $roles = array_flip(array_intersect(array_flip(user_roles()), !empty($flag->roles['flag']) ? $flag->roles['flag'] : array()));
152
      $rows[] = array(
153
        $flag->name,
154
        $flag->module,
155
        $flag->entity_type ? $flag->entity_type : t('Unknown'),
156
        theme('links', array('links' => $ops)),
157
      );
158
    }
159
  }
160

    
161
  if (isset($flag_updates_needed)) {
162
    drupal_set_message(t('Some flags provided by modules need to be updated to a new format before they can be used with this version of Flag. See the disabled flags for a list of flags that need updating.'), 'warning');
163
  }
164

    
165
  if (!empty($rows)) {
166
    $header = array(t('Disabled Flags'), t('Module'), t('Flag type'), t('Operations'));
167
    $output .= theme('table', array('header' => $header, 'rows' => $rows));
168
  }
169

    
170
  return $output;
171
}
172

    
173
/**
174
 * Theme the output for the main flag administration page.
175
 */
176
function theme_flag_admin_page($variables) {
177
  $flags = $variables['flags'];
178
  $default_flags = $variables['default_flags'];
179

    
180
  $output = '';
181

    
182
  $output .= drupal_render($variables['flag_admin_listing']);
183
  $output .= theme('flag_admin_listing_disabled', array('flags' => $flags, 'default_flags' => $default_flags));
184

    
185
  if (!module_exists('views')) {
186
    $output .= '<p>' . t('The <a href="@views-url">Views</a> module is not installed, or not enabled. It is recommended that you install the Views module to be able to easily produce lists of flagged content.', array('@views-url' => url('http://drupal.org/project/views'))) . '</p>';
187
  }
188
  else {
189
    $output .= '<p>';
190
    $output .= t('Lists of flagged content can be displayed using views. You can configure these in the <a href="@views-url">Views administration section</a>.', array('@views-url' => url('admin/structure/views')));
191
    if (flag_get_flag('bookmarks')) {
192
      $output .= ' ' . t('Flag module automatically provides a few <a href="@views-url">default views for the <em>bookmarks</em> flag</a>. You can use these as templates by cloning these views and then customizing as desired.', array('@views-url' => url('admin/structure/views', array('query' => array('tag' => 'flag')))));
193
    }
194
    $output .= ' ' . t('The <a href="@flag-handbook-url">Flag module handbook</a> contains extensive <a href="@customize-url">documentation on creating customized views</a> using flags.', array('@flag-handbook-url' => 'http://drupal.org/handbook/modules/flag', '@customize-url' => 'http://drupal.org/node/296954'));
195
    $output .= '</p>';
196
  }
197

    
198
  if (!module_exists('flag_actions')) {
199
    $output .= '<p>' . t('Flagging an item may trigger <em>actions</em>. However, you don\'t have the <em>Flag actions</em> module <a href="@modules-url">enabled</a>, so you won\'t be able to enjoy this feature.', array('@actions-url' => url(FLAG_ADMIN_PATH . '/actions'), '@modules-url' => url('admin/modules'))) . '</p>';
200
  }
201
  else {
202
    $output .= '<p>' . t('Flagging an item may trigger <a href="@actions-url">actions</a>.', array('@actions-url' => url(FLAG_ADMIN_PATH . '/actions'))) . '</p>';
203
  }
204

    
205
  if (!module_exists('rules')) {
206
    $output .= '<p>' . t('Flagging an item may trigger <em>rules</em>. However, you don\'t have the <a href="@rules-url">Rules</a> module enabled, so you won\'t be able to enjoy this feature. The Rules module is a more extensive solution than Flag actions.', array('@rules-url' => url('http://drupal.org/node/407070'))) . '</p>';
207
  }
208
  else {
209
    $output .= '<p>' . t('Flagging an item may trigger <a href="@rules-url">rules</a>.', array('@rules-url' => url('admin/config/workflow/rules'))) . '</p>';
210
  }
211

    
212
  $output .= '<p>' . t('To learn about the various ways to use flags, please check out the <a href="@handbook-url">Flag module handbook</a>.', array('@handbook-url' => 'http://drupal.org/handbook/modules/flag')) . '</p>';
213

    
214
  return $output;
215
}
216

    
217
/**
218
 * Menu callback for adding a new flag.
219
 *
220
 * @param $entity_type
221
 *  The entity type for the new flag, taken from the path argument. If not
222
 *  present (i.e., '/add'), a form showing all possible flag types is shown.
223
 *  Otherwise, this shows a form for adding af flag the given type.
224
 *
225
 * @see flag_add_form()
226
 * @see flag_form()
227
 */
228
function flag_add_page($entity_type = NULL) {
229
  if (isset($entity_type)) {
230
    $flag = flag_flag::factory_by_entity_type($entity_type);
231
    // Mark the flag as new.
232
    $flag->is_new = TRUE;
233
    $type_info = flag_fetch_definition($entity_type);
234
    drupal_set_title(t('Add new @type flag', array('@type' => $type_info['title'])));
235
    return drupal_get_form('flag_form', $flag);
236
  }
237

    
238
  drupal_set_title(t('Select flag type'));
239
  return drupal_get_form('flag_add_form');
240
}
241

    
242
/**
243
 * Present a form for creating a new flag, setting the type of flag.
244
 */
245
function flag_add_form($form, &$form_state) {
246
  $types = array();
247
  foreach (flag_fetch_definition() as $type => $info) {
248
    $types[$type] = $info['title'] . '<div class="description">' . $info['description'] . '</div>';
249
  }
250

    
251
  $form['type'] = array(
252
    '#type' => 'radios',
253
    '#title' => t('Flag type'),
254
    '#default_value' => 'node',
255
    '#description' => t('The type of object this flag will affect. This cannot be changed once the flag is created.'),
256
    '#required' => TRUE,
257
    '#options' => $types,
258
  );
259

    
260
  $form['actions'] = array(
261
    '#type' => 'actions',
262
  );
263

    
264
  $form['actions']['submit'] = array(
265
    '#type' => 'submit',
266
    '#value' => t('Continue'),
267
  );
268

    
269
  return $form;
270
}
271

    
272
function flag_add_form_validate($form, &$form_state) {
273
  $flag = flag_flag::factory_by_entity_type($form_state['values']['type']);
274
  if (get_class($flag) == 'flag_broken') {
275
    form_set_error('type', t("This flag type, %type, isn't valid.", array('%type' => $form_state['values']['type'])));
276
  }
277
}
278

    
279
function flag_add_form_submit($form, &$form_state) {
280
  $form_state['redirect'] = FLAG_ADMIN_PATH . '/add/' . $form_state['values']['type'];
281
}
282

    
283
/**
284
 * Add/Edit flag page.
285
 */
286
function flag_form($form, &$form_state, $flag) {
287
  $form['#flag'] = $flag;
288
  $form['#flag_name'] = $flag->name;
289

    
290
  $form['title'] = array(
291
    '#type' => 'textfield',
292
    '#title' => t('Title'),
293
    '#default_value' => $flag->title,
294
    '#description' => t('A short, descriptive title for this flag. It will be used in administrative interfaces to refer to this flag, and in page titles and menu items of some <a href="@insite-views-url">views</a> this module provides (theses are customizable, though). Some examples could be <em>Bookmarks</em>, <em>Favorites</em>, or <em>Offensive</em>.', array('@insite-views-url' => url('admin/structure/views'))),
295
    '#maxlength' => 255,
296
    '#required' => TRUE,
297
    '#weight' => -3,
298
  );
299

    
300
  $form['name'] = array(
301
    '#type' => 'machine_name',
302
    '#title' => t('Machine name'),
303
    '#default_value' => $flag->name,
304
    '#description' => t('The machine-name for this flag. It may be up to 32 characters long and may only contain lowercase letters, underscores, and numbers. It will be used in URLs and in all API calls.'),
305
    '#maxlength' => 32,
306
    '#weight' => -2,
307
    '#machine_name' => array(
308
      'exists' => 'flag_get_flag',
309
      'source' => array('title'),
310
    ),
311
  );
312

    
313
  $form['global'] = array(
314
    '#type' => 'checkbox',
315
    '#title' => t('Global flag'),
316
    '#default_value' => $flag->global,
317
    '#description' => t('If checked, flag is considered "global" and each entity is either flagged or not. If unchecked, each user has individual flags on entities.'),
318
    '#weight' => -1,
319
  );
320
  // Don't allow the 'global' checkbox to be changed when flaggings exist:
321
  // there are too many unpleasant consequences in either direction.
322
  // @todo: Allow this, but with a confirmation form, assuming anyone actually
323
  // needs this feature.
324
  if (!empty($flag->fid) && flag_get_flag_counts($flag->name)) {
325
    $form['global']['#disabled'] = TRUE;
326
    $form['global']['#description'] .= '<br />' . t('This setting cannot be changed when flaggings exist for this flag.');
327
  }
328

    
329
  $form['messages'] = array(
330
    '#type' => 'fieldset',
331
    '#title' => t('Messages'),
332
  );
333

    
334
  $form['messages']['flag_short'] = array(
335
    '#type' => 'textfield',
336
    '#title' => t('Flag link text'),
337
    '#default_value' => !empty($flag->flag_short) ? $flag->flag_short : t('Flag this item'),
338
    '#description' => t('The text for the "flag this" link for this flag.'),
339
    '#required' => TRUE,
340
  );
341

    
342
  $form['messages']['flag_long'] = array(
343
    '#type' => 'textfield',
344
    '#title' => t('Flag link description'),
345
    '#default_value' => $flag->flag_long,
346
    '#description' => t('The description of the "flag this" link. Usually displayed on mouseover.'),
347
  );
348

    
349
  $form['messages']['flag_message'] = array(
350
    '#type' => 'textfield',
351
    '#title' => t('Flagged message'),
352
    '#default_value' => $flag->flag_message,
353
    '#description' => t('Message displayed after flagging content. If JavaScript is enabled, it will be displayed below the link. If not, it will be displayed in the message area.'),
354
  );
355

    
356
  $form['messages']['unflag_short'] = array(
357
    '#type' => 'textfield',
358
    '#title' => t('Unflag link text'),
359
    '#default_value' => !empty($flag->unflag_short) ? $flag->unflag_short : t('Unflag this item'),
360
    '#description' => t('The text for the "unflag this" link for this flag.'),
361
    '#required' => TRUE,
362
  );
363

    
364
  $form['messages']['unflag_long'] = array(
365
    '#type' => 'textfield',
366
    '#title' => t('Unflag link description'),
367
    '#default_value' => $flag->unflag_long,
368
    '#description' => t('The description of the "unflag this" link. Usually displayed on mouseover.'),
369
  );
370

    
371
  $form['messages']['unflag_message'] = array(
372
    '#type' => 'textfield',
373
    '#title' => t('Unflagged message'),
374
    '#default_value' => $flag->unflag_message,
375
    '#description' => t('Message displayed after content has been unflagged. If JavaScript is enabled, it will be displayed below the link. If not, it will be displayed in the message area.'),
376
  );
377

    
378
  $form['messages']['tokens_help'] = array(
379
    '#title' => t('Token replacement'),
380
    '#type' => 'fieldset',
381
    '#description' =>
382
      '<p>' . t('The above six texts may contain any of the tokens listed below. For example, <em>"Flag link text"</em> could be entered as:') . '</p>' .
383
      theme('item_list', array(
384
        'items' => array(
385
          t('Add &lt;em&gt;[node:title]&lt;/em&gt; to your favorites'),
386
          t('Add this [node:type] to your favorites'),
387
          t('Vote for this proposal ([node:flag-vote-count] people have already done so)'),
388
        ),
389
        'attributes' => array('class' => 'token-examples'),
390
      )) .
391
      '<p>' . t('These tokens will be replaced with the appropriate fields from the node (or user, or comment).') . '</p>' .
392
      theme('flag_tokens_browser', array('types' => $flag->get_labels_token_types())),
393
    '#collapsible' => TRUE,
394
    '#collapsed' => TRUE,
395
  );
396

    
397
  $form['access'] = array(
398
    '#type' => 'fieldset',
399
    '#title' => t('Flag access'),
400
    '#tree' => FALSE,
401
    '#weight' => 10,
402
  );
403

    
404
  // Flag classes will want to override this form element.
405
  $form['access']['types'] = array(
406
    '#type' => 'checkboxes',
407
    '#title' => t('Flaggable types'),
408
    '#options' => array(),
409
    '#default_value' => $flag->types,
410
    '#description' => t('Check any sub-types that this flag may be used on.'),
411
    '#required' => TRUE,
412
    '#weight' => 10,
413
  );
414

    
415
  // Disabled access breaks checkboxes unless #value is hard coded.
416
  if (!empty($flag->locked['types'])) {
417
    $form['access']['types']['#value'] = $flag->types;
418
  }
419

    
420
  // Load the user permissions into the flag.
421
  if (isset($flag->fid)) {
422
    $flag->fetch_roles();
423
  }
424
  elseif (isset($flag->import_roles)) {
425
    // Convert the roles data from old API 2 flags that have been run through
426
    // the update system.
427
    // @see FlagUpdate_2::update()
428
    $flag->roles = $flag->import_roles;
429
  }
430
  else {
431
    // For new flags, provide a reasonable default value.
432
    $flag->roles = array(
433
      'flag' => array(DRUPAL_AUTHENTICATED_RID),
434
      'unflag' => array(DRUPAL_AUTHENTICATED_RID),
435
    );
436
  }
437

    
438
  $form['access']['roles'] = array(
439
    '#title' => t('Roles that may use this flag'),
440
    '#description' => t('Users may only unflag content if they have access to flag the content initially. Checking <em>authenticated user</em> will allow access for all logged-in users.'),
441
    '#theme' => 'flag_form_roles',
442
    '#theme_wrappers' => array('form_element'),
443
    '#weight' => -2,
444
    '#attached' => array(
445
      'js' => array(drupal_get_path('module', 'flag') . '/theme/flag-admin.js'),
446
      'css' => array(drupal_get_path('module', 'flag') . '/theme/flag-admin.css'),
447
    ),
448
  );
449
  if (module_exists('session_api')) {
450
    $form['access']['roles']['#description'] .= ' ' . t('Support for anonymous users is being provided by <a href="http://drupal.org/project/session_api">Session API</a>.');
451
  }
452
  else {
453
    $form['access']['roles']['#description'] .= ' ' . t('Anonymous users may flag content if the <a href="http://drupal.org/project/session_api">Session API</a> module is installed.');
454
  }
455

    
456
  $form['access']['roles']['flag'] = array(
457
    '#type' => 'checkboxes',
458
    '#options' => user_roles(!module_exists('session_api')),
459
    '#default_value' => $flag->roles['flag'],
460
    '#parents' => array('roles', 'flag'),
461
  );
462
  $form['access']['roles']['unflag'] = array(
463
    '#type' => 'checkboxes',
464
    '#options' => user_roles(!module_exists('session_api')),
465
    '#default_value' => $flag->roles['unflag'],
466
    '#parents' => array('roles', 'unflag'),
467
  );
468

    
469
  $form['access']['unflag_denied_text'] = array(
470
    '#type' => 'textfield',
471
    '#title' => t('Unflag not allowed text'),
472
    '#default_value' => $flag->unflag_denied_text,
473
    '#description' => t('If a user is allowed to flag but not unflag, this text will be displayed after flagging. Often this is the past-tense of the link text, such as "flagged".'),
474
    '#weight' => -1,
475
  );
476

    
477
  $form['display'] = array(
478
    '#type' => 'fieldset',
479
    '#title' => t('Display options'),
480
    '#description' => t('Flags are usually controlled through links that allow users to toggle their behavior. You can choose how users interact with flags by changing options here. It is legitimate to have none of the following checkboxes ticked, if, for some reason, you wish <a href="@placement-url">to place the the links on the page yourself</a>.', array('@placement-url' => 'http://drupal.org/node/295383')),
481
    '#tree' => FALSE,
482
    '#weight' => 20,
483
    '#after_build' => array('flag_link_type_options_states'),
484
  );
485

    
486
  $form['display']['link_type'] = array(
487
    '#type' => 'radios',
488
    '#title' => t('Link type'),
489
    '#options' => _flag_link_type_options(),
490
    '#after_build' => array('flag_check_link_types'),
491
    '#default_value' => $flag->link_type,
492
    // Give this a high weight so additions by the flag classes for entity-
493
    // specific options go above.
494
    '#weight' => 18,
495
    '#attached' => array(
496
      'js' => array(drupal_get_path('module', 'flag') . '/theme/flag-admin.js'),
497
    ),
498
    '#attributes' => array(
499
      'class' => array('flag-link-options'),
500
    ),
501
  );
502
  // Add the descriptions to each ratio button element. These attach to the
503
  // elements when FormAPI expands them.
504
  foreach (_flag_link_type_descriptions() as $key => $description) {
505
    $form['display']['link_type'][$key]['#description'] = $description;
506
  }
507

    
508
  $form['display']['link_options_intro'] = array(
509
    // This is a hack to allow a markup element to use FormAPI states.
510
    // @see http://www.bywombats.com/blog/06-25-2011/using-containers-states-enabled-markup-form-elements
511
    '#type' => 'container',
512
    '#children' => '<p id="link-options-intro">' . t('The selected link type may require these additional settings:') . '</p>',
513
    '#weight' => 20,
514
  );
515

    
516
  $form['display']['link_options_confirm'] = array(
517
    '#type' => 'fieldset',
518
    '#title' => t('Options for the "Confirmation form" link type'),
519
    // Any "link type" provider module must put its settings fields inside
520
    // a fieldset whose HTML ID is link-options-LINKTYPE, where LINKTYPE is
521
    // the machine-name of the link type. This is necessary for the
522
    // radiobutton's JavaScript dependency feature to work.
523
    '#id' => 'link-options-confirm',
524
    '#weight' => 21,
525
  );
526

    
527
  $form['display']['link_options_confirm']['flag_confirmation'] = array(
528
    '#type' => 'textfield',
529
    '#title' => t('Flag confirmation message'),
530
    '#default_value' => isset($flag->flag_confirmation) ? $flag->flag_confirmation : '',
531
    '#description' => t('Message displayed if the user has clicked the "flag this" link and confirmation is required. Usually presented in the form of a question such as, "Are you sure you want to flag this content?"'),
532
    // This will get changed to a state by flag_link_type_options_states().
533
    '#required' => TRUE,
534
  );
535

    
536
  $form['display']['link_options_confirm']['unflag_confirmation'] = array(
537
    '#type' => 'textfield',
538
    '#title' => t('Unflag confirmation message'),
539
    '#default_value' => isset($flag->unflag_confirmation) ? $flag->unflag_confirmation : '',
540
    '#description' => t('Message displayed if the user has clicked the "unflag this" link and confirmation is required. Usually presented in the form of a question such as, "Are you sure you want to unflag this content?"'),
541
    // This will get changed to a state by flag_link_type_options_states().
542
    '#required' => TRUE,
543
  );
544

    
545
  $form['actions'] = array(
546
    '#type' => 'actions',
547
  );
548

    
549
  $form['actions']['submit'] = array(
550
    '#type' => 'submit',
551
    '#value' => t('Save flag'),
552
    // We put this button on the form before calling $flag->options_form()
553
    // to give the flag handler a chance to remove it (e.g. flag_broken).
554
    '#weight' => 999,
555
  );
556

    
557
  // Add our process handler to disable access to locked properties.
558
  $form['#process'][] = 'flag_form_locked_process';
559

    
560
  // Allow the flag handler to make additions and changes to the form.
561
  // Note that the flag_broken handler will completely empty the form array!
562
  $flag->options_form($form);
563

    
564
  return $form;
565
}
566

    
567
/**
568
 * FormAPI after_build function to set states on link type options fieldsets.
569
 *
570
 * We do this in an after build so we handle further link types fieldsets from
571
 * other modules that provide link types.
572
 *
573
 * This expects a link type's fieldset to be $form['display'][link_options_TYPE]
574
 * so that can be matched up with the radio button value.
575
 */
576
function flag_link_type_options_states($element) {
577
  $intro_element_values_array = array();
578
  foreach (element_children($element) as $key) {
579
    if (isset($element[$key]['#type']) && $element[$key]['#type'] == 'fieldset' && substr($key, 0, 12) == 'link_options') {
580
      // Trim the radio value from the fieldset key. This assumed the fieldset
581
      // key is 'link_options_TYPE'.
582
      $radio_value = substr($key, 13);
583
      $element[$key]['#states'] = array(
584
        'visible' => array(
585
          ':input[name="link_type"]' => array('value' => $radio_value),
586
        ),
587
      );
588

    
589
      // If an element in a link type options fieldset is required, then we
590
      // remove this, as this would break the form, by demanding the user
591
      // enter a value for a form element they possibly can't see!
592
      // Instead, we set the required property as a state.
593
      foreach (element_children($element[$key]) as $child_key) {
594
        if (!empty($element[$key][$child_key]['#required'])) {
595
          $element[$key][$child_key]['#required'] = FALSE;
596
          $element[$key][$child_key]['#states']['required'] = array(
597
            ':input[name="link_type"]' => array('value' => $radio_value),
598
          );
599
        }
600
      }
601

    
602
      // Gather up the radio values for the format we need for a multiple
603
      // value state.
604
      $intro_element_values_array[] = array('value' => $radio_value);
605
    }
606
  }
607

    
608
  $element['link_options_intro']['#states'] = array(
609
    'visible' => array(
610
      ':input[name="link_type"]' => $intro_element_values_array,
611
    ),
612
  );
613

    
614
  return $element;
615
}
616

    
617
/**
618
 * Form process handler for locking flag properties.
619
 *
620
 * Flags defined in code may define an array of properties in $flag->locked that
621
 * are to be locked and may not be edited by the user.
622
 */
623
function flag_form_locked_process($element, &$form_state, $form) {
624
  $flag = $form['#flag'];
625

    
626
  // Disable access to a form element whose name matches a locked flag property.
627
  if (isset($element['#name']) && !empty($flag->locked[$element['#name']])) {
628
    $element['#access'] = FALSE;
629
  }
630

    
631
  // Recurse into the form array.
632
  foreach (element_children($element) as $key) {
633
    // Workaround for Core inconvenience: setting #process here prevents an
634
    // element's essential #process handlers from its hook_element_info()
635
    // definition from being set in form_builder().
636
    // @see http://drupal.org/node/1779496
637
    if (isset($element[$key]['#type']) && ($info = element_info($element[$key]['#type']))) {
638
      if (isset($info['#process'])) {
639
        $element[$key]['#process'] = $info['#process'];
640
      }
641
    }
642

    
643
    $element[$key]['#process'][] = 'flag_form_locked_process';
644
  }
645

    
646
  return $element;
647
}
648

    
649
/**
650
 * Add/Edit flag form validate.
651
 */
652
function flag_form_validate($form, &$form_state) {
653
  $form_state['values']['title'] = trim($form_state['values']['title']);
654
  $form_values = $form_state['values'];
655

    
656
  $flag = $form['#flag'];
657
  $flag->form_input($form_values);
658
  $errors = $flag->validate();
659
  foreach ($errors as $field => $field_errors) {
660
    foreach ($field_errors as $error) {
661
      form_set_error($field, $error['message']);
662
    }
663
  }
664
}
665

    
666
/**
667
 * Add/Edit flag form submit.
668
 */
669
function flag_form_submit($form, &$form_state) {
670
  $flag = $form['#flag'];
671

    
672
  $form_state['values']['title'] = trim($form_state['values']['title']);
673
  $flag->form_input($form_state['values']);
674

    
675
  $flag->save();
676
  $flag->enable();
677
  drupal_set_message(t('Flag @title has been saved.', array('@title' => $flag->get_title())));
678
  // We clear caches more vigorously if the flag was new.
679
  _flag_clear_cache($flag->entity_type, !empty($flag->is_new));
680

    
681
  // Save permissions.
682
  // This needs to be done after the flag cache has been cleared, so that
683
  // the new permissions are picked up by hook_permission().
684
  // This may need to move to the flag class when we implement extra permissions
685
  // for different flag types: http://drupal.org/node/879988
686

    
687
  // If the flag machine name as changed, clean up all the obsolete permissions.
688
  if ($flag->name != $form['#flag_name']) {
689
    $old_name = $form['#flag_name'];
690
    $permissions = array("flag $old_name", "unflag $old_name");
691
    foreach (array_keys(user_roles()) as $rid) {
692
      user_role_revoke_permissions($rid, $permissions);
693
    }
694
  }
695

    
696
  foreach (array_keys(user_roles(!module_exists('session_api'))) as $rid) {
697
    // Create an array of permissions, based on the checkboxes element name.
698
    $permissions = array(
699
      "flag $flag->name" => $flag->roles['flag'][$rid],
700
      "unflag $flag->name" => $flag->roles['unflag'][$rid],
701
    );
702
    user_role_change_permissions($rid, $permissions);
703
  }
704
  // @todo: when we add database caching for flags we'll have to clear the
705
  // cache again here.
706

    
707
  $form_state['redirect'] = FLAG_ADMIN_PATH;
708
}
709

    
710
/**
711
 * Output the access options for roles in a table.
712
 */
713
function theme_flag_form_roles($variables) {
714
  $element = $variables['element'];
715

    
716
  $header = array(
717
    array('class' => array('checkbox'), 'data' => t('Flag')),
718
    array('class' => array('checkbox'), 'data' => t('Unflag')),
719
    t('Role'),
720
  );
721
  $rows = array();
722
  foreach (element_children($element['flag']) as $role) {
723
    $row = array();
724
    $role_name = $element['flag'][$role]['#title'];
725
    unset($element['flag'][$role]['#title']);
726
    unset($element['unflag'][$role]['#title']);
727
    $element['flag'][$role]['#attributes']['class'] = array('flag-access');
728
    $element['unflag'][$role]['#attributes']['class'] = array('unflag-access');
729
    $row[] = array('class' => array('checkbox'), 'data' => drupal_render($element['flag'][$role]));
730
    $row[] = array('class' => array('checkbox'), 'data' => drupal_render($element['unflag'][$role]));
731
    $row[] = $role_name;
732
    $rows[] = $row;
733
  }
734

    
735
  return theme('table', array(
736
    'header' => $header,
737
    'rows' => $rows,
738
    'attributes' => array(
739
      'class' => array('flag-admin-table'),
740
      'id' => 'flag-roles',
741
    ),
742
  ));
743
}
744

    
745
/**
746
 * Delete flag page.
747
 */
748
function flag_delete_confirm($form, &$form_state, $flag) {
749
  $form['#flag'] = $flag;
750

    
751
  return confirm_form($form,
752
    t('Are you sure you want to delete %title?', array('%title' => $flag->get_title())),
753
    !empty($_GET['destination']) ? $_GET['destination'] : FLAG_ADMIN_PATH,
754
    isset($flag->module) ? t('This flag is provided by the %module module. It will lose any customizations and be disabled.', array('%module' => $flag->module)) : t('This action cannot be undone.'),
755
    t('Delete'), t('Cancel')
756
  );
757
}
758

    
759
function flag_delete_confirm_submit($form, &$form_state) {
760
  $flag = $form['#flag'];
761
  if ($form_state['values']['confirm']) {
762
    $flag->delete();
763
    $flag->disable();
764
    _flag_clear_cache($flag->entity_type, TRUE);
765
  }
766
  drupal_set_message(t('Flag @name has been deleted.', array('@name' => $flag->get_title())));
767
  $form_state['redirect'] = FLAG_ADMIN_PATH;
768
}
769

    
770
/**
771
 * FormAPI after_build function to check that the link type exists.
772
 */
773
function flag_check_link_types($element) {
774
  $link_types = flag_get_link_types();
775
  if (!isset($link_types[$element['#value']])) {
776
    drupal_set_message(t('This flag uses a link type of %type, which does not exist.', array('%type' => $element['#value'])), 'error');
777
  }
778
  return $element;
779
}
780

    
781
/**
782
 * Clears various caches when one or more flags are modified.
783
 *
784
 * @param $entity_types
785
 *  The entity types for the flags. May be a single value or an array.
786
 * @param $is_insert_or_delete
787
 *  Whether the modified flag is being inserted (saved for the first time) or
788
 *  deleted. This results in a more vigorous clearing of caches. In
789
 *  particular, when no flags exist yet, no Field admin UI paths exist and these
790
 *  need to be created.
791
 */
792
function _flag_clear_cache($entity_types, $is_insert_or_delete = FALSE) {
793
  if (!is_array($entity_types)) {
794
    $entity_types = array($entity_types);
795
  }
796

    
797
  // Reset our flags cache, thereby making the following code aware of the
798
  // modifications.
799
  drupal_static_reset('flag_get_flags');
800

    
801
  if ($is_insert_or_delete) {
802
    // A new or deleted flag means we are changing bundles on the Flagging
803
    // entity, and thus need to clear the entity info cache.
804
    entity_info_cache_clear();
805
  }
806

    
807
  // Clear FieldAPI's field_extra cache, so our changes to pseudofields are
808
  // noticed. It's rather too much effort to both a) check whether the
809
  // pseudofield setting has changed either way, and b) specifically clear just
810
  // the bundles that are (or were!!) affected, so we just clear for all bundles
811
  // on our entity type regardlesss.
812
  foreach ($entity_types as $entity_type) {
813
    cache_clear_all("field_info:bundle_extra:$entity_type:", 'cache_field', TRUE);
814
  }
815

    
816
  if (module_exists('views')) {
817
    views_invalidate_cache();
818
  }
819

    
820
  // The title of a flag may appear in the menu (indirectly, via our "default
821
  // views"), so we need to clear the menu cache. This call also clears the
822
  // page cache, which is desirable too because the flag labels may have
823
  // changed.
824
  menu_rebuild();
825
}