Projet

Général

Profil

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

root / drupal7 / sites / all / modules / flag / includes / flag.admin.inc @ 76e2e7c3

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(
120
    t('Flag type'),
121
    t('Roles'),
122
    t('Entity bundles'),
123
    t('Global?'),
124
    t('Operations'),
125
  ));
126
  $output .= theme('table', array(
127
    'header' => $header,
128
    'rows' => $rows,
129
    'attributes' => array('id' => 'flag-admin-listing-table'),
130
  ));
131
  $output .= drupal_render_children($form);
132

    
133
  return $output;
134
}
135

    
136
/**
137
 * Theme the list of disabled flags into a table.
138
 */
139
function theme_flag_admin_listing_disabled($variables) {
140
  $flags = $variables['flags'];
141
  $default_flags = $variables['default_flags'];
142
  $output = '';
143

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

    
171
  if (isset($flag_updates_needed)) {
172
    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');
173
  }
174

    
175
  if (!empty($rows)) {
176
    $header = array(
177
      t('Disabled Flags'),
178
      t('Module'),
179
      t('Flag type'),
180
      t('Operations'),
181
    );
182
    $output .= theme('table', array('header' => $header, 'rows' => $rows));
183
  }
184

    
185
  return $output;
186
}
187

    
188
/**
189
 * Theme the output for the main flag administration page.
190
 */
191
function theme_flag_admin_page($variables) {
192
  $flags = $variables['flags'];
193
  $default_flags = $variables['default_flags'];
194

    
195
  $output = '';
196

    
197
  $output .= drupal_render($variables['flag_admin_listing']);
198
  $output .= theme('flag_admin_listing_disabled', array('flags' => $flags, 'default_flags' => $default_flags));
199

    
200
  if (!module_exists('views')) {
201
    $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>';
202
  }
203
  else {
204
    $output .= '<p>';
205
    $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')));
206
    if (flag_get_flag('bookmarks')) {
207
      $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')))));
208
    }
209
    $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'));
210
    $output .= '</p>';
211
  }
212

    
213
  if (!module_exists('flag_actions')) {
214
    $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>';
215
  }
216
  else {
217
    $output .= '<p>' . t('Flagging an item may trigger <a href="@actions-url">actions</a>.', array('@actions-url' => url(FLAG_ADMIN_PATH . '/actions'))) . '</p>';
218
  }
219

    
220
  if (!module_exists('rules')) {
221
    $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>';
222
  }
223
  else {
224
    $output .= '<p>' . t('Flagging an item may trigger <a href="@rules-url">rules</a>.', array('@rules-url' => url('admin/config/workflow/rules'))) . '</p>';
225
  }
226

    
227
  $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>';
228

    
229
  return $output;
230
}
231

    
232
/**
233
 * Menu callback for adding a new flag.
234
 *
235
 * @param $entity_type
236
 *  The entity type for the new flag, taken from the path argument. If not
237
 *  present (i.e., '/add'), a form showing all possible flag types is shown.
238
 *  Otherwise, this shows a form for adding af flag the given type.
239
 *
240
 * @see flag_add_form()
241
 * @see flag_form()
242
 */
243
function flag_add_page($entity_type = NULL) {
244
  if (isset($entity_type)) {
245
    $flag = flag_flag::factory_by_entity_type($entity_type);
246
    // Mark the flag as new.
247
    $flag->is_new = TRUE;
248
    $type_info = flag_fetch_definition($entity_type);
249
    drupal_set_title(t('Add new @type flag', array('@type' => $type_info['title'])));
250
    return drupal_get_form('flag_form', $flag);
251
  }
252

    
253
  drupal_set_title(t('Select flag type'));
254
  return drupal_get_form('flag_add_form');
255
}
256

    
257
/**
258
 * Present a form for creating a new flag, setting the type of flag.
259
 */
