Projet

Général

Profil

Paste
Télécharger (60,8 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / ctools / page_manager / page_manager.admin.inc @ 74f6bef0

1
<?php
2

    
3
/**
4
 * @file
5
 * Administrative functions for the page manager.
6
 *
7
 * This provides the UI to list, create, edit and delete pages, though much
8
 * of this is delegated through to individual tasks.
9
 */
10

    
11
/**
12
 * Output a list of pages that are managed.
13
 */
14
function page_manager_list_page($js = NULL) {
15
  // Prevent this page from showing up when random other links fail.
16
  if ($js && $js != 'ajax' && $js != 'nojs') {
17
    return MENU_NOT_FOUND;
18
  }
19

    
20
  // TRUE if 'ajax', FALSE if otherwise.
21
  $js = $js == 'ajax';
22

    
23
  // If we do any form rendering, it's to completely replace a form on the
24
  // page, so don't let it force our ids to change.
25
  if ($js && isset($_POST['ajax_html_ids'])) {
26
    unset($_POST['ajax_html_ids']);
27
  }
28

    
29
  if (module_exists('advanced_help') && !$js) {
30
    drupal_set_message(theme('advanced_help_topic', array(
31
      'module' => 'page_manager',
32
      'topic' => 'getting-started',
33
      'type' => t('See the getting started guide for more information.'),
34
    )));
35
  }
36

    
37
  $tasks = page_manager_get_tasks_by_type('page');
38
  $pages = array('operations' => array(), 'tasks' => array());
39

    
40
  page_manager_get_pages($tasks, $pages);
41

    
42
  // Add lock icon to all locked tasks.
43
  global $user;
44

    
45
  ctools_include('object-cache');
46
  $locks = ctools_object_cache_test_objects('page_manager_page', $pages['tasks']);
47
  foreach ($locks as $task_name => $lock) {
48
    if ($lock->uid == $user->uid) {
49
      $pages['rows'][$task_name]['class'][] = ' page-manager-locked';
50
      $pages['rows'][$task_name]['title'] = t('This page is currently locked for editing by you. Nobody else may edit this page until these changes are saved or canceled.');
51
    }
52
    else {
53
      $pages['rows'][$task_name]['class'][] = ' page-manager-locked-other';
54
      $pages['rows'][$task_name]['title'] = t('This page is currently locked for editing by another user. You may not edit this page without breaking the lock.');
55
    }
56
  }
57

    
58
  $input = $_POST;
59

    
60
  // Respond to a reset command by clearing session and doing a drupal goto
61
  // back to the base URL.
62
  if (isset($input['op']) && $input['op'] == t('Reset')) {
63
    unset($_SESSION['page_manager']['#admin']);
64
    if (!$js) {
65
      drupal_goto($_GET['q']);
66
    }
67
    // clear everything but form id, form build id and form token:
68
    $keys = array_keys($input);
69
    foreach ($keys as $id) {
70
      if ($id != 'form_id' && $id != 'form_build_id' && $id != 'form_token') {
71
        unset($input[$id]);
72
      }
73
    }
74
    $replace_form = TRUE;
75
  }
76
  if (count($input) <= 1) {
77
    if (isset($_SESSION['page_manager']['#admin']) && is_array($_SESSION['page_manager']['#admin'])) {
78
      $input  = $_SESSION['page_manager']['#admin'];
79
    }
80
  }
81
  else {
82
    $_SESSION['page_manager']['#admin'] = $input;
83
    unset($_SESSION['page_manager']['#admin']['q']);
84
  }
85

    
86
  $form_state = array(
87
    'pages' => &$pages,
88
    'input' => $input,
89
    'rerender' => TRUE,
90
    'no_redirect' => TRUE,
91
  );
92

    
93
  // This form will sort and filter the pages.
94
  $form = drupal_build_form('page_manager_list_pages_form', $form_state);
95

    
96
  $header = array(
97
    array('data' => t('Type'), 'class' => array('page-manager-page-type')),
98
    array('data' => t('Name'), 'class' => array('page-manager-page-name')),
99
    array('data' => t('Title'), 'class' => array('page-manager-page-title')),
100
    array('data' => t('Path'), 'class' => array('page-manager-page-path')),
101
    array('data' => t('Storage'), 'class' => array('page-manager-page-storage')),
102
  );
103

    
104
  $header[] = array('data' => t('Operations'), 'class' => array('page-manager-page-operations'));
105
  $table = theme('table', array('header' => $header, 'rows' => $pages['rows'], 'attributes' => array('id' => 'page-manager-list-pages')));
106

    
107
  $operations = '<div id="page-manager-links" class="links">' . theme('links', array('links' => $pages['operations'])) . '</div>';
108

    
109
  drupal_add_css(drupal_get_path('module', 'page_manager') . '/css/page-manager.css');
110

    
111

    
112
  if (!$js) {
113
    return array('#markup' => drupal_render($form) . $table . $operations);
114
  }
115

    
116
  ctools_include('ajax');
117
  $commands = array();
118
  $commands[] = ajax_command_replace('#page-manager-list-pages', $table);
119
  if (!empty($replace_form)) {
120
    $commands[] = ajax_command_replace('#page-manager-list-pages-form', drupal_render($form));
121
  }
122
  print ajax_render($commands);
123
  ajax_footer();
124
}
125

    
126
/**
127
 * Sort tasks into buckets based upon whether or not they have subtasks.
128
 */
129
function page_manager_get_pages($tasks, &$pages, $task_id = NULL) {
130
  foreach ($tasks as $id => $task) {
131
    if (empty($task_id) && !empty($task['page operations'])) {
132
      $pages['operations'] = array_merge($pages['operations'], $task['page operations']);
133
    }
134

    
135
    // If a type has subtasks, add its subtasks in its own table.
136
    if (!empty($task['subtasks'])) {
137
      page_manager_get_pages(page_manager_get_task_subtasks($task), $pages, $task['name']);
138
      continue;
139
    }
140

    
141
    if (isset($task_id)) {
142
      $task_name = page_manager_make_task_name($task_id, $task['name']);
143
    }
144
    else {
145
      $task_name = $task['name'];
146
    }
147

    
148
    $class = array('page-task-' . $id);
149
    if (isset($task['row class'])) {
150
      $class[] = $task['row class'];
151
    }
152

    
153
    if (!empty($task['disabled'])) {
154
      $class[] = 'page-manager-disabled';
155
    }
156

    
157
    $path = array();
158
    $visible_path = '';
159
    if (!empty($task['admin path'])) {
160
      foreach (explode('/', $task['admin path']) as $bit) {
161
        if (isset($bit[0]) && $bit[0] != '!') {
162
          $path[] = $bit;
163
        }
164
      }
165

    
166
      $path = implode('/', $path);
167
      if (empty($task['disabled']) && strpos($path, '%') === FALSE) {
168
        $visible_path = l('/' . $task['admin path'], $path);
169
      }
170
      else {
171
        $visible_path = '/' . $task['admin path'];
172
      }
173
    }
174

    
175
    $row = array('data' => array(), 'class' => $class, 'title' => strip_tags($task['admin description']));
176

    
177
    $type = isset($task['admin type']) ? $task['admin type'] : t('System');
178
    $pages['types'][$type] = $type;
179
    $row['data']['type'] = array('data' => $type, 'class' => array('page-manager-page-type'));
180

    
181
    $row['data']['name'] = array('data' => $task_name, 'class' => array('page-manager-page-name'));
182
    $row['data']['title'] = array('data' => $task['admin title'], 'class' => array('page-manager-page-title'));
183
    $row['data']['path'] = array('data' => $visible_path, 'class' => array('page-manager-page-path'));
184

    
185
    $storage = isset($task['storage']) ? $task['storage'] : t('In code');
186
    $pages['storages'][$storage] = $storage;
187
    $row['data']['storage'] = array('data' => $storage, 'class' => array('page-manager-page-storage'));
188

    
189

    
190
/*
191
    if (empty($task['disabled'])) {
192
      $item = menu_get_item($path);
193
      if (empty($item)) {
194
        dsm($path);
195
      }
196
      else {
197
        dsm($item);
198
      }
199
    }
200
*/
201
    $operations = array(
202
      array(
203
        'title' => t('Edit'),
204
        'href' => page_manager_edit_url($task_name),
205
      ),
206
    );
207

    
208
    if (!empty($task['enable callback'])) {
209
      if (!empty($task['disabled'])) {
210
        array_unshift($operations, array(
211
          'title' => t('Enable'),
212
          'href' => 'admin/structure/pages/nojs/enable/' . $task_name,
213
          'query' => array('token' => drupal_get_token($task_name)),
214
        ));
215
      }
216
      else {
217
        $operations[] = array(
218
          'title' => t('Disable'),
219
          'href' => 'admin/structure/pages/nojs/disable/' . $task_name,
220
          'query' => array('token' => drupal_get_token($task_name)),
221
        );
222
      }
223
    }
224

    
225
    $ops = theme('links__ctools_dropbutton', array('links' => $operations, 'attributes' => array('class' => array('links', 'inline'))));
226

    
227
    $row['data']['operations'] = array('data' => $ops, 'class' => array('page-manager-page-operations'));
228

    
229
    $pages['disabled'][$task_name] = !empty($task['disabled']);
230
    $pages['tasks'][] = $task_name;
231
    $pages['rows'][$task_name] = $row;
232
  }
233
}
234

    
235
/**
236
 * Provide a form for sorting and filtering the list of pages.
237
 */
238
function page_manager_list_pages_form($form, &$form_state) {
239
  // This forces the form to *always* treat as submitted which is
240
  // necessary to make it work.
241
  if (empty($_POST)) {
242
    $form["#programmed"] = TRUE;
243
  }
244
  $form['#action'] = url('admin/structure/pages/nojs/', array('absolute' => TRUE));
245
  if (!variable_get('clean_url', FALSE)) {
246
    $form['q'] = array(
247
      '#type' => 'hidden',
248
      '#value' => $_GET['q'],
249
    );
250
  }
251

    
252
  $all = array('all' => t('<All>'));
253

    
254
  $form['type'] = array(
255
    '#type' => 'select',
256
    '#title' => t('Type'),
257
    '#options' => $all + $form_state['pages']['types'],
258
    '#default_value' => 'all',
259
  );
260

    
261
  $form['storage'] = array(
262
    '#type' => 'select',
263
    '#title' => t('Storage'),
264
    '#options' => $all + $form_state['pages']['storages'],
265
    '#default_value' => 'all',
266
  );
267

    
268
  $form['disabled'] = array(
269
    '#type' => 'select',
270
    '#title' => t('Enabled'),
271
    '#options' => $all + array('0' => t('Enabled'), '1' => t('Disabled')),
272
    '#default_value' => 'all',
273
  );
274

    
275
  $form['search'] = array(
276
    '#type' => 'textfield',
277
    '#title' => t('Search'),
278
  );
279

    
280
  $form['order'] = array(
281
    '#type' => 'select',
282
    '#title' => t('Sort by'),
283
    '#options' => array(
284
      'disabled' => t('Enabled, title'),
285
      'title' => t('Title'),
286
      'name' => t('Name'),
287
      'path' => t('Path'),
288
      'type' => t('Type'),
289
      'storage' => t('Storage'),
290
    ),
291
    '#default_value' => 'disabled',
292
  );
293

    
294
  $form['sort'] = array(
295
    '#type' => 'select',
296
    '#title' => t('Order'),
297
    '#options' => array(
298
      'asc' => t('Up'),
299
      'desc' => t('Down'),
300
    ),
301
    '#default_value' => 'asc',
302
  );
303

    
304
  $form['submit'] = array(
305
    '#name' => '', // so it won't in the $_GET args
306
    '#type' => 'submit',
307
    '#id' => 'edit-pages-apply',
308
    '#value' => t('Apply'),
309
    '#attributes' => array('class' => array('use-ajax-submit ctools-auto-submit-click')),
310
  );
311

    
312
  $form['reset'] = array(
313
    '#type' => 'submit',
314
    '#id' => 'edit-pages-reset',
315
    '#value' => t('Reset'),
316
    '#attributes' => array('class' => array('use-ajax-submit')),
317
  );
318

    
319
  $form['#attached']['js'] = array(ctools_attach_js('auto-submit'), ctools_attach_js('page-list', 'page_manager'));
320
  $form['#attached']['library'][] = array('system', 'drupal.ajax');
321
  $form['#attached']['library'][] = array('system', 'jquery.form');
322
  $form['#prefix'] = '<div class="clearfix">';
323
  $form['#suffix'] = '</div>';
324
  $form['#attributes'] = array('class' => array('ctools-auto-submit-full-form'));
325

    
326
  return $form;
327
}
328

    
329
/**
330
 * Accept submission from the page manager sort/filter form and apply it
331
 * to the list of pages.
332
 */
333
function page_manager_list_pages_form_submit(&$form, &$form_state) {
334
  // Filter and re-sort the pages.
335

    
336
  // This is a copy.
337
  $rows = $form_state['pages']['rows'];
338

    
339
  $sorts = array();
340
  foreach ($rows as $name => $data) {
341
    // Filter
342
    if ($form_state['values']['type'] != 'all' && $form_state['values']['type'] != $data['data']['type']['data']) {
343
      continue;
344
    }
345

    
346
    if ($form_state['values']['storage'] != 'all' && $form_state['values']['storage'] != $data['data']['storage']['data']) {
347
      continue;
348
    }
349

    
350
    if ($form_state['values']['disabled'] != 'all' && $form_state['values']['disabled'] != $form_state['pages']['disabled'][$name]) {
351
      continue;
352
    }
353

    
354
    if ($form_state['values']['search'] &&
355
        strpos($data['data']['name']['data'], $form_state['values']['search']) === FALSE &&
356
        strpos($data['data']['path']['data'], $form_state['values']['search']) === FALSE &&
357
        strpos($data['data']['title']['data'], $form_state['values']['search']) === FALSE) {
358
          continue;
359
    }
360
    // Set up sorting
361
    switch ($form_state['values']['order']) {
362
      case 'disabled':
363
        $sorts[$name] = !$form_state['pages']['disabled'][$name] . $data['data']['title']['data'];
364
        break;
365
      case 'title':
366
        $sorts[$name] = $data['data']['title']['data'];
367
        break;
368
      case 'name':
369
        $sorts[$name] = $data['data']['name']['data'];
370
        break;
371
      case 'path':
372
        $sorts[$name] = $data['data']['path']['data'];
373
        break;
374
      case 'type':
375
        $sorts[$name] = $data['data']['type']['data'];
376
        break;
377
      case 'storage':
378
        $sorts[$name] = $data['data']['storage']['data'];
379
        break;
380
    }
381
  }
382

    
383
  // Now actually sort
384
  if ($form_state['values']['sort'] == 'desc') {
385
    arsort($sorts);
386
  }
387
  else {
388
    asort($sorts);
389
  }
390

    
391
  // Nuke the original.
392
  $form_state['pages']['rows'] = array();
393
  // And restore.
394
  foreach ($sorts as $name => $title) {
395
    $form_state['pages']['rows'][$name] = $rows[$name];
396
  }
397

    
398
}
399

    
400
/**
401
 * Render the edit page for a a page, custom or system.
402
 */
403
function page_manager_edit_page($page) {
404
  drupal_set_title($page->subtask['admin title']);
405
  // Provide and process the save page form before anything else.
406
  $form_state = array('page' => &$page);
407
  $built_form = drupal_build_form('page_manager_save_page_form', $form_state);
408
  $form = drupal_render($built_form);
409

    
410
  $operations = page_manager_get_operations($page);
411
  $args = array('summary');
412
  $rendered_operations = page_manager_render_operations($page, $operations, $args, array('class' => array('operations-main')), 'nav');
413
  $content = page_manager_get_operation_content(FALSE, $page, $args, $operations);
414
  $output = theme('page_manager_edit_page', array('page' => $page, 'save' => $form, 'operations' => $rendered_operations, 'content' => $content));
415
  return array('#markup' => $output);
416
}
417

    
418
/**
419
 * Entry point to edit a single operation for a page.
420
 *
421
 * @param $js
422
 *   Whether or not the page was called via javascript.
423
 * @param $page
424
 *   The cached page that is being edited.
425
 * @param ...
426
 *   A number of items used to drill down into the actual operation called.
427
 */
428
function page_manager_edit_page_operation() {
429
  $args = func_get_args();
430
  $js = array_shift($args);
431
  $page = array_shift($args);
432

    
433
  $operations = page_manager_get_operations($page);
434
  $content = page_manager_get_operation_content($js, $page, $args, $operations);
435

    
436
  // If the operation requested we go somewhere else afterward, oblige it.
437
  if (isset($content['new trail'])) {
438
    $args = $content['new trail'];
439
    // Get operations again, for the operation may have changed their availability.
440
    $operations = page_manager_get_operations($page);
441
    $content = page_manager_get_operation_content($js, $page, $args, $operations);
442
  }
443

    
444
  // Rendering the content may have been a form submission that changed the
445
  // operations, such as renaming or adding a handler. Thus we get a new set
446
  // of operations.
447
  $operations = page_manager_get_operations($page);
448
  $rendered_operations = page_manager_render_operations($page, $operations, $args, array('class' => array('operations-main')), 'nav');
449

    
450
  // Since this form should never be submitted to this page, process it late so
451
  // that we can be sure it notices changes.
452
  $form_state = array('page' => &$page);
453
  $built_form = drupal_build_form('page_manager_save_page_form', $form_state);
454
  $form = drupal_render($built_form);
455

    
456
  $output = theme('page_manager_edit_page', array('page' => $page, 'save' => $form, 'operations' => $rendered_operations, 'content' => $content));
457

    
458
  if ($js) {
459
    $commands = array();
460
    $commands[] = ajax_command_replace('#page-manager-edit', $output);
461

    
462
    print ajax_render($commands);
463
    ajax_footer();
464
    return;
465
  }
466

    
467
  drupal_set_title($page->subtask['admin title']);
468
  return $output;
469
}
470

    
471
/**
472
 * Take the operations array from a task and expand it.
473
 *
474
 * This allows some of the operations to be dynamic, based upon settings
475
 * on the task or the task's handlers. Each operation should have a type. In
476
 * addition to all the types allowed in page_manager_render_operations, these
477
 * types will be dynamically replaced with something else:
478
 * - 'handlers': An automatically created group that contains all the task's
479
 *   handlers and appropriate links.
480
 * - 'function': A callback (which will be placed in the 'function' parameter
481
 *   that should return an array of operations. This can be used to provide
482
 *   additional, dynamic links if needed.
483
 */
484
function page_manager_get_operations($page, $operations = NULL) {
485
  if (!isset($operations)) {
486
    // All tasks have at least these 2 ops:
487
    $operations = array(
488
      'summary' => array(
489
        'title' => t('Summary'),
490
        'description' => t('Get a summary of the information about this page.'),
491
        'path' => 'admin/structure/pages/edit',
492
        'ajax' => FALSE,
493
        'no operations' => TRUE,
494
        'form info' => array(
495
          'no buttons' => TRUE,
496
        ),
497
        'form' => 'page_manager_page_summary',
498
      ),
499
      'actions' => array(
500
        'type' => 'group',
501
        'title' => '',
502
        'class' => array('operations-actions'),
503
        'location' => 'primary',
504
        'children' => array(),
505
      ),
506
    );
507

    
508
    if (isset($page->subtask['operations'])) {
509
      $operations += $page->subtask['operations'];
510
      // add actions separately.
511
      if (!empty($page->subtask['operations']['actions'])) {
512
        $operations['actions']['children'] += $page->subtask['operations']['actions']['children'];
513
      }
514
    }
515
    $operations['handlers'] = array('type' => 'handlers');
516
  }
517

    
518
  $result = array();
519
  foreach ($operations as $id => $operation) {
520
    if (empty($operation['type'])) {
521
      $operation['type'] = 'operation';
522
    }
523
    switch ($operation['type']) {
524
      case 'handlers':
525
        $result[$id] = page_manager_get_handler_operations($page);
526
        break;
527
      case 'function':
528
        if (function_exists($operation['function'])) {
529
          $retval = $function($page, $operation);
530
          if (is_array($retval)) {
531
            $result[$id] = $retval;
532
          }
533
        }
534
        break;
535
      default:
536
        $result[$id] = $operation;
537
    }
538
  }
539

    
540
  if (!empty($page->subtask['enable callback']) && !empty($page->subtask['disabled']) && empty($result['actions']['children']['enable'])) {
541
    $result['actions']['children']['enable'] = array(
542
      'title' => t('Enable'),
543
      'description' => t('Activate this page so that it will be in use in your system.'),
544
      'form' => 'page_manager_enable_form',
545
      'ajax' => FALSE,
546
      'silent' => TRUE,
547
      'no update and save' => TRUE,
548
      'form info' => array(
549
        'finish text' => t('Enable'),
550
      ),
551
    );
552
  }
553

    
554
  if (!empty($page->subtask['enable callback']) && empty($page->subtask['disabled']) && empty($result['actions']['children']['disable'])) {
555
    $result['actions']['children']['disable'] = array(
556
      'title' => t('Disable'),
557
      'description' => t('De-activate this page. The data will remain but the page will not be in use on your system.'),
558
      'form' => 'page_manager_disable_form',
559
      'ajax' => FALSE,
560
      'silent' => TRUE,
561
      'no update and save' => TRUE,
562
      'form info' => array(
563
        'finish text' => t('Disable'),
564
      ),
565
    );
566
  }
567

    
568
  $result['actions']['children']['add'] = array(
569
    'title' => t('Add variant'),
570
    'description' => t('Add a new variant to this page.'),
571
    'form' => 'page_manager_handler_add',
572
    'ajax' => FALSE,
573
    'silent' => TRUE, // prevents a message about updating and prevents this item from showing as changed.
574
    'no update and save' => TRUE, // get rid of update and save button which is bad here.
575
    'form info' => array(
576
      'finish text' => t('Create variant'),
577
    ),
578
  );
579

    
580
  // Restrict variant import to users who can already execute arbitrary PHP
581
  if (user_access('use PHP for settings')) {
582
    $result['actions']['children']['import'] = array(
583
      'title' => t('Import variant'),
584
      'description' => t('Add a new variant to this page from code exported from another page.'),
585
      'form' => 'page_manager_handler_import',
586
    );
587
  }
588

    
589
  if (count($page->handlers) > 1) {
590
    $result['actions']['children']['rearrange'] = array(
591
      'title' => t('Reorder variants'),
592
      'ajax' => FALSE,
593
      'description' => t('Change the priority of the variants to ensure that the right one gets selected.'),
594
      'form' => 'page_manager_handler_rearrange',
595
    );
596
  }
597

    
598
  // This is a special operation used to configure a new task handler before
599
  // it is added.
600
  if (isset($page->new_handler)) {
601
  $plugin = page_manager_get_task_handler($page->new_handler->handler);
602
    $result['actions']['children']['configure'] = array(
603
      'title' => t('Configure'),
604
      'description' => t('Configure a newly created variant prior to actually adding it to the page.'),
605
      'ajax' => FALSE,
606
      'no update and save' => TRUE, // get rid of update and save button which is bad here.
607
      'form info' => array(
608
        // We use our own cancel and finish callback to handle the fun stuff.
609
        'finish callback' => 'page_manager_handler_add_finish',
610
        'cancel callback' => 'page_manager_handler_add_cancel',
611
        'show trail' => TRUE,
612
        'show back' => TRUE,
613
        'finish text' => t('Create variant'),
614
      ),
615
      'form' => array(
616
        'forms' => $plugin['forms'],
617
      ),
618
    );
619

    
620
    foreach ($page->forms as $id) {
621
      if (isset($plugin['add features'][$id])) {
622
        $result['actions']['children']['configure']['form']['order'][$id] = $plugin['add features'][$id];
623
      }
624
      else if (isset($plugin['required forms'][$id])) {
625
        $result['actions']['children']['configure']['form']['order'][$id] = $plugin['required forms'][$id];
626
      }
627
    }
628
  }
629

    
630
  if ($page->locked) {
631
    $result['actions']['children']['break-lock'] = array(
632
      'title' => t('Break lock'),
633
      'description' => t('Break the lock on this page so that you can edit it.'),
634
      'form' => 'page_manager_break_lock',
635
      'ajax' => FALSE,
636
      'no update and save' => TRUE, // get rid of update and save button which is bad here.
637
      'form info' => array(
638
        'finish text' => t('Break lock'),
639
       ),
640
      'even locked' => TRUE, // show button even if locked
641
      'silent' => TRUE,
642
    );
643
  }
644

    
645
  drupal_alter('page_manager_operations', $result, $page);
646
  return $result;
647
}
648

    
649
/**
650
 * Collect all the operations related to task handlers (variants) and
651
 * build a menu.
652
 */
653
function page_manager_get_handler_operations(&$page) {
654
  ctools_include('export');
655
  $group = array(
656
    'type' => 'group',
657
    'class' => array('operations-handlers'),
658
    'title' => t('Variants'),
659
  );
660

    
661
  $operations = array();
662

    
663
  // If there is only one variant, let's not have it collapsible.
664
  $collapsible = count($page->handler_info) != 1;
665
  foreach ($page->handler_info as $id => $info) {
666
    if ($info['changed'] & PAGE_MANAGER_CHANGED_DELETED) {
667
      continue;
668
    }
669
    $handler = $page->handlers[$id];
670
    $plugin = page_manager_get_task_handler($handler->handler);
671

    
672
    $operations[$id] = array(
673
      'type' => 'group',
674
      'class' => array('operations-handlers-' . $id),
675
      'title' => page_manager_get_handler_title($plugin, $handler, $page->task, $page->subtask_id),
676
      'collapsible' => $collapsible,
677
      'children' => array(),
678
    );
679

    
680
    $operations[$id]['children']['actions'] = array(
681
      'type' => 'group',
682
      'class' => array('operations-handlers-actions-' . $id),
683
      'title' => t('Variant operations'),
684
      'children' => array(),
685
      'location' => $id,
686
    );
687

    
688
    // There needs to be a 'summary' item here for variants.
689
    $operations[$id]['children']['summary'] = array(
690
      'title' => t('Summary'),
691
      'description' => t('Get a summary of the information about this variant.'),
692
      'form info' => array(
693
        'no buttons' => TRUE,
694
      ),
695
      'form' => 'page_manager_handler_summary',
696
    );
697

    
698
    if ($plugin && isset($plugin['operations'])) {
699
      $operations[$id]['children'] += $plugin['operations'];
700
    }
701

    
702
    $actions = &$operations[$id]['children']['actions']['children'];
703

    
704
    $actions['clone'] = array(
705
      'title' => t('Clone'),
706
      'description' => t('Make an exact copy of this variant.'),
707
      'form' => 'page_manager_handler_clone',
708
    );
709
    $actions['export'] = array(
710
      'title' => t('Export'),
711
      'description' => t('Export this variant into code to import into another page.'),
712
      'form' => 'page_manager_handler_export',
713
    );
714
    if ($handler->export_type == (EXPORT_IN_CODE | EXPORT_IN_DATABASE)) {
715
      $actions['delete'] = array(
716
        'title' => t('Revert'),
717
        'description' => t('Remove all changes to this variant and revert to the version in code.'),
718
        'form' => 'page_manager_handler_delete',
719
        'no update and save' => TRUE,
720
        'form info' => array(
721
          'finish text' => t('Revert'),
722
        ),
723
      );
724
    }
725
    else if ($handler->export_type != EXPORT_IN_CODE) {
726
      $actions['delete'] = array(
727
        'title' => t('Delete'),
728
        'description' => t('Remove this variant from the page completely.'),
729
        'form' => 'page_manager_handler_delete',
730
        'form info' => array(
731
          'finish text' => t('Delete'),
732
          'save text' => t('Delete and save'),
733
        ),
734
      );
735
    }
736
    if (!empty($handler->disabled)) {
737
      $actions['enable'] = array(
738
        'title' => t('Enable'),
739
        'description' => t('Activate this variant so that it will be in use in your system.'),
740
        'form' => 'page_manager_handler_enable',
741
        'silent' => TRUE,
742
        'form info' => array(
743
          'finish text' => t('Enable'),
744
          'save text' => t('Enable and save'),
745
        ),
746
      );
747
    }
748
    else {
749
      $actions['disable'] = array(
750
        'title' => t('Disable'),
751
        'description' => t('De-activate this variant. The data will remain but the variant will not be in use on your system.'),
752
        'form' => 'page_manager_handler_disable',
753
        'silent' => TRUE,
754
        'form info' => array(
755
          'finish text' => t('Disable'),
756
          'save text' => t('Disable and save'),
757
        ),
758
      );
759
    }
760

    
761
    drupal_alter('page_manager_variant_operations', $operations[$id], $handler);
762
  }
763
  if (empty($operations)) {
764
    $operations['empty'] = array(
765
      'type' => 'text',
766
      'title' => t('No variants'),
767
    );
768
  }
769

    
770
  $group['children'] = $operations;
771
  return $group;
772
}
773

    
774
/**
775
 * Get an operation from a trail.
776
 *
777
 * @return array($operation, $active, $args)
778
 */
779
function page_manager_get_operation($operations, $trail) {
780
  $args = $trail;
781
  $stop = FALSE;
782
  $active = array();
783
  $titles = array();
784
  // Drill down into operations array:
785
  while (!$stop) {
786
    $check = reset($args);
787
    $stop = TRUE;
788
    if (is_array($operations)) {
789
      if (isset($operations[$check])) {
790
        $active[] = $check;
791
        $operation = array_shift($args);
792
        // check to see if this operation has children. If so, we continue.
793
        if (!isset($operations[$check]['children'])) {
794
          $operations = $operations[$check];
795
        }
796
        else {
797
          $titles[] = $operations[$check]['title'];
798
          $operations = $operations[$check]['children'];
799
          // continue only if the operation hs children.
800
          $stop = FALSE;
801
        }
802
      }
803
    }
804
  }
805

    
806
  return array($operations, $active, $args, $titles);
807
}
808

    
809
/**
810
 * Fetch the content for an operation.
811
 *
812
 * First, this drills down through the arguments to find the operation, and
813
 * turns whatever it finds into the active trail which is then used to
814
 * hilite where we are when rendering the operation list.
815
 *
816
 * The arguments are discovered from the URL, and are an exact match for where
817
 * the operation is in the hierarchy. For example, handlers/foo/settings will
818
 * be the operation to edit the settings for the handler named foo. This comes
819
 * in as an array ('handlers', 'foo', 'settings') and is used to find where the
820
 * data for that operation is in the array.
821
 */
822
function page_manager_get_operation_content($js, &$page, $trail, $operations) {
823
  list($operation, $active, $args, $titles) = page_manager_get_operation($operations, $trail);
824
  // Once we've found the operation, send it off to render.
825
  if ($operation) {
826
    $content = _page_manager_get_operation_content($js, $page, $active, $operation, $titles, $args);
827
  }
828

    
829
  if (empty($content)) {
830
    $content = _page_manager_get_operation_content($js, $page, array('summary'), $operations['summary']);
831
  }
832

    
833
  return $content;
834
}
835

    
836
/**
837
 * Fetch the content for an operation, after it's been discovered from arguments.
838
 *
839
 * This system runs through the CTools form wizard. Each operation specifies a form
840
 * or set of forms that it may use. Operations may also specify wrappers and can
841
 * set their own next/finish handlers so that they can make additional things happen
842
 * at the end.
843
 */
844
function _page_manager_get_operation_content($js, &$page, $active, $operation, $titles = array(), $args = array()) {
845
  if (isset($operation['form'])) {
846
    $form_info = array(
847
      'id' => 'page_manager_page',
848
      'finish text' => t('Update'),
849
      'show trail' => FALSE,
850
      'show back' => FALSE,
851
      'show return' => FALSE,
852
      'show cancel' => FALSE,
853
      'next callback' => 'page_manager_edit_page_next',
854
      'finish callback' => 'page_manager_edit_page_finish',
855
      // Items specific to the 'edit' routines that will get moved over:
856
      'path' => page_manager_edit_url($page->task_name, $active) . "/%step",
857
      // wrapper function to add an extra finish button.
858
      'wrapper' => 'page_manager_operation_wrapper',
859
    );
860

    
861
    // If $operation['form'] is simply a string, then it is the function
862
    // name of the form.
863
    if (!is_array($operation['form'])) {
864
      $form_info['order'] = array(
865
        'form' => $operation['title'],
866
      );
867
      $form_info['forms'] = array(
868
        'form' => array('form id' => $operation['form']),
869
      );
870
      if (isset($operation['wrapper'])) {
871
        $form_info['forms']['form']['wrapper'] = $operation['wrapper'];
872
      }
873
    }
874
    // Otherwise it's the order and forms arrays directly.
875
    else {
876
      $form_info['order'] = $operation['form']['order'];
877
      $form_info['forms'] = $operation['form']['forms'];
878
    }
879

    
880
    // Allow the operation to override any form info settings:
881
    if (isset($operation['form info'])) {
882
      foreach ($operation['form info'] as $key => $setting) {
883
        $form_info[$key] = $setting;
884
      }
885
    }
886

    
887
    if (!empty($page->subtask['operations include'])) {
888
      // Quickly load any files necessary to display the forms.
889
      $page->subtask['operations include']['function'] = 'nop';
890
      ctools_plugin_get_function($page->subtask, 'operations include');
891
    }
892

    
893
    $step = array_shift($args);
894
    // If step is unset, go with the basic step.
895
    if (!isset($step)) {
896
      $step = current(array_keys($form_info['order']));
897
    }
898

    
899
    // If it is locked, hide the buttonzzz!
900
    if ($page->locked && empty($operation['even locked'])) {
901
      $form_info['no buttons'] = TRUE;
902
    }
903

    
904
    ctools_include('wizard');
905
    $form_state = array(
906
      'page' => $page,
907
      'type' => 'edit',
908
      'ajax' => $js && (!isset($operation['ajax']) || !empty($operation['ajax'])),
909
      'rerender' => TRUE,
910
      'trail' => $active,
911
      'task_name' => $page->task_name,
912
      'task_id' => $page->task_id,
913
      'task' => $page->task,
914
      'subtask_id' => $page->subtask_id,
915
      'subtask' => $page->subtask,
916
      'operation' => $operation,
917
    );
918

    
919
    if ($active && $active[0] == 'handlers' && isset($form_state['page']->handlers[$form_state['trail'][1]])) {
920
      $form_state['handler_id'] = $form_state['trail'][1];
921
      $form_state['handler'] = &$form_state['page']->handlers[$form_state['handler_id']];
922
    }
923

    
924
    if ($active && $active[0] == 'actions' && $active[1] == 'configure' && isset($form_state['page']->new_handler)) {
925
      $form_state['handler_id'] = $form_state['page']->new_handler->name;
926
      $form_state['handler'] = &$form_state['page']->new_handler;
927
    }
928

    
929
    $built_form = ctools_wizard_multistep_form($form_info, $step, $form_state);
930
    $output = drupal_render($built_form);
931
    $title = empty($form_state['title']) ? $operation['title'] : $form_state['title'];
932
    $titles[] = $title;
933
    $title = implode(' &raquo; ', array_filter($titles));
934

    
935
    // If there are messages for the form, render them.
936
    if ($messages = theme('status_messages')) {
937
      $output = $messages . $output;
938
    }
939

    
940
    $description = isset($operation['admin description']) ? $operation['admin description'] : (isset($operation['description']) ? $operation['description'] : '');
941
    $return = array(
942
      'title' => $title,
943
      'content' => $output,
944
      'description' => $description,
945
    );
946

    
947
    // Append any extra content, used for the preview which is submitted then
948
    // rendered.
949
    if (isset($form_state['content'])) {
950
      $return['content'] .= $form_state['content'];
951
    }
952

    
953
    // If the form wanted us to go somewhere else next, pass that along.
954
    if (isset($form_state['new trail'])) {
955
      $return['new trail'] = $form_state['new trail'];
956
    }
957
  }
958
  else {
959
    $return = array(
960
      'title' => t('Error'),
961
      'content' => t('This operation trail does not exist.'),
962
    );
963
  }
964

    
965
  $return['active'] = $active;
966
  return $return;
967
}
968

    
969
function page_manager_operation_wrapper($form, &$form_state) {
970
  if (empty($form_state['operation']['no update and save']) && !empty($form['buttons']['return']['#wizard type']) && $form['buttons']['return']['#wizard type']) {
971
    $form['buttons']['save'] = array(
972
      '#type' => 'submit',
973
      '#value' => !empty($form_state['form_info']['save text']) ? $form_state['form_info']['save text'] : t('Update and save'),
974
      '#wizard type' => 'finish',
975
      '#attributes' => $form['buttons']['return']['#attributes'],
976
      '#save' => TRUE,
977
    );
978
  }
979

    
980
  return $form;
981
}
982

    
983
/**
984
 * Callback generated when the an operation edit finished.
985
 */
986
function page_manager_edit_page_finish(&$form_state) {
987
  if (empty($form_state['operation']['silent'])) {
988
    if (empty($form_state['clicked_button']['#save'])) {
989
      drupal_set_message(t('The page has been updated. Changes will not be permanent until you save.'));
990
    }
991
    else {
992
      drupal_set_message(t('The page has been updated and saved.'));
993
    }
994
    $path = array();
995
    foreach ($form_state['trail'] as $operation) {
996
      $path[] = $operation;
997
      $form_state['page']->changes[implode('/', $path)] = TRUE;
998
    }
999
  }
1000

    
1001
  // If a handler was modified, set it to changed so we know to overwrite it.
1002
  if (isset($form_state['handler_id'])) {
1003
    $form_state['page']->handler_info[$form_state['handler_id']]['changed'] |= PAGE_MANAGER_CHANGED_CACHED;
1004
  }
1005

    
1006
  // While we make buttons go away on locked pages, it is still possible to
1007
  // have a lock a appear while you were editing, and have your changes
1008
  // disappear. This at least warns the user that this has happened.
1009
  if (!empty($page->locked)) {
1010
    drupal_set_message(t('Unable to update changes due to lock.'));
1011
  }
1012

    
1013
  // If the 'Update and Save' button was selected, write our cache out.
1014
  if (!empty($form_state['clicked_button']['#save'])) {
1015
    page_manager_save_page_cache($form_state['page']);
1016
    page_manager_clear_page_cache($form_state['page']->task_name);
1017
    $form_state['page'] = page_manager_get_page_cache($form_state['page']->task_name);
1018
  }
1019
  else {
1020
    if (empty($form_state['do not cache'])) {
1021
      page_manager_set_page_cache($form_state['page']);
1022
    }
1023
  }
1024

    
1025
  // We basically always want to force a rerender when the forms
1026
  // are finished, so make sure there is a new trail.
1027
  if (empty($form_state['new trail'])) {
1028
    // force a rerender to get rid of old form items that may have changed
1029
    // during save.
1030
    $form_state['new trail'] = $form_state['trail'];
1031
  }
1032

    
1033
  if (isset($form_state['new trail']) && empty($form_state['ajax'])) {
1034
    $form_state['redirect'] = page_manager_edit_url($form_state['page']->task_name, $form_state['new trail']);
1035
  }
1036

    
1037
  $form_state['complete'] = TRUE;
1038
}
1039

    
1040
/**
1041
 * Callback generated when the 'next' button is clicked.
1042
 *
1043
 * All we do here is store the cache.
1044
 */
1045
function page_manager_edit_page_next(&$form_state) {
1046
  page_manager_set_page_cache($form_state['page']);
1047
}
1048

    
1049
/**
1050
 * Callback generated when the 'cancel' button is clicked.
1051
 *
1052
 * All we do here is clear the cache.
1053
 */
1054
function page_manager_edit_page_cancel(&$form_state) {
1055
  $page = $form_state['page'];
1056
}
1057

    
1058
/**
1059
 * Render an operations array.
1060
 *
1061
 * This renders an array of operations into a series of nested UL statements,
1062
 * with ajax automatically on unless specified otherwise. Operations will
1063
 * automatically have the URLs generated nested.
1064
 *
1065
 * Each operation should have a 'type', which tells the renderer how to deal
1066
 * with it:
1067
 * - 'operation': An AJAX link to render. This is the default and is
1068
 *   assumed if a type is not specified. Other fields for the operation:
1069
 * - - 'title': The text to display. Can be an image. Must be pre-sanitized.
1070
 * - - 'description': Text to place in the hover box over the link using the
1071
 *     title attribute.
1072
 * - - 'arguments': Anything optional to put at the end of the URL.
1073
 * - - 'path': If set, overrides the default path.
1074
 * - - 'no operations': If set, the path will not have operations appended.
1075
 * - - 'no task': If set, the path will not have the task id.
1076
 * - - 'no link': If set, this item will just be text, not a link.
1077
 * - - 'ajax': If set to TRUE, ajax will be used. The default is TRUE.
1078
 * - - 'class': An optional class to specify for the link.
1079
 * - - 'form': The form to display for this operation, if using a single form.
1080
 * - - 'forms': An array of forms that must be paired with 'order' of this
1081
 *      operation uses multiple forms. See wizard tool for details.
1082
 * - - 'order': The form order to use for multiple forms. See wizard tool for
1083
 *     details.
1084
 * - - 'form info': Form info overrides for the wizard. See the wizard tool
1085
 *      for available settings
1086
 * - 'group':
1087
 * - - 'title': The title of the link. May be HTML.
1088
 * - - 'title class': A class to apply to the title.
1089
 * - - 'children': An array of more operations that this group represents.
1090
 *     All operations within this group will have this group's ID as part
1091
 *     of the AJAX url to make it easier to find.
1092
 * - - 'class': A class to apply to the UL of the children.
1093
 * - - 'collapsible': If TRUE the collapsible tool will be used.
1094
 */
1095
function page_manager_render_operations(&$page, $operations, $active_trail, $attributes, $location, $parents = array()) {
1096
  drupal_add_library('system', 'drupal.ajax');
1097

    
1098
  if (!isset($output[$location])) {
1099
    $output[$location] = '';
1100
  }
1101

    
1102
  $keys = array_keys($operations);
1103
  $first = array_shift($keys);
1104
  $last = array_pop($keys);
1105

    
1106
  // Make sure the 'first' and 'last' operations are part of THIS nav tree:
1107
  while ($keys && isset($operations[$first]['location']) && $operations[$first]['location'] != $location) {
1108
    $first = array_shift($keys);
1109
  }
1110
  while ($keys && isset($operations[$last]['location']) && $operations[$last]['location'] != $location) {
1111
    $last = array_pop($keys);
1112
  }
1113

    
1114
  $active = reset($active_trail);
1115
  foreach ($operations as $id => $operation) {
1116
    $current_path = '';
1117
    if ($parents) {
1118
      $current_path .= implode('/', $parents) . '/';
1119
    }
1120
    $current_path .= $id;
1121

    
1122
    if (empty($operation['type'])) {
1123
      $operation['type'] = 'operation';
1124
    }
1125

    
1126
    // We only render an li for things in the same nav tree.
1127
    if (empty($operation['location']) || $operation['location'] == $location) {
1128
      if (!is_array($attributes['class'])) {
1129
        $attributes['class'] = array($attributes['class']);
1130
      }
1131

    
1132
      $class = empty($attributes['class']) || !is_array($attributes['class']) ? array() : $attributes['class'];
1133

    
1134
      if ($id == $first) {
1135
        $class[] = 'operation-first';
1136
      }
1137
      else if ($id == $last) {
1138
        $class[] = 'operation-last';
1139
      }
1140

    
1141
      if (empty($operation['silent']) && !empty($page->changes[$current_path])) {
1142
        $class[] = $operation['type'] == 'group' ? 'changed-group' : 'changed';
1143
      }
1144
      else {
1145
        $class[] = 'not-changed';
1146
      }
1147

    
1148
      if ($active == $id) {
1149
        $class[] = $operation['type'] == 'group' ? 'active-group' : 'active';
1150
      }
1151
      else {
1152
        $class[] = 'not-active';
1153
      }
1154

    
1155
      $output[$location] .= '<li class="' . implode(' ', $class) . '">';
1156
    }
1157

    
1158
    switch ($operation['type']) {
1159
      case 'text':
1160
        $output[$location] .= $operation['title'];
1161
        break;
1162
      case 'operation':
1163
        $path = isset($operation['path']) ? $operation['path'] : 'admin/structure/pages/nojs/operation';
1164
        if (!isset($operation['no task'])) {
1165
          $path .= '/' . $page->task_name;
1166
        }
1167

    
1168
        if (!isset($operation['no operations'])) {
1169
          $path .= '/' . $current_path;
1170
          if (isset($operation['arguments'])) {
1171
            $path .= '/' . $arguments;
1172
          }
1173
        }
1174

    
1175
        $class = array('page-manager-operation');
1176
        if (!isset($operation['ajax']) || !empty($operation['ajax'])) {
1177
          $class[] = 'use-ajax';
1178
        }
1179
        if (!empty($operation['class'])) {
1180
          $class[] = $operation['class'];
1181
        }
1182

    
1183
        $description = isset($operation['description']) ? $operation['description'] : '';
1184
        if (empty($operation['silent']) && !empty($page->changes[$current_path])) {
1185
          $description .= ' ' . t('This setting contains unsaved changes.');
1186
        }
1187

    
1188
        $output[$location] .= l($operation['title'], $path, array('attributes' => array('id' => 'page-manager-operation-' . $id, 'class' => $class, 'title' => $description), 'html' => TRUE));
1189
        break;
1190
      case 'group':
1191
        if ($active == $id) {
1192
          $trail = $active_trail;
1193
          array_shift($trail);
1194
        }
1195
        else {
1196
          $trail = array();
1197
        }
1198
        $group_location = isset($operation['location']) ? $operation['location'] : $location;
1199
        $temp = page_manager_render_operations($page, $operation['children'], $trail, $operation, $group_location, array_merge($parents, array($id)));
1200
        if ($temp) {
1201
          foreach ($temp as $id => $text) {
1202
            if (empty($output[$id])) {
1203
              $output[$id] = '';
1204
            }
1205
            $output[$id] .= $text;
1206
          }
1207
        }
1208
        break;
1209
    }
1210

    
1211
    if (empty($operation['location']) || $operation['location'] == $location) {
1212
      $output[$location] .= '</li>';
1213
    }
1214
  }
1215

    
1216
  if ($output[$location]) {
1217
    $classes = isset($attributes['class']) && is_array($attributes['class']) ? $attributes['class'] : array();
1218
    $classes[] = 'page-manager-operations';
1219

    
1220
    $output[$location] = '<ul class="' . implode(' ', $classes) . '">' . $output[$location] . '</ul>';
1221

    
1222
    if (!empty($attributes['title'])) {
1223
      $class = '';
1224
      if (isset($attributes['title class'])) {
1225
        $class = $attributes['title class'];
1226
      }
1227
      $title = '<div class="page-manager-group-title' . $class . '">' . $attributes['title'] . '</div>';
1228

    
1229
      if (!empty($attributes['collapsible'])) {
1230
        $output[$location] = theme('ctools_collapsible', array('handle' => $title, 'content' => $output[$location], 'collapsed' => empty($active_trail)));
1231
      }
1232
      else {
1233
        $output[$location] = $title . $output[$location];
1234
      }
1235
    }
1236
    return $output;
1237
  }
1238
}
1239

    
1240
/**
1241
 * Provide a simple form for saving the page manager info out of the cache.
1242
 */
1243
function page_manager_save_page_form($form, &$form_state) {
1244
  if (!empty($form_state['page']->changed)) {
1245
    $form['markup'] = array(
1246
      '#markup' => '<div class="changed-notification">' . t('You have unsaved changes to this page. You must select Save to write them to the database, or Cancel to discard these changes. Please note that if you have changed any form, you must submit that form before saving.') . '</div>',
1247
    );
1248

    
1249
    // Always make sure we submit back to the proper page.
1250
    $form['#action'] = url('admin/structure/pages/edit/' . $form_state['page']->task_name);
1251
    $form['save'] = array(
1252
      '#type' => 'submit',
1253
      '#value' => t('Save'),
1254
      '#submit' => array('page_manager_save_page_form_submit'),
1255
    );
1256

    
1257
    $form['cancel'] = array(
1258
      '#type' => 'submit',
1259
      '#value' => t('Cancel'),
1260
      '#submit' => array('page_manager_save_page_form_cancel'),
1261
    );
1262
    return $form;
1263
  }
1264
}
1265

    
1266
/**
1267
 * Save the page.
1268
 */
1269
function page_manager_save_page_form_submit(&$form, &$form_state) {
1270
  page_manager_save_page_cache($form_state['page']);
1271
}
1272

    
1273
/**
1274
 * Discard changes to the page.
1275
 */
1276
function page_manager_save_page_form_cancel($form, &$form_state) {
1277
  drupal_set_message(t('All pending changes have been discarded, and the page is now unlocked.'));
1278
  page_manager_clear_page_cache($form_state['page']->task_name);
1279

    
1280
  if (!empty($form_state['page']->new)) {
1281
    $form_state['redirect'] = 'admin/structure/pages';
1282
  }
1283
}
1284

    
1285
// --------------------------------------------------------------------------
1286
// Handler (variant) related forms.
1287

    
1288
/**
1289
 * Add a new task handler.
1290
 */
1291
function page_manager_handler_add($form, &$form_state) {
1292
  // Get a list of possible task handlers for this task.
1293
  return page_manager_handler_add_form($form, $form_state);
1294
}
1295

    
1296
/**
1297
 * Handler related forms.
1298
 */
1299
function page_manager_handler_add_submit(&$form, &$form_state) {
1300
  $cache = $form_state['page'];
1301
  $plugin = page_manager_get_task_handler($form_state['values']['handler']);
1302

    
1303
  // Create a new handler.
1304
  $handler = page_manager_new_task_handler($plugin);
1305
  if (!empty($form_state['values']['title'])) {
1306
    $handler->conf['title'] = $form_state['values']['title'];
1307
  }
1308
  else {
1309
    $handler->conf['title'] = $plugin['title'];
1310
  }
1311
  $cache->new_handler = $handler;
1312

    
1313
  // Figure out which forms to present them with
1314
  $cache->forms = array();
1315

    
1316
  $features = $form_state['values']['features'];
1317
  if (isset($features[$form_state['values']['handler']])) {
1318
    $cache->forms = array_merge($cache->forms, array_keys(array_filter($features[$form_state['values']['handler']])));
1319
  }
1320

    
1321
  if (isset($plugin['required forms'])) {
1322
    $cache->forms = array_merge($cache->forms, array_keys($plugin['required forms']));
1323
  }
1324

    
1325
  $form_state['no_rerender'] = TRUE;
1326
  if (!empty($cache->forms)) {
1327
    // Tell the form to go to the config page.
1328
    drupal_set_message(t('Before this variant can be added, it must be configured. When you are finished, click "Create variant" at the end of this wizard to add this to your page.'));
1329
    $form_state['new trail'] = array('actions', 'configure');
1330
  }
1331
  else {
1332
    // It has no forms at all. Add the variant and go to its first operation.
1333
    page_manager_handler_add_finish($form_state);
1334
  }
1335
}
1336

    
1337
/**
1338
 * Finish the add process and make the new handler official.
1339
 */
1340
function page_manager_handler_add_finish(&$form_state) {
1341
  $page = &$form_state['page'];
1342
  $handler = $page->new_handler;
1343
  page_manager_handler_add_to_page($page, $handler);
1344

    
1345
  // Remove the temporary page.
1346
  unset($page->new_handler);
1347
  unset($page->forms);
1348

    
1349
  // Set the new destination
1350
  $plugin = page_manager_get_task_handler($handler->handler);
1351
  if (!empty($plugin['add finish'])) {
1352
    $location = $plugin['add finish'];
1353
  }
1354
  else {
1355
    $keys = array_keys($plugin['operations']);
1356
    $location = reset($keys);
1357
  }
1358

    
1359
  $form_state['new trail'] = array('handlers', $handler->name, $location);
1360

    
1361
  // Pass through.
1362
  page_manager_edit_page_finish($form_state);
1363
}
1364

    
1365
/**
1366
 * Throw away a new handler and return to the add form
1367
 */
1368
function page_manager_handler_add_cancel(&$form_state) {
1369
  $form_state['new trail'] = array('handlers', 'add');
1370

    
1371
  // Remove the temporary page.
1372
  unset($page->new_handler);
1373
  unset($page->forms);
1374
}
1375

    
1376
/**
1377
 * Provide a consistent UI for adding handlers.
1378
 */
1379
function page_manager_handler_add_form($form, $form_state, $features = array()) {
1380
  $task = $form_state['task'];
1381
  $task_handler_plugins = page_manager_get_task_handler_plugins($task);
1382
  if (empty($task_handler_plugins)) {
1383
    drupal_set_message(t('There are currently no variants available and a page may not be added. Perhaps you need to install the Panels module to get a variant?'), 'error');
1384
    $form['buttons']['return']['#disabled'] = TRUE;
1385
    return;
1386
  }
1387

    
1388
  foreach ($task_handler_plugins as $id => $plugin) {
1389
    $options[$id] = $plugin['title'];
1390
    if (isset($plugin['add features'])) {
1391
      $features[$id] = $plugin['add features'];
1392
    }
1393
  }
1394

    
1395
  if (!isset($form_state['type']) || $form_state['type'] != 'add') {
1396
    $form['title'] = array(
1397
      '#type' => 'textfield',
1398
      '#title' => t('Title'),
1399
      '#description' => t('Administrative title of this variant. If you leave blank it will be automatically assigned.'),
1400
    );
1401
  }
1402

    
1403
  $form['handler'] = array(
1404
    '#title' => t('Variant type'),
1405
    '#type' => 'select',
1406
    '#options' => $options,
1407
  );
1408

    
1409
  // This set of checkboxes is not dangerous at all.
1410
  $form['features'] = array(
1411
    '#type' => 'item',
1412
    '#title' => t('Optional features'),
1413
    '#description' => t('Check any optional features you need to be presented with forms for configuring them. If you do not check them here you will still be able to utilize these features once the new page is created. If you are not sure, leave these unchecked.'),
1414
    '#tree' => TRUE,
1415
  );
1416

    
1417
  ctools_include('dependent');
1418
  foreach ($features as $plugin => $feature_list) {
1419
    foreach ($feature_list as $feature_id => $feature) {
1420
      $form['features'][$plugin][$feature_id] = array(
1421
        '#type' => 'checkbox',
1422
        '#title' => $feature,
1423
      );
1424
      if (!empty($form_state['page']->forms) && in_array($feature_id, $form_state['page']->forms)) {
1425
        $form['features'][$plugin][$feature_id]['#default_value'] = TRUE;
1426
      }
1427

    
1428
      if ($plugin != 'default') {
1429
        $form['features'][$plugin][$feature_id] += array(
1430
          '#dependency' => array('edit-handler' => array($plugin)),
1431
        );
1432
      }
1433
    }
1434
  }
1435

    
1436
  return $form;
1437
}
1438

    
1439
/**
1440
 * Rearrange the order of variants.
1441
 */
1442
function page_manager_handler_import($form, &$form_state) {
1443
  $form['title'] = array(
1444
    '#type' => 'textfield',
1445
    '#title' => t('Variant name'),
1446
    '#description' => t('Enter the name of the new variant.'),
1447
  );
1448

    
1449
  if (user_access('use PHP for settings')) {
1450
    $form['object'] = array(
1451
      '#type' => 'textarea',
1452
      '#title' => t('Paste variant code here'),
1453
      '#rows' => 15,
1454
    );
1455
  }
1456
  // Users ordinarily can't get here without the PHP block visibility perm.
1457
  // In case they somehow do, though, disable the form widget for extra safety.
1458
  else {
1459
    $form['shoveoff'] = array(
1460
      '#markup' => '<div>' . t('You do not have sufficient permissions to perform this action.') . '</div>',
1461
    );
1462
  }
1463

    
1464
  return $form;
1465
}
1466

    
1467
/**
1468
 * Make sure that an import actually provides a handler.
1469
 */
1470
function page_manager_handler_import_validate($form, &$form_state) {
1471
  if (!user_access('use PHP for settings')) {
1472
    form_error($form['shoveoff'], t('You account permissions do not permit you to import.'));
1473
    return;
1474
  }
1475
  ob_start();
1476
  eval($form_state['values']['object']);
1477
  ob_end_clean();
1478

    
1479
  if (empty($handler)) {
1480
    $errors = ob_get_contents();
1481
    if (empty($errors)) {
1482
      $errors = t('No variant found.');
1483
    }
1484

    
1485
    form_error($form['object'], t('Unable to get a variant from the import. Errors reported: @errors', array('@errors' => $errors)));
1486
  }
1487

    
1488
  $form_state['handler'] = $handler;
1489
}
1490

    
1491
/**
1492
 * Clone an existing task handler into a new handler.
1493
 */
1494
function page_manager_handler_import_submit(&$form, &$form_state) {
1495
  $handler = $form_state['handler'];
1496

    
1497
  page_manager_handler_add_to_page($form_state['page'], $handler, $form_state['values']['title']);
1498

    
1499
  $plugin = page_manager_get_task_handler($handler->handler);
1500
  // It has no forms at all. Add the variant and go to its first operation.
1501
  $keys = array_keys($plugin['operations']);
1502
  $form_state['new trail'] = array('handlers', $handler->name, reset($keys));
1503
}
1504

    
1505
/**
1506
 * Rearrange the order of variants.
1507
 */
1508
function page_manager_handler_rearrange($form, &$form_state) {
1509
  $page = $form_state['page'];
1510

    
1511
  $form['handlers'] = array('#tree' => TRUE);
1512

    
1513
  foreach ($page->handler_info as $id => $info) {
1514
    if ($info['changed'] & PAGE_MANAGER_CHANGED_DELETED) {
1515
      continue;
1516
    }
1517
    $handler = $page->handlers[$id];
1518
    $plugin = page_manager_get_task_handler($handler->handler);
1519

    
1520
    $form['handlers'][$id]['title'] = array(
1521
      '#markup' => page_manager_get_handler_title($plugin, $handler, $page->task, $page->subtask_id),
1522
    );
1523

    
1524
    $form['handlers'][$id]['weight'] = array(
1525
      '#type' => 'weight',
1526
      '#default_value' => $info['weight'],
1527
      '#delta' => 30,
1528
    );
1529
  }
1530

    
1531
  return $form;
1532
}
1533

    
1534
function page_manager_handler_rearrange_submit(&$form, &$form_state) {
1535
  $handler_info = &$form_state['page']->handler_info;
1536

    
1537
  foreach ($form_state['values']['handlers'] as $id => $info) {
1538
    if ($handler_info[$id]['weight'] = $info['weight']) {
1539
      $handler_info[$id]['weight'] = $info['weight'];
1540
      $handler_info[$id]['changed'] |= PAGE_MANAGER_CHANGED_MOVED;
1541
    }
1542
  }
1543

    
1544
  // Sort the new cache.
1545
  uasort($handler_info, '_page_manager_handler_sort');
1546

    
1547
}
1548

    
1549
/**
1550
 * Used as a callback to uasort to sort the task cache by weight.
1551
 *
1552
 * The 'name' field is used as a backup when weights are the same, which
1553
 * can happen when multiple modules put items out there at the same
1554
 * weight.
1555
 */
1556
function _page_manager_handler_sort($a, $b) {
1557
  if ($a['weight'] < $b['weight']) {
1558
    return -1;
1559
  }
1560
  elseif ($a['weight'] > $b['weight']) {
1561
    return 1;
1562
  }
1563
  elseif ($a['name'] < $b['name']) {
1564
    return -1;
1565
  }
1566
  elseif ($a['name'] > $b['name']) {
1567
    return 1;
1568
  }
1569
}
1570

    
1571
/**
1572
 * Rearrange the order of variants.
1573
 */
1574
function page_manager_handler_delete($form, &$form_state) {
1575
  if ($form_state['handler']->type == t('Overridden')) {
1576
    $text = t('Reverting the variant will delete the variant that is in the database, reverting it to the original default variant. This deletion will not be made permanent until you click Save.');
1577
  }
1578
  else {
1579
    $text = t('Are you sure you want to delete this variant? This deletion will not be made permanent until you click Save.');
1580
  }
1581
  $form['markup'] = array(
1582
    '#markup' => '<p>' . $text . '</p>',
1583
  );
1584

    
1585
  return $form;
1586
}
1587

    
1588
/**
1589
 * Submit handler to delete a view.
1590
 */
1591
function page_manager_handler_delete_submit(&$form, &$form_state) {
1592
  $form_state['page']->handler_info[$form_state['handler_id']]['changed'] |= PAGE_MANAGER_CHANGED_DELETED;
1593
  $form_state['new trail'] = array('summary');
1594
}
1595

    
1596
/**
1597
 * Entry point to export a page.
1598
 */
1599
function page_manager_handler_export($form, &$form_state) {
1600
  $export = page_manager_export_task_handler($form_state['handler']);
1601

    
1602
  $lines = substr_count($export, "\n");
1603
  $form['code'] = array(
1604
    '#type' => 'textarea',
1605
    '#default_value' => $export,
1606
    '#rows' => $lines,
1607
  );
1608

    
1609
  unset($form['buttons']);
1610
  return $form;
1611
}
1612

    
1613
/**
1614
 * Rearrange the order of variants.
1615
 */
1616
function page_manager_handler_clone($form, &$form_state) {
1617
  // This provides its own button because it does something totally different.
1618
  $form['title'] = array(
1619
    '#type' => 'textfield',
1620
    '#title' => t('Variant name'),
1621
    '#description' => t('Enter the name of the new variant.'),
1622
  );
1623

    
1624
  return $form;
1625
}
1626

    
1627
/**
1628
 * Clone an existing task handler into a new handler.
1629
 */
1630
function page_manager_handler_clone_submit(&$form, &$form_state) {
1631
  $export = page_manager_export_task_handler($form_state['handler']);
1632
  ob_start();
1633
  eval($export);
1634
  ob_end_clean();
1635

    
1636
  page_manager_handler_add_to_page($form_state['page'], $handler, $form_state['values']['title']);
1637

    
1638
  $plugin = page_manager_get_task_handler($handler->handler);
1639
  // It has no forms at all. Add the variant and go to its first operation.
1640
  $keys = array_keys($plugin['operations']);
1641
  $form_state['new trail'] = array('handlers', $handler->name, reset($keys));
1642
}
1643

    
1644
/**
1645
 * Form to enable a handler.
1646
 */
1647
function page_manager_handler_enable($form, &$form_state) {
1648
  $form['markup'] = array(
1649
    '#markup' => t('This variant is currently disabled. Enabling it will make it available in your system. This will not take effect until you save this page.'),
1650
  );
1651

    
1652
  return $form;
1653
}
1654

    
1655
/**
1656
 * Enable the page after it has been confirmed.
1657
 */
1658
function page_manager_handler_enable_submit(&$form, &$form_state) {
1659
  $form_state['handler']->disabled = FALSE;
1660
  $form_state['page']->handler_info[$form_state['handler_id']]['disabled'] = FALSE;
1661
  $form_state['page']->handler_info[$form_state['handler_id']]['changed'] |= PAGE_MANAGER_CHANGED_STATUS;
1662
  $form_state['new trail'] = array('handlers', $form_state['handler_id'], 'actions', 'disable');
1663
}
1664

    
1665
/**
1666
 * Form to disable a page.
1667
 */
1668
function page_manager_handler_disable($form, &$form_state) {
1669
  $form['markup'] = array(
1670
    '#markup' => t('This variant is currently enabled. Disabling it will make it unavailable in your system, and it will not be used. This will not take effect until you save this page.'),
1671
  );
1672

    
1673
  return $form;
1674
}
1675

    
1676
/**
1677
 * Form to disable a page.
1678
 */
1679
function page_manager_handler_summary($form, &$form_state) {
1680
  $handler = $form_state['handler'];
1681
  $page = $form_state['page'];
1682
  $plugin = page_manager_get_task_handler($handler->handler);
1683

    
1684
  $form['markup'] = array(
1685
    '#markup' => page_manager_get_handler_summary($plugin, $handler, $page, FALSE),
1686
  );
1687

    
1688
  return $form;
1689
}
1690

    
1691
/**
1692
 * Disable the page after it has been confirmed.
1693
 */
1694
function page_manager_handler_disable_submit(&$form, &$form_state) {
1695
  $form_state['handler']->disabled = TRUE;
1696
  $form_state['page']->handler_info[$form_state['handler_id']]['disabled'] = TRUE;
1697
  $form_state['page']->handler_info[$form_state['handler_id']]['changed'] |= PAGE_MANAGER_CHANGED_STATUS;
1698
  $form_state['new trail'] = array('handlers', $form_state['handler_id'], 'actions', 'enable');
1699
}
1700

    
1701
/**
1702
 * Break the lock on a page so that it can be edited.
1703
 */
1704
function page_manager_break_lock($form, &$form_state) {
1705
  $form['markup'] = array(
1706
    '#markup' => t('Breaking the lock on this page will <strong>discard</strong> any pending changes made by the locking user. Are you REALLY sure you want to do this?')
1707
  );
1708

    
1709
  return $form;
1710
}
1711

    
1712
/**
1713
 * Submit to break the lock on a page.
1714
 */
1715
function page_manager_break_lock_submit(&$form, &$form_state) {
1716
  $page = &$form_state['page'];
1717
  $form_state['page']->locked = FALSE;
1718
  ctools_object_cache_clear_all('page_manager_page', $page->task_name);
1719
  $form_state['do not cache'] = TRUE;
1720
  drupal_set_message(t('The lock has been cleared and all changes discarded. You may now make changes to this page.'));
1721

    
1722
  $form_state['new trail'] = array('summary');
1723
}
1724

    
1725
/**
1726
 * Form to enable a page.
1727
 */
1728
function page_manager_enable_form($form, &$form_state) {
1729
  $form['markup'] = array(
1730
    '#markup' => t('Enabling this page will immediately make it available in your system (there is no need to wait for a save.)'),
1731
  );
1732

    
1733
  return $form;
1734
}
1735

    
1736
/**
1737
 * Enable the page after it has been confirmed.
1738
 */
1739
function page_manager_enable_form_submit(&$form, &$form_state) {
1740
  $page = &$form_state['page'];
1741
  if ($function = ctools_plugin_get_function($page->subtask, 'enable callback')) {
1742
    $result = $function($page, FALSE);
1743
    menu_rebuild();
1744
  }
1745
  $form_state['new trail'] = array('actions', 'disable');
1746

    
1747
  // We don't want to cause this to cache if it wasn't already. If it was
1748
  // cached, however, we want to update the enabled state.
1749
  if (empty($form_state['page']->changed)) {
1750
    $form_state['do not cache'] = TRUE;
1751
  }
1752
}
1753

    
1754
/**
1755
 * Form to disable a page.
1756
 */
1757
function page_manager_disable_form($form, &$form_state) {
1758
  $form['markup'] = array(
1759
    '#markup' => t('Disabling this page will immediately make it unavailable in your system (there is no need to wait for a save.)'),
1760
  );
1761

    
1762
  return $form;
1763
}
1764

    
1765
/**
1766
 * Disable the page after it has been confirmed.
1767
 */
1768
function page_manager_disable_form_submit(&$form, &$form_state) {
1769
  $page = &$form_state['page'];
1770
  if ($function = ctools_plugin_get_function($page->subtask, 'enable callback')) {
1771
    $result = $function($page, TRUE);
1772
      menu_rebuild();
1773
    $form_state['new trail'] = array('actions', 'enable');
1774

    
1775
    // We don't want to cause this to cache if it wasn't already. If it was
1776
    // cached, however, we want to update the enabled state.
1777
    if (empty($form_state['page']->changed)) {
1778
      $form_state['do not cache'] = TRUE;
1779
    }
1780
  }
1781
}
1782

    
1783
/**
1784
 * Print the summary information for a page.
1785
 */
1786
function page_manager_page_summary($form, &$form_state) {
1787
  $page = $form_state['page'];
1788

    
1789
  $output = '';
1790

    
1791
/*
1792
  if (isset($form_state['subtask']['admin title'])) {
1793
    $form_state['title'] = $form_state['subtask']['admin title'];
1794
  }
1795
*/
1796

    
1797
  if (isset($form_state['subtask']['admin description'])) {
1798
    $output .= '<div class="description">' . $form_state['subtask']['admin description'] . '</div>';
1799
  }
1800

    
1801
  $output .= page_manager_get_page_summary($page->task, $page->subtask);
1802

    
1803
  if (!empty($page->handlers)) {
1804
    foreach ($page->handler_info as $id => $info) {
1805
      if ($info['changed'] & PAGE_MANAGER_CHANGED_DELETED) {
1806
        continue;
1807
      }
1808

    
1809
      $handler = $page->handlers[$id];
1810
      $plugin = page_manager_get_task_handler($handler->handler);
1811

    
1812
      $output .= '<div class="handler-summary">';
1813
      $output .= page_manager_get_handler_summary($plugin, $handler, $page);
1814
      $output .= '</div>';
1815

    
1816
    }
1817
  }
1818
  else {
1819
    $output .= '<p>' . t('This page has no variants and thus no output of its own.') . '</p>';
1820
  }
1821

    
1822
  $links = array(
1823
    array(
1824
      'title' => ' &raquo; ' . t('Add a new variant'),
1825
      'href' => page_manager_edit_url($page->task_name, array('actions', 'add')),
1826
      'html' => TRUE,
1827
    ),
1828
  );
1829

    
1830
  $output .= '<div class="links">' . theme('links', array('links' => $links)) . '</div>';
1831
  $form['markup'] = array(
1832
    '#markup' => $output,
1833
  );
1834

    
1835
  return $form;
1836
}
1837

    
1838
/**
1839
 * Menu callback to enable or disable a page
1840
 */
1841
function page_manager_enable_page($disable, $js, $page) {
1842
  if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], $page->task_name)) {
1843
    return MENU_ACCESS_DENIED;
1844
  }
1845
  if ($page->locked) {
1846
    if ($disable) {
1847
      drupal_set_message(t('Unable to disable due to lock.'));
1848
    }
1849
    else {
1850
      drupal_set_message(t('Unable to enable due to lock.'));
1851
    }
1852
  }
1853
  else {
1854
    if ($function = ctools_plugin_get_function($page->subtask, 'enable callback')) {
1855
      $result = $function($page, $disable);
1856
      menu_rebuild();
1857

    
1858
      // We want to re-cache this if it's changed so that status is properly
1859
      // updated on the changed form.
1860
      if (!empty($page->changed)) {
1861
        page_manager_set_page_cache($page);
1862
      }
1863
    }
1864
  }
1865

    
1866
  // For now $js is not actually in use on this.
1867
  drupal_goto('admin/structure/pages');
1868
}