260
function flag_add_form($form, &$form_state) {
261
  $types = array();
262
  foreach (flag_fetch_definition() as $type => $info) {
263
    $types[$type] = $info['title'] . '<div class="description">' . $info['description'] . '</div>';
264
  }
265

    
266
  $form['type'] = array(
267
    '#type' => 'radios',
268
    '#title' => t('Flag type'),
269
    '#default_value' => 'node',
270
    '#description' => t('The type of object this flag will affect. This cannot be changed once the flag is created.'),
271
    '#required' => TRUE,
272
    '#options' => $types,
273
  );
274

    
275
  $form['actions'] = array(
276
    '#type' => 'actions',
277
  );
278

    
279
  $form['actions']['submit'] = array(
280
    '#type' => 'submit',
281
    '#value' => t('Continue'),
282
  );
283

    
284
  return $form;
285
}
286

    
287
function flag_add_form_validate($form, &$form_state) {
288
  $flag = flag_flag::factory_by_entity_type($form_state['values']['type']);
289
  if (get_class($flag) == 'flag_broken') {
290
    form_set_error('type', t("This flag type, %type, isn't valid.", array('%type' => $form_state['values']['type'])));
291
  }
292
}
293

    
294
function flag_add_form_submit($form, &$form_state) {
295
  $form_state['redirect'] = FLAG_ADMIN_PATH . '/add/' . $form_state['values']['type'];
296
}
297

    
298
/**
299
 * Add/Edit flag page.
300
 */
301
function flag_form($form, &$form_state, $flag) {
302
  $form['#flag'] = $flag;
303
  $form['#flag_name'] = $flag->name;
304

    
305
  $form['title'] = array(
306
    '#type' => 'textfield',
307
    '#title' => t('Title'),
308
    '#default_value' => $flag->title,
309
    '#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'))),
310
    '#maxlength' => 255,
311
    '#required' => TRUE,
312
    '#weight' => -3,
313
  );
314

    
315
  $form['name'] = array(
316
    '#type' => 'machine_name',
317
    '#title' => t('Machine name'),
318
    '#default_value' => $flag->name,
319
    '#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.'),
320
    '#maxlength' => 32,
321
    '#weight' => -2,
322
    '#machine_name' => array(
323
      'exists' => 'flag_get_flag',
324
      'source' => array('title'),
325
    ),
326
  );
327

    
328
  $form['global'] = array(
329
    '#type' => 'checkbox',
330
    '#title' => t('Global flag'),
331
    '#default_value' => $flag->global,
332
    '#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.'),
333
    '#weight' => -1,
334
  );
335
  // Don't allow the 'global' checkbox to be changed when flaggings exist:
336
  // there are too many unpleasant consequences in either direction.
337
  // @todo: Allow this, but with a confirmation form, assuming anyone actually
338
  // needs this feature.
339
  if (!empty($flag->fid) && flag_get_flag_counts($flag->name)) {
340
    $form['global']['#disabled'] = TRUE;
341
    $form['global']['#description'] .= '<br />' . t('This setting cannot be changed when flaggings exist for this flag.');
342
  }
343

    
344
  $form['messages'] = array(
345
    '#type' => 'fieldset',
346
    '#title' => t('Messages'),
347
  );
348

    
349
  $form['messages']['flag_short'] = array(
350
    '#type' => 'textfield',
351
    '#title' => t('Flag link text'),
352
    '#default_value' => !empty($flag->flag_short) ? $flag->flag_short : t('Flag this item'),
353
    '#description' => t('The text for the "flag this" link for this flag.'),
354
    '#required' => TRUE,
355
  );
356

    
357
  $form['messages']['flag_long'] = array(
358
    '#type' => 'textfield',
359
    '#title' => t('Flag link description'),
360
    '#default_value' => $flag->flag_long,
361
    '#description' => t('The description of the "flag this" link. Usually displayed on mouseover.'),
362
  );
363

    
364
  $form['messages']['flag_message'] = array(
365
    '#type' => 'textfield',
366
    '#title' => t('Flagged message'),
367
    '#default_value' => $flag->flag_message,
368
    '#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.'),
369
  );
370

    
371
  $form['messages']['unflag_short'] = array(
372
    '#type' => 'textfield',
373
    '#title' => t('Unflag link text'),
374
    '#default_value' => !empty($flag->unflag_short) ? $flag->unflag_short : t('Unflag this item'),
375
    '#description' => t('The text for the "unflag this" link for this flag.'),
376
    '#required' => TRUE,
377
  );
378

    
379
  $form['messages']['unflag_long'] = array(
380
    '#type' => 'textfield',
381
    '#title' => t('Unflag link description'),
382
    '#default_value' => $flag->unflag_long,
383
    '#description' => t('The description of the "unflag this" link. Usually displayed on mouseover.'),
384
  );
385

    
386
  $form['messages']['unflag_message'] = array(
387
    '#type' => 'textfield',
388
    '#title' => t('Unflagged message'),
389
    '#default_value' => $flag->unflag_message,
390
    '#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.'),
391
  );
392

    
393
  $form['messages']['tokens_help'] = array(
394
    '#title' => t('Token replacement'),
395
    '#type' => 'fieldset',
396
    '#description' =>
397
    '<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>' .
398
    theme('item_list', array(
399
      'items' => array(
400
        t('Add &lt;em&gt;[node:title]&lt;/em&gt; to your favorites'),
401
        t('Add this [node:type] to your favorites'),
402
        t('Vote for this proposal ([node:flag-vote-count] people have already done so)'),
403
      ),
404
      'attributes' => array('class' => 'token-examples'),
405
    )) .
406
    '<p>' . t('These tokens will be replaced with the appropriate fields from the node (or user, or comment).') . '</p>' .
407
    theme('flag_tokens_browser', array('types' => $flag->get_labels_token_types())),
408
    '#collapsible' => TRUE,
409
    '#collapsed' => TRUE,
410
  );
411

    
412
  $form['access'] = array(
413
    '#type' => 'fieldset',
414
    '#title' => t('Flag access'),
415
    '#tree' => FALSE,
416
    '#weight' => 10,
417
  );
418

    
419
  // Flag classes will want to override this form element.
420
  $form['access']['types'] = array(
421
    '#type' => 'checkboxes',
422
    '#title' => t('Flaggable types'),
423
    '#options' => array(),
424
    '#default_value' => $flag->types,
425
    '#description' => t('Check any sub-types that this flag may be used on.'),
426
    '#required' => TRUE,
427
    '#weight' => 10,
428
  );
429

    
430
  // Disabled access breaks checkboxes unless #value is hard coded.
431
  if (!empty($flag->locked['types'])) {
432
    $form['access']['types']['#value'] = $flag->types;
433
  }
434

    
435
  // Load the user permissions into the flag.
436
  if (isset($flag->fid)) {
437
    $flag->fetch_roles();
438
  }
439
  elseif (isset($flag->import_roles)) {
440
    // Convert the roles data from old API 2 flags that have been run through
441
    // the update system.
442
    // @see FlagUpdate_2::update()
443
    $flag->roles = $flag->import_roles;
444
  }
445
  else {
446
    // For new flags, provide a reasonable default value.
447
    $flag->roles = array(
448
      'flag' => array(DRUPAL_AUTHENTICATED_RID),
449
      'unflag' => array(DRUPAL_AUTHENTICATED_RID),
450
    );
451
  }
452

    
453
  $form['access']['roles'] = array(
454
    '#title' => t('Roles that may use this flag'),
455
    '#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.'),
456
    '#theme' => 'flag_form_roles',
457
    '#theme_wrappers' => array('form_element'),
458
    '#weight' => -2,
459
    '#attached' => array(
460
      'js' => array(drupal_get_path('module', 'flag') . '/theme/flag-admin.js'),
461
      'css' => array(drupal_get_path('module', 'flag') . '/theme/flag-admin.css'),
462
    ),
463
  );
464
  if (module_exists('session_api')) {
465
    $form['access']['roles']['#description'] .= ' ' . t('Support for anonymous users is being provided by <a href="http://drupal.org/project/session_api">Session API</a>.');
466
  }
467
  else {
468
    $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.');
469
  }
470

    
471
  $form['access']['roles']['flag'] = array(
472
    '#type' => 'checkboxes',
473
    '#options' => user_roles(!module_exists('session_api')),
474
    '#default_value' => $flag->roles['flag'],
475
    '#parents' => array('roles', 'flag'),
476
  );
477
  $form['access']['roles']['unflag'] = array(
478
    '#type' => 'checkboxes',
479
    '#options' => user_roles(!module_exists('session_api')),
480
    '#default_value' => $flag->roles['unflag'],
481
    '#parents' => array('roles', 'unflag'),
482
  );
483

    
484
  $form['access']['unflag_denied_text'] = array(
485
    '#type' => 'textfield',
486
    '#title' => t('Unflag not allowed text'),
487
    '#default_value' => $flag->unflag_denied_text,
488
    '#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".'),
489
    '#weight' => -1,
490
  );
491

    
492
  $form['display'] = array(
493
    '#type' => 'fieldset',
494
    '#title' => t('Display options'),
495
    '#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')),
496
    '#tree' => FALSE,
497
    '#weight' => 20,
498
    '#after_build' => array('flag_link_type_options_states'),
499
  );
500

    
501
  $form['display']['link_type'] = array(
502
    '#type' => 'radios',
503
    '#title' => t('Link type'),
504
    '#options' => _flag_link_type_options(),
505
    '#after_build' => array('flag_check_link_types'),
506
    '#default_value' => $flag->link_type,
507
    // Give this a high weight so additions by the flag classes for entity-
508
    // specific options go above.
509
    '#weight' => 18,
510
    '#attached' => array(
511
      'js' => array(drupal_get_path('module', 'flag') . '/theme/flag-admin.js'),
512
    ),
513
    '#attributes' => array(
514
      'class' => array('flag-link-options'),
515
    ),
516
  );
517
  // Add the descriptions to each ratio button element. These attach to the
518
  // elements when FormAPI expands them.
519
  foreach (_flag_link_type_descriptions() as $key => $description) {
520
    $form['display']['link_type'][$key]['#description'] = $description;
521
  }
522

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

    
531
  $form['display']['link_options_confirm'] = array(
532
    '#type' => 'fieldset',
533
    '#title' => t('Options for the "Confirmation form" link type'),
534
    // Any "link type" provider module must put its settings fields inside
535
    // a fieldset whose HTML ID is link-options-LINKTYPE, where LINKTYPE is
536
    // the machine-name of the link type. This is necessary for the
537
    // radiobutton's JavaScript dependency feature to work.
538
    '#id' => 'link-options-confirm',
539
    '#weight' => 21,
540
  );
541

    
542
  $form['display']['link_options_confirm']['flag_confirmation'] = array(
543
    '#type' => 'textfield',
544
    '#title' => t('Flag confirmation message'),
545
    '#default_value' => isset($flag->flag_confirmation) ? $flag->flag_confirmation : '',
546
    '#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?"'),
547
    // This will get changed to a state by flag_link_type_options_states().
548
    '#required' => TRUE,
549
  );
550

    
551
  $form['display']['link_options_confirm']['unflag_confirmation'] = array(
552
    '#type' => 'textfield',
553
    '#title' => t('Unflag confirmation message'),
554
    '#default_value' => isset($flag->unflag_confirmation) ? $flag->unflag_confirmation : '',
555
    '#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?"'),
556
    // This will get changed to a state by flag_link_type_options_states().
557
    '#required' => TRUE,
558
  );
559

    
560
  $form['actions'] = array(
561
    '#type' => 'actions',
562
  );
563

    
564
  $form['actions']['submit'] = array(
565
    '#type' => 'submit',
566
    '#value' => t('Save flag'),
567
    // We put this button on the form before calling $flag->options_form()
568
    // to give the flag handler a chance to remove it (e.g. flag_broken).
569
    '#weight' => 999,
570
  );
571

    
572
  // Add our process handler to disable access to locked properties.
573
  $form['#process'][] = 'flag_form_locked_process';
574

    
575
  // Allow the flag handler to make additions and changes to the form.
576
  // Note that the flag_broken handler will completely empty the form array!
577
  $flag->options_form($form);
578

    
579
  return $form;
580
}
581

    
582
/**
583
 * FormAPI after_build function to set states on link type options fieldsets.
584
 *
585
 * We do this in an after build so we handle further link types fieldsets from
586
 * other modules that provide link types.
587
 *
588
 * This expects a link type's fieldset to be $form['display'][link_options_TYPE]
589
 * so that can be matched up with the radio button value.
590
 */
591
function flag_link_type_options_states($element) {
592
  $intro_element_values_array = array();
593
  foreach (element_children($element) as $key) {
594
    if (isset($element[$key]['#type']) && $element[$key]['#type'] == 'fieldset' && substr($key, 0, 12) == 'link_options') {
595
      // Trim the radio value from the fieldset key. This assumed the fieldset
596
      // key is 'link_options_TYPE'.
597
      $radio_value = substr($key, 13);
598
      $element[$key]['#states'] = array(
599
        'visible' => array(
600
          ':input[name="link_type"]' => array('value' => $radio_value),
601
        ),
602
      );
603

    
604
      // If an element in a link type options fieldset is required, then we
605
      // remove this, as this would break the form, by demanding the user
606
      // enter a value for a form element they possibly can't see!
607
      // Instead, we set the required property as a state.
608
      foreach (element_children($element[$key]) as $child_key) {
609
        if (!empty($element[$key][$child_key]['#required'])) {
610
          $element[$key][$child_key]['#required'] = FALSE;
611
          $element[$key][$child_key]['#states']['required'] = array(
612
            ':input[name="link_type"]' => array('value' => $radio_value),
613
          );
614
        }
615
      }
616

    
617
      // Gather up the radio values for the format we need for a multiple
618
      // value state.
619
      $intro_element_values_array[] = array('value' => $radio_value);
620
    }
621
  }
622

    
623
  $element['link_options_intro']['#states'] = array(
624
    'visible' => array(
625
      ':input[name="link_type"]' => $intro_element_values_array,
626
    ),
627
  );
628

    
629
  return $element;
630
}
631

    
632
/**
633
 * Form process handler for locking flag properties.
634
 *
635
 * Flags defined in code may define an array of properties in $flag->locked that
636
 * are to be locked and may not be edited by the user.
637
 */
638
function flag_form_locked_process($element, &$form_state, $form) {
639
  $flag = $form['#flag'];
640

    
641
  // Disable access to a form element whose name matches a locked flag property.
642
  if (isset($element['#name']) && !empty($flag->locked[$element['#name']])) {
643
    $element['#access'] = FALSE;
644
  }
645

    
646
  // Recurse into the form array.
647
  foreach (element_children($element) as $key) {
648
    // Workaround for Core inconvenience: setting #process here prevents an
649
    // element's essential #process handlers from its hook_element_info()
650
    // definition from being set in form_builder().
651
    // @see http://drupal.org/node/1779496
652
    if (isset($element[$key]['#type']) && ($info = element_info($element[$key]['#type']))) {
653
      if (isset($info['#process'])) {
654
        $element[$key]['#process'] = $info['#process'];
655
      }
656
    }
657

    
658
    $element[$key]['#process'][] = 'flag_form_locked_process';
659
  }
660

    
661
  return $element;
662
}
663

    
664
/**
665
 * Add/Edit flag form validate.
666
 */
667
function flag_form_validate($form, &$form_state) {
668
  $form_state['values']['title'] = trim($form_state['values']['title']);
669
  $form_values = $form_state['values'];
670

    
671
  $flag = $form['#flag'];
672
  $flag->form_input($form_values);
673
  $errors = $flag->validate();
674
  foreach ($errors as $field => $field_errors) {
675
    foreach ($field_errors as $error) {
676
      form_set_error($field, $error['message']);
677
    }
678
  }
679
}
680

    
681
/**
682
 * Add/Edit flag form submit.
683
 */
684
function flag_form_submit($form, &$form_state) {
685
  $flag = $form['#flag'];
686

    
687
  $form_state['values']['title'] = trim($form_state['values']['title']);
688
  $flag->form_input($form_state['values']);
689

    
690
  $flag->save();
691
  $flag->enable();
692
  drupal_set_message(t('Flag @title has been saved.', array('@title' => $flag->get_title())));
693
  // We clear caches more vigorously if the flag was new.
694
  _flag_clear_cache($flag->entity_type, !empty($flag->is_new));
695

    
696
  // Save permissions.
697
  // This needs to be done after the flag cache has been cleared, so that
698
  // the new permissions are picked up by hook_permission().
699
  // This may need to move to the flag class when we implement extra permissions
700
  // for different flag types: http://drupal.org/node/879988
701

    
702
  // If the flag machine name as changed, clean up all the obsolete permissions.
703
  if ($flag->name != $form['#flag_name']) {
704
    $old_name = $form['#flag_name'];
705
    $permissions = array("flag $old_name", "unflag $old_name");
706
    foreach (array_keys(user_roles()) as $rid) {
707
      user_role_revoke_permissions($rid, $permissions);
708
    }
709
  }
710

    
711
  foreach (array_keys(user_roles(!module_exists('session_api'))) as $rid) {
712
    // Create an array of permissions, based on the checkboxes element name.
713
    $permissions = array(
714
      "flag $flag->name" => $flag->roles['flag'][$rid],
715
      "unflag $flag->name" => $flag->roles['unflag'][$rid],
716
    );
717
    user_role_change_permissions($rid, $permissions);
718
  }
719
  // @todo: when we add database caching for flags we'll have to clear the
720
  // cache again here.
721

    
722
  $form_state['redirect'] = FLAG_ADMIN_PATH;
723
}
724

    
725
/**
726
 * Output the access options for roles in a table.
727
 */
728
function theme_flag_form_roles($variables) {
729
  $element = $variables['element'];
730

    
731
  $header = array(
732
    array('class' => array('checkbox'), 'data' => t('Flag')),
733
    array('class' => array('checkbox'), 'data' => t('Unflag')),
734
    t('Role'),
735
  );
736
  $rows = array();
737
  foreach (element_children($element['flag']) as $role) {
738
    $row = array();
739
    $role_name = $element['flag'][$role]['#title'];
740
    unset($element['flag'][$role]['#title']);
741
    unset($element['unflag'][$role]['#title']);
742
    $element['flag'][$role]['#attributes']['class'] = array('flag-access');
743
    $element['unflag'][$role]['#attributes']['class'] = array('unflag-access');
744
    $row[] = array('class' => array('checkbox'), 'data' => drupal_render($element['flag'][$role]));
745
    $row[] = array('class' => array('checkbox'), 'data' => drupal_render($element['unflag'][$role]));
746
    $row[] = $role_name;
747
    $rows[] = $row;
748
  }
749

    
750
  return theme('table', array(
751
    'header' => $header,
752
    'rows' => $rows,
753
    'attributes' => array(
754
      'class' => array('flag-admin-table'),
755
      'id' => 'flag-roles',
756
    ),
757
  ));
758
}
759

    
760
/**
761
 * Delete flag page.
762
 */
763
function flag_delete_confirm($form, &$form_state, $flag) {
764
  $form['#flag'] = $flag;
765

    
766
  return confirm_form($form,
767
    t('Are you sure you want to delete %title?', array('%title' => $flag->get_title())),
768
    !empty($_GET['destination']) ? $_GET['destination'] : FLAG_ADMIN_PATH,
769
    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.'),
770
    t('Delete'), t('Cancel')
771
  );
772
}
773

    
774
function flag_delete_confirm_submit($form, &$form_state) {
775
  $flag = $form['#flag'];
776
  if ($form_state['values']['confirm']) {
777
    $flag->delete();
778
    $flag->disable();
779
    _flag_clear_cache($flag->entity_type, TRUE);
780
  }
781
  drupal_set_message(t('Flag @name has been deleted.', array('@name' => $flag->get_title())));
782
  $form_state['redirect'] = FLAG_ADMIN_PATH;
783
}
784

    
785
/**
786
 * FormAPI after_build function to check that the link type exists.
787
 */
788
function flag_check_link_types($element) {
789
  $link_types = flag_get_link_types();
790
  if (!isset($link_types[$element['#value']])) {
791
    drupal_set_message(t('This flag uses a link type of %type, which does not exist.', array('%type' => $element['#value'])), 'error');
792
  }
793
  return $element;
794
}
795

    
796
/**
797
 * Clears various caches when one or more flags are modified.
798
 *
799
 * @param $entity_types
800
 *  The entity types for the flags. May be a single value or an array.
801
 * @param $is_insert_or_delete
802
 *  Whether the modified flag is being inserted (saved for the first time) or
803
 *  deleted. This results in a more vigorous clearing of caches. In
804
 *  particular, when no flags exist yet, no Field admin UI paths exist and these
805
 *  need to be created.
806
 */
807
function _flag_clear_cache($entity_types, $is_insert_or_delete = FALSE) {
808
  if (!is_array($entity_types)) {
809
    $entity_types = array($entity_types);
810
  }
811

    
812
  // Reset our flags cache, thereby making the following code aware of the
813
  // modifications.
814
  drupal_static_reset('flag_get_flags');
815

    
816
  if ($is_insert_or_delete) {
817
    // A new or deleted flag means we are changing bundles on the Flagging
818
    // entity, and thus need to clear the entity info cache.
819
    entity_info_cache_clear();
820
  }
821

    
822
  // Clear FieldAPI's field_extra cache, so our changes to pseudofields are
823
  // noticed. It's rather too much effort to both a) check whether the
824
  // pseudofield setting has changed either way, and b) specifically clear just
825
  // the bundles that are (or were!!) affected, so we just clear for all bundles
826
  // on our entity type regardlesss.
827
  foreach ($entity_types as $entity_type) {
828
    cache_clear_all("field_info:bundle_extra:$entity_type:", 'cache_field', TRUE);
829
  }
830

    
831
  if (module_exists('views')) {
832
    views_invalidate_cache();
833
  }
834

    
835
  // The title of a flag may appear in the menu (indirectly, via our "default
836
  // views"), so we need to clear the menu cache. This call also clears the
837
  // page cache, which is desirable too because the flag labels may have
838
  // changed.
839
  menu_rebuild();
840
}