| [ Index ] |
PHP Cross Reference of Drupal 6 (yi-drupal) |
[Summary view] [Print] [Text view]
1 <?php 2 // $Id: ctools_export_ui.class.php,v 1.1.2.20 2010/10/15 21:05:55 merlinofchaos Exp $ 3 4 /** 5 * Base class for export UI. 6 */ 7 class ctools_export_ui { 8 var $plugin; 9 var $name; 10 var $options = array(); 11 12 /** 13 * Fake constructor -- this is easier to deal with than the real 14 * constructor because we are retaining PHP4 compatibility, which 15 * would require all child classes to implement their own constructor. 16 */ 17 function init($plugin) { 18 ctools_include('export'); 19 20 $this->plugin = $plugin; 21 } 22 23 /** 24 * Get a page title for the current page from our plugin strings. 25 */ 26 function get_page_title($op, $item = NULL) { 27 if (empty($this->plugin['strings']['title'][$op])) { 28 return; 29 } 30 31 // Replace %title that might be there with the exportable title. 32 $title = $this->plugin['strings']['title'][$op]; 33 if (!empty($item)) { 34 $export_key = $this->plugin['export']['key']; 35 $title = (str_replace('%title', check_plain($item->{$export_key}), $title)); 36 } 37 38 return $title; 39 } 40 41 /** 42 * Add text on the top of the page. 43 */ 44 function help_area($form_state) { 45 // If needed add advanced help strings. 46 $output = ''; 47 if (!empty($this->plugin['use advanced help'])) { 48 $config = $this->plugin['advanced help']; 49 if ($config['enabled']) { 50 $output = theme('advanced_help_topic', $config['module'], $config['topic']); 51 $output .= ' ' . $this->plugin['strings']['advanced help']['enabled']; 52 } 53 else { 54 $output = $this->plugin['strings']['advanced help']['disabled']; 55 } 56 } 57 return $output; 58 } 59 60 // ------------------------------------------------------------------------ 61 // Menu item manipulation 62 63 /** 64 * hook_menu() entry point. 65 * 66 * Child implementations that need to add or modify menu items should 67 * probably call parent::hook_menu($items) and then modify as needed. 68 */ 69 function hook_menu(&$items) { 70 // During upgrades, the schema can be empty as this is called prior to 71 // actual update functions being run. Ensure that we can cope with this 72 // situation. 73 if (empty($this->plugin['schema'])) { 74 return; 75 } 76 77 $prefix = ctools_export_ui_plugin_base_path($this->plugin); 78 79 if (isset($this->plugin['menu']['items']) && is_array($this->plugin['menu']['items'])) { 80 $my_items = array(); 81 foreach ($this->plugin['menu']['items'] as $item) { 82 // Add menu item defaults. 83 $item += array( 84 'file' => 'export-ui.inc', 85 'file path' => drupal_get_path('module', 'ctools') . '/includes', 86 ); 87 88 $path = !empty($item['path']) ? $prefix . '/' . $item['path'] : $prefix; 89 unset($item['path']); 90 $my_items[$path] = $item; 91 } 92 $items += $my_items; 93 } 94 } 95 96 /** 97 * Menu callback to determine if an operation is accessible. 98 * 99 * This function enforces a basic access check on the configured perm 100 * string, and then additional checks as needed. 101 * 102 * @param $op 103 * The 'op' of the menu item, which is defined by 'allowed operations' 104 * and embedded into the arguments in the menu item. 105 * @param $item 106 * If an op that works on an item, then the item object, otherwise NULL. 107 * 108 * @return 109 * TRUE if the current user has access, FALSE if not. 110 */ 111 function access($op, $item) { 112 if (!user_access($this->plugin['access'])) { 113 return FALSE; 114 } 115 116 // More fine-grained access control: 117 if ($op == 'add' && !user_access($this->plugin['create access'])) { 118 return FALSE; 119 } 120 121 // More fine-grained access control: 122 if (($op == 'revert' || $op == 'delete') && !user_access($this->plugin['delete access'])) { 123 return FALSE; 124 } 125 126 // If we need to do a token test, do it here. 127 if (!empty($this->plugin['allowed operations'][$op]['token']) && (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], $op))) { 128 return FALSE; 129 } 130 131 switch ($op) { 132 case 'import': 133 return user_access('use PHP for block visibility'); 134 case 'revert': 135 return ($item->export_type & EXPORT_IN_DATABASE) && ($item->export_type & EXPORT_IN_CODE); 136 case 'delete': 137 return ($item->export_type & EXPORT_IN_DATABASE) && !($item->export_type & EXPORT_IN_CODE); 138 case 'disable': 139 return empty($item->disabled); 140 case 'enable': 141 return !empty($item->disabled); 142 default: 143 return TRUE; 144 } 145 } 146 147 // ------------------------------------------------------------------------ 148 // These methods are the API for generating the list of exportable items. 149 150 /** 151 * Master entry point for handling a list. 152 * 153 * It is unlikely that a child object will need to override this method, 154 * unless the listing mechanism is going to be highly specialized. 155 */ 156 function list_page($js, $input) { 157 $this->items = ctools_export_crud_load_all($this->plugin['schema'], $js); 158 159 // Respond to a reset command by clearing session and doing a drupal goto 160 // back to the base URL. 161 if (isset($input['op']) && $input['op'] == t('Reset')) { 162 unset($_SESSION['ctools_export_ui'][$this->plugin['name']]); 163 if (!$js) { 164 return drupal_goto($_GET['q']); 165 } 166 // clear everything but form id, form build id and form token: 167 $keys = array_keys($input); 168 foreach ($keys as $id) { 169 if (!in_array($id, array('form_id', 'form_build_id', 'form_token'))) { 170 unset($input[$id]); 171 } 172 } 173 $replace_form = TRUE; 174 } 175 176 // If there is no input, check to see if we have stored input in the 177 // session. 178 if (!isset($input['form_id'])) { 179 if (isset($_SESSION['ctools_export_ui'][$this->plugin['name']]) && is_array($_SESSION['ctools_export_ui'][$this->plugin['name']])) { 180 $input = $_SESSION['ctools_export_ui'][$this->plugin['name']]; 181 } 182 } 183 else { 184 $_SESSION['ctools_export_ui'][$this->plugin['name']] = $input; 185 unset($_SESSION['ctools_export_ui'][$this->plugin['name']]['q']); 186 } 187 188 // This is where the form will put the output. 189 $this->rows = array(); 190 $this->sorts = array(); 191 192 $form_state = array( 193 'plugin' => $this->plugin, 194 'input' => $input, 195 'rerender' => TRUE, 196 'no_redirect' => TRUE, 197 'object' => &$this, 198 ); 199 200 $help_area = $this->help_area($form_state); 201 202 ctools_include('form'); 203 $form = ctools_build_form('ctools_export_ui_list_form', $form_state); 204 205 $output = $this->list_header($form_state) . $this->list_render($form_state) . $this->list_footer($form_state); 206 207 if (!$js) { 208 $this->list_css(); 209 return $help_area . $form . $output; 210 } 211 212 ctools_include('ajax'); 213 $commands = array(); 214 $commands[] = ctools_ajax_command_replace('#ctools-export-ui-list-items', $output); 215 if (!empty($replace_form)) { 216 $commands[] = ctools_ajax_command_replace('#ctools-export-ui-list-form', $form); 217 } 218 ctools_ajax_render($commands); 219 } 220 221 /** 222 * Create the filter/sort form at the top of a list of exports. 223 * 224 * This handles the very default conditions, and most lists are expected 225 * to override this and call through to parent::list_form() in order to 226 * get the base form and then modify it as necessary to add search 227 * gadgets for custom fields. 228 */ 229 function list_form(&$form, &$form_state) { 230 // This forces the form to *always* treat as submitted which is 231 // necessary to make it work. 232 $form['#token'] = FALSE; 233 if (empty($form_state['input'])) { 234 $form["#post"] = TRUE; 235 } 236 237 // Add the 'q' in if we are not using clean URLs or it can get lost when 238 // using this kind of form. 239 if (!variable_get('clean_url', FALSE)) { 240 $form['q'] = array( 241 '#type' => 'hidden', 242 '#value' => $_GET['q'], 243 ); 244 } 245 246 $all = array('all' => t('- All -')); 247 248 $form['top row'] = array( 249 '#prefix' => '<div class="ctools-export-ui-row ctools-export-ui-top-row clear-block">', 250 '#suffix' => '</div>', 251 ); 252 253 $form['bottom row'] = array( 254 '#prefix' => '<div class="ctools-export-ui-row ctools-export-ui-bottom-row clear-block">', 255 '#suffix' => '</div>', 256 ); 257 258 $form['top row']['storage'] = array( 259 '#type' => 'select', 260 '#title' => t('Storage'), 261 '#options' => $all + array( 262 t('Normal') => t('Normal'), 263 t('Default') => t('Default'), 264 t('Overridden') => t('Overridden'), 265 ), 266 '#default_value' => 'all', 267 ); 268 269 $form['top row']['disabled'] = array( 270 '#type' => 'select', 271 '#title' => t('Enabled'), 272 '#options' => $all + array( 273 '0' => t('Enabled'), 274 '1' => t('Disabled') 275 ), 276 '#default_value' => 'all', 277 ); 278 279 $form['top row']['search'] = array( 280 '#type' => 'textfield', 281 '#title' => t('Search'), 282 ); 283 284 $form['bottom row']['order'] = array( 285 '#type' => 'select', 286 '#title' => t('Sort by'), 287 '#options' => $this->list_sort_options(), 288 '#default_value' => 'disabled', 289 ); 290 291 $form['bottom row']['sort'] = array( 292 '#type' => 'select', 293 '#title' => t('Order'), 294 '#options' => array( 295 'asc' => t('Up'), 296 'desc' => t('Down'), 297 ), 298 '#default_value' => 'asc', 299 ); 300 301 $form['bottom row']['submit'] = array( 302 '#type' => 'submit', 303 '#id' => 'ctools-export-ui-list-items-apply', 304 '#value' => t('Apply'), 305 '#attributes' => array('class' => 'ctools-use-ajax ctools-auto-submit-click'), 306 ); 307 308 $form['bottom row']['reset'] = array( 309 '#type' => 'submit', 310 '#id' => 'ctools-export-ui-list-items-apply', 311 '#value' => t('Reset'), 312 '#attributes' => array('class' => 'ctools-use-ajax'), 313 ); 314 315 ctools_add_js('ajax-responder'); 316 ctools_add_js('auto-submit'); 317 drupal_add_js('misc/jquery.form.js'); 318 319 $form['#prefix'] = '<div class="clear-block">'; 320 $form['#suffix'] = '</div>'; 321 $form['#attributes'] = array('class' => 'ctools-auto-submit-full-form'); 322 } 323 324 /** 325 * Validate the filter/sort form. 326 * 327 * It is very rare that a filter form needs validation, but if it is 328 * needed, override this. 329 */ 330 function list_form_validate(&$form, &$form_state) { } 331 332 /** 333 * Submit the filter/sort form. 334 * 335 * This submit handler is actually responsible for building up all of the 336 * rows that will later be rendered, since it is doing the filtering and 337 * sorting. 338 * 339 * For the most part, you should not need to override this method, as the 340 * fiddly bits call through to other functions. 341 */ 342 function list_form_submit(&$form, &$form_state) { 343 // Filter and re-sort the pages. 344 $plugin = $this->plugin; 345 346 $prefix = ctools_export_ui_plugin_base_path($plugin); 347 348 foreach ($this->items as $name => $item) { 349 // Call through to the filter and see if we're going to render this 350 // row. If it returns TRUE, then this row is filtered out. 351 if ($this->list_filter($form_state, $item)) { 352 continue; 353 } 354 355 // Note: Creating this list seems a little clumsy, but can't think of 356 // better ways to do this. 357 $allowed_operations = drupal_map_assoc(array_keys($plugin['allowed operations'])); 358 $not_allowed_operations = array('import'); 359 360 if ($item->type == t('Normal')) { 361 $not_allowed_operations[] = 'revert'; 362 } 363 elseif ($item->type == t('Overridden')) { 364 $not_allowed_operations[] = 'delete'; 365 } 366 else { 367 $not_allowed_operations[] = 'revert'; 368 $not_allowed_operations[] = 'delete'; 369 } 370 371 $not_allowed_operations[] = empty($item->disabled) ? 'enable' : 'disable'; 372 373 foreach ($not_allowed_operations as $op) { 374 // Remove the operations that are not allowed for the specific 375 // exportable. 376 unset($allowed_operations[$op]); 377 } 378 379 $operations = array(); 380 381 foreach ($allowed_operations as $op) { 382 $operations[$op] = array( 383 'title' => $plugin['allowed operations'][$op]['title'], 384 'href' => ctools_export_ui_plugin_menu_path($plugin, $op, $name), 385 ); 386 if (!empty($plugin['allowed operations'][$op]['ajax'])) { 387 $operations[$op]['attributes'] = array('class' => 'ctools-use-ajax'); 388 } 389 if (!empty($plugin['allowed operations'][$op]['token'])) { 390 $operations[$op]['query'] = array('token' => drupal_get_token($op)); 391 } 392 } 393 394 $this->list_build_row($item, $form_state, $operations); 395 } 396 397 // Now actually sort 398 if ($form_state['values']['sort'] == 'desc') { 399 arsort($this->sorts); 400 } 401 else { 402 asort($this->sorts); 403 } 404 405 // Nuke the original. 406 $rows = $this->rows; 407 $this->rows = array(); 408 // And restore. 409 foreach ($this->sorts as $name => $title) { 410 $this->rows[$name] = $rows[$name]; 411 } 412 } 413 414 /** 415 * Determine if a row should be filtered out. 416 * 417 * This handles the default filters for the export UI list form. If you 418 * added additional filters in list_form() then this is where you should 419 * handle them. 420 * 421 * @return 422 * TRUE if the item should be excluded. 423 */ 424 function list_filter($form_state, $item) { 425 if ($form_state['values']['storage'] != 'all' && $form_state['values']['storage'] != $item->type) { 426 return TRUE; 427 } 428 429 if ($form_state['values']['disabled'] != 'all' && $form_state['values']['disabled'] != !empty($item->disabled)) { 430 return TRUE; 431 } 432 433 if ($form_state['values']['search']) { 434 $search = strtolower($form_state['values']['search']); 435 foreach ($this->list_search_fields() as $field) { 436 if (strpos(strtolower($item->$field), $search) !== FALSE) { 437 $hit = TRUE; 438 break; 439 } 440 } 441 if (empty($hit)) { 442 return TRUE; 443 } 444 } 445 } 446 447 /** 448 * Provide a list of fields to test against for the default "search" widget. 449 * 450 * This widget will search against whatever fields are configured here. By 451 * default it will attempt to search against the name, title and description fields. 452 */ 453 function list_search_fields() { 454 $fields = array( 455 $this->plugin['export']['key'], 456 ); 457 458 if (!empty($this->plugin['export']['admin_title'])) { 459 $fields[] = $this->plugin['export']['admin_title']; 460 } 461 if (!empty($this->plugin['export']['admin_description'])) { 462 $fields[] = $this->plugin['export']['admin_description']; 463 } 464 465 return $fields; 466 } 467 468 /** 469 * Provide a list of sort options. 470 * 471 * Override this if you wish to provide more or change how these work. 472 * The actual handling of the sorting will happen in build_row(). 473 */ 474 function list_sort_options() { 475 if (!empty($this->plugin['export']['admin_title'])) { 476 $options = array( 477 'disabled' => t('Enabled, title'), 478 $this->plugin['export']['admin_title'] => t('Title'), 479 ); 480 } 481 else { 482 $options = array( 483 'disabled' => t('Enabled, name'), 484 ); 485 } 486 487 $options += array( 488 'name' => t('Name'), 489 'storage' => t('Storage'), 490 ); 491 492 return $options; 493 } 494 495 /** 496 * Add listing CSS to the page. 497 * 498 * Override this if you need custom CSS for your list. 499 */ 500 function list_css() { 501 ctools_add_css('export-ui-list'); 502 } 503 504 /** 505 * Build a row based on the item. 506 * 507 * By default all of the rows are placed into a table by the render 508 * method, so this is building up a row suitable for theme('table'). 509 * This doesn't have to be true if you override both. 510 */ 511 function list_build_row($item, &$form_state, $operations) { 512 // Set up sorting 513 $name = $item->{$this->plugin['export']['key']}; 514 515 // Note: $item->type should have already been set up by export.inc so 516 // we can use it safely. 517 switch ($form_state['values']['order']) { 518 case 'disabled': 519 $this->sorts[$name] = empty($item->disabled) . $name; 520 break; 521 case 'title': 522 $this->sorts[$name] = $item->{$this->plugin['export']['admin_title']}; 523 break; 524 case 'name': 525 $this->sorts[$name] = $name; 526 break; 527 case 'storage': 528 $this->sorts[$name] = $item->type . $name; 529 break; 530 } 531 532 $this->rows[$name]['data'] = array(); 533 $this->rows[$name]['class'] = !empty($item->disabled) ? 'ctools-export-ui-disabled' : 'ctools-export-ui-enabled'; 534 535 // If we have an admin title, make it the first row. 536 if (!empty($this->plugin['export']['admin_title'])) { 537 $this->rows[$name]['data'][] = array('data' => check_plain($item->{$this->plugin['export']['admin_title']}), 'class' => 'ctools-export-ui-title'); 538 } 539 $this->rows[$name]['data'][] = array('data' => check_plain($name), 'class' => 'ctools-export-ui-name'); 540 $this->rows[$name]['data'][] = array('data' => check_plain($item->type), 'class' => 'ctools-export-ui-storage'); 541 $this->rows[$name]['data'][] = array('data' => theme('links', $operations), 'class' => 'ctools-export-ui-operations'); 542 543 // Add an automatic mouseover of the description if one exists. 544 if (!empty($this->plugin['export']['admin_description'])) { 545 $this->rows[$name]['title'] = $item->{$this->plugin['export']['admin_description']}; 546 } 547 } 548 549 /** 550 * Provide the table header. 551 * 552 * If you've added columns via list_build_row() but are still using a 553 * table, override this method to set up the table header. 554 */ 555 function list_table_header() { 556 $header = array(); 557 if (!empty($this->plugin['export']['admin_title'])) { 558 $header[] = array('data' => t('Title'), 'class' => 'ctools-export-ui-title'); 559 } 560 561 $header[] = array('data' => t('Name'), 'class' => 'ctools-export-ui-name'); 562 $header[] = array('data' => t('Storage'), 'class' => 'ctools-export-ui-storage'); 563 $header[] = array('data' => t('Operations'), 'class' => 'ctools-export-ui-operations'); 564 565 return $header; 566 } 567 568 /** 569 * Render all of the rows together. 570 * 571 * By default we place all of the rows in a table, and this should be the 572 * way most lists will go. 573 * 574 * Whatever you do if this method is overridden, the ID is important for AJAX 575 * so be sure it exists. 576 */ 577 function list_render(&$form_state) { 578 return theme('table', $this->list_table_header(), $this->rows, array('id' => 'ctools-export-ui-list-items')); 579 } 580 581 /** 582 * Render a header to go before the list. 583 * 584 * This will appear after the filter/sort widgets. 585 */ 586 function list_header($form_state) { } 587 588 /** 589 * Render a footer to go after thie list. 590 * 591 * This is a good place to add additional links. 592 */ 593 function list_footer($form_state) { } 594 595 // ------------------------------------------------------------------------ 596 // These methods are the API for adding/editing exportable items 597 598 function add_page($js, $input, $step = NULL) { 599 drupal_set_title($this->get_page_title('add')); 600 601 // If a step not set, they are trying to create a new item. If a step 602 // is set, they're in the process of creating an item. 603 if (!empty($this->plugin['use wizard']) && !empty($step)) { 604 $item = $this->edit_cache_get(NULL, 'add'); 605 } 606 if (empty($item)) { 607 $item = ctools_export_crud_new($this->plugin['schema']); 608 } 609 610 $form_state = array( 611 'plugin' => $this->plugin, 612 'object' => &$this, 613 'ajax' => $js, 614 'item' => $item, 615 'op' => 'add', 616 'form type' => 'add', 617 'rerender' => TRUE, 618 'no_redirect' => TRUE, 619 'step' => $step, 620 // Store these in case additional args are needed. 621 'function args' => func_get_args(), 622 ); 623 624 $output = $this->edit_execute_form($form_state); 625 if (!empty($form_state['executed'])) { 626 $export_key = $this->plugin['export']['key']; 627 drupal_goto(str_replace('%ctools_export_ui', $form_state['item']->{$export_key}, $this->plugin['redirect']['add'])); 628 } 629 630 return $output; 631 } 632 633 /** 634 * Main entry point to edit an item. 635 */ 636 function edit_page($js, $input, $item, $step = NULL) { 637 drupal_set_title($this->get_page_title('edit', $item)); 638 639 // Check to see if there is a cached item to get if we're using the wizard. 640 if (!empty($this->plugin['use wizard'])) { 641 $cached = $this->edit_cache_get($item, 'edit'); 642 if (!empty($cached)) { 643 $item = $cached; 644 } 645 } 646 647 $form_state = array( 648 'plugin' => $this->plugin, 649 'object' => &$this, 650 'ajax' => $js, 651 'item' => $item, 652 'op' => 'edit', 653 'form type' => 'edit', 654 'rerender' => TRUE, 655 'no_redirect' => TRUE, 656 'step' => $step, 657 // Store these in case additional args are needed. 658 'function args' => func_get_args(), 659 ); 660 661 $output = $this->edit_execute_form($form_state); 662 if (!empty($form_state['executed'])) { 663 $export_key = $this->plugin['export']['key']; 664 drupal_goto(str_replace('%ctools_export_ui', $form_state['item']->{$export_key}, $this->plugin['redirect']['edit'])); 665 } 666 667 return $output; 668 } 669 670 /** 671 * Main entry point to clone an item. 672 */ 673 function clone_page($js, $input, $original, $step = NULL) { 674 drupal_set_title($this->get_page_title('clone', $original)); 675 676 // If a step not set, they are trying to create a new clone. If a step 677 // is set, they're in the process of cloning an item. 678 if (!empty($this->plugin['use wizard']) && !empty($step)) { 679 $item = $this->edit_cache_get(NULL, 'clone'); 680 } 681 if (empty($item)) { 682 // To make a clone of an item, we first export it and then re-import it. 683 // Export the handler, which is a fantastic way to clean database IDs out of it. 684 $export = ctools_export_crud_export($this->plugin['schema'], $original); 685 $item = ctools_export_crud_import($this->plugin['schema'], $export); 686 $item->{$this->plugin['export']['key']} = 'clone_of_' . $item->{$this->plugin['export']['key']}; 687 } 688 689 // Tabs and breadcrumb disappearing, this helps alleviate through cheating. 690 ctools_include('menu'); 691 $trail = ctools_get_menu_trail(ctools_export_ui_plugin_base_path($this->plugin)); 692 menu_set_active_trail($trail); 693 $name = $original->{$this->plugin['export']['key']}; 694 695 $form_state = array( 696 'plugin' => $this->plugin, 697 'object' => &$this, 698 'ajax' => $js, 699 'item' => $item, 700 'op' => 'add', 701 'form type' => 'clone', 702 'original name' => $name, 703 'rerender' => TRUE, 704 'no_redirect' => TRUE, 705 'step' => $step, 706 // Store these in case additional args are needed. 707 'function args' => func_get_args(), 708 ); 709 710 $output = $this->edit_execute_form($form_state); 711 if (!empty($form_state['executed'])) { 712 $export_key = $this->plugin['export']['key']; 713 drupal_goto(str_replace('%ctools_export_ui', $form_state['item']->{$export_key}, $this->plugin['redirect']['clone'])); 714 } 715 716 return $output; 717 } 718 719 /** 720 * Execute the form. 721 * 722 * Add and Edit both funnel into this, but they have a few different 723 * settings. 724 */ 725 function edit_execute_form(&$form_state) { 726 if (!empty($this->plugin['use wizard'])) { 727 return $this->edit_execute_form_wizard($form_state); 728 } 729 else { 730 return $this->edit_execute_form_standard($form_state); 731 } 732 } 733 734 /** 735 * Execute the standard form for editing. 736 * 737 * By default, export UI will provide a single form for editing an object. 738 */ 739 function edit_execute_form_standard(&$form_state) { 740 ctools_include('form'); 741 $output = ctools_build_form('ctools_export_ui_edit_item_form', $form_state); 742 if (!empty($form_state['executed'])) { 743 $this->edit_save_form($form_state); 744 } 745 746 return $output; 747 } 748 749 /** 750 * Get the form info for the wizard. 751 * 752 * This gets the form info out of the plugin, then adds defaults based on 753 * how we want edit forms to work. 754 * 755 * Overriding this can allow child UIs to tweak this info for specialized 756 * wizards. 757 * 758 * @param array $form_state 759 * The already created form state. 760 */ 761 function get_wizard_info(&$form_state) { 762 if (!isset($form_state['step'])) { 763 $form_state['step'] = NULL; 764 } 765 766 $export_key = $this->plugin['export']['key']; 767 768 // When cloning, the name of the item being cloned is referenced in the 769 // path, not the name of this item. 770 if ($form_state['form type'] == 'clone') { 771 $name = $form_state['original name']; 772 } 773 else { 774 $name = $form_state['item']->{$export_key}; 775 } 776 777 $form_info = !empty($this->plugin['form info']) ? $this->plugin['form info'] : array(); 778 $form_info += array( 779 'id' => 'ctools_export_ui_edit', 780 'path' => ctools_export_ui_plugin_menu_path($this->plugin, $form_state['form type'], $name) . '/%step', 781 'show trail' => TRUE, 782 'free trail' => TRUE, 783 'show back' => $form_state['form type'] == 'add', 784 'show return' => FALSE, 785 'show cancel' => TRUE, 786 'finish callback' => 'ctools_export_ui_wizard_finish', 787 'next callback' => 'ctools_export_ui_wizard_next', 788 'back callback' => 'ctools_export_ui_wizard_back', 789 'cancel callback' => 'ctools_export_ui_wizard_cancel', 790 'order' => array(), 791 'import order' => array( 792 'import' => t('Import code'), 793 'settings' => t('Settings'), 794 ), 795 ); 796 797 // Set the order of forms based on the op if we have a specific one. 798 if (isset($form_info[$form_state['form type'] . ' order'])) { 799 $form_info['order'] = $form_info[$form_state['form type'] . ' order']; 800 } 801 802 // We have generic fallback forms we can use if they are not specified, 803 // and they automatically delegate back to the UI object. Use these if 804 // not specified. 805 foreach ($form_info['order'] as $key => $title) { 806 if (empty($form_info['forms'][$key])) { 807 $form_info['forms'][$key] = array( 808 'form id' => 'ctools_export_ui_edit_item_wizard_form', 809 ); 810 } 811 } 812 813 // 'free trail' means the wizard can freely go back and form from item 814 // via the trail and not with next/back buttons. 815 if ($form_state['form type'] == 'add' || ($form_state['form type'] == 'import' && empty($form_state['item']->{$export_key}))) { 816 $form_info['free trail'] = FALSE; 817 } 818 819 return $form_info; 820 } 821 822 /** 823 * Execute the wizard for editing. 824 * 825 * For complex objects, sometimes a wizard is needed. This is normally 826 * activated by setting 'use wizard' => TRUE in the plugin definition 827 * and then creating a 'form info' array to feed the wizard the data 828 * it needs. 829 * 830 * When creating this wizard, the plugin is responsible for defining all forms 831 * that will be utilized with the wizard. 832 * 833 * Using 'add order' or 'edit order' can be used to ensure that add/edit order 834 * is different. 835 */ 836 function edit_execute_form_wizard(&$form_state) { 837 $form_info = $this->get_wizard_info($form_state); 838 839 // If there aren't any forms set, fail. 840 if (empty($form_info['order'])) { 841 return MENU_NOT_FOUND; 842 } 843 844 // Figure out if this is a new instance of the wizard 845 if (empty($form_state['step'])) { 846 $form_state['step'] = reset(array_keys($form_info['order'])); 847 } 848 849 if (empty($form_info['order'][$form_state['step']]) && empty($form_info['forms'][$form_state['step']])) { 850 return MENU_NOT_FOUND; 851 } 852 853 ctools_include('wizard'); 854 $output = ctools_wizard_multistep_form($form_info, $form_state['step'], $form_state); 855 if (!empty($form_state['complete'])) { 856 $this->edit_save_form($form_state); 857 } 858 else if ($output && !empty($form_state['item']->export_ui_item_is_cached)) { 859 // @todo this should be in the plugin strings 860 drupal_set_message(t('You have unsaved changes. These changes will not be made permanent until you click <em>Save</em>.'), 'warning'); 861 } 862 863 // Unset the executed flag if any non-wizard button was pressed. Those 864 // buttons require special handling by whatever client is operating them. 865 if (!empty($form_state['executed']) && empty($form_state['clicked_button']['#wizard type'])) { 866 unset($form_state['executed']); 867 } 868 return $output; 869 } 870 871 /** 872 * Wizard 'back' callback when using a wizard to edit an item. 873 * 874 * The wizard callback delegates this back to the object. 875 */ 876 function edit_wizard_back(&$form_state) { 877 // This only exists so child implementations can use it. 878 } 879 880 /** 881 * Wizard 'next' callback when using a wizard to edit an item. 882 * 883 * The wizard callback delegates this back to the object. 884 */ 885 function edit_wizard_next(&$form_state) { 886 $this->edit_cache_set($form_state['item'], $form_state['form type']); 887 } 888 889 /** 890 * Wizard 'cancel' callback when using a wizard to edit an item. 891 * 892 * The wizard callback delegates this back to the object. 893 */ 894 function edit_wizard_cancel(&$form_state) { 895 $this->edit_cache_clear($form_state['item'], $form_state['form type']); 896 } 897 898 /** 899 * Wizard 'cancel' callback when using a wizard to edit an item. 900 * 901 * The wizard callback delegates this back to the object. 902 */ 903 function edit_wizard_finish(&$form_state) { 904 $form_state['complete'] = TRUE; 905 906 // If we are importing, and overwrite was selected, delete the original so 907 // that this one writes properly. 908 if ($form_state['form type'] == 'import' && !empty($form_state['item']->export_ui_allow_overwrite)) { 909 ctools_export_crud_delete($this->plugin['schema'], $form_state['item']); 910 } 911 912 $this->edit_cache_clear($form_state['item'], $form_state['form type']); 913 } 914 915 /** 916 * Retrieve the item currently being edited from the object cache. 917 */ 918 function edit_cache_get($item, $op = 'edit') { 919 ctools_include('object-cache'); 920 if (is_string($item)) { 921 $name = $item; 922 } 923 else { 924 $name = $this->edit_cache_get_key($item, $op); 925 } 926 927 $cache = ctools_object_cache_get('ctui_' . $this->plugin['name'], $name); 928 if ($cache) { 929 $cache->export_ui_item_is_cached = TRUE; 930 return $cache; 931 } 932 } 933 934 /** 935 * Cache the item currently currently being edited. 936 */ 937 function edit_cache_set($item, $op = 'edit') { 938 ctools_include('object-cache'); 939 $name = $this->edit_cache_get_key($item, $op); 940 return $this->edit_cache_set_key($item, $name); 941 } 942 943 function edit_cache_set_key($item, $name) { 944 return ctools_object_cache_set('ctui_' . $this->plugin['name'], $name, $item); 945 } 946 947 /** 948 * Clear the object cache for the currently edited item. 949 */ 950 function edit_cache_clear($item, $op = 'edit') { 951 ctools_include('object-cache'); 952 $name = $this->edit_cache_get_key($item, $op); 953 return ctools_object_cache_clear('ctui_' . $this->plugin['name'], $name); 954 } 955 956 /** 957 * Figure out what the cache key is for this object. 958 */ 959 function edit_cache_get_key($item, $op) { 960 $export_key = $this->plugin['export']['key']; 961 return $op == 'edit' ? $item->{$this->plugin['export']['key']} : "::$op"; 962 } 963 964 /** 965 * Called to save the final product from the edit form. 966 */ 967 function edit_save_form($form_state) { 968 $item = &$form_state['item']; 969 $export_key = $this->plugin['export']['key']; 970 971 $result = ctools_export_crud_save($this->plugin['schema'], $item); 972 973 if ($result) { 974 $message = str_replace('%title', check_plain($item->{$export_key}), $this->plugin['strings']['confirmation'][$form_state['op']]['success']); 975 drupal_set_message($message); 976 } 977 else { 978 $message = str_replace('%title', check_plain($item->{$export_key}), $this->plugin['strings']['confirmation'][$form_state['op']]['fail']); 979 drupal_set_message($message, 'error'); 980 } 981 } 982 983 /** 984 * Provide the actual editing form. 985 */ 986 function edit_form(&$form, &$form_state) { 987 $export_key = $this->plugin['export']['key']; 988 $item = $form_state['item']; 989 $schema = ctools_export_get_schema($this->plugin['schema']); 990 991 // TODO: Drupal 7 has a nifty method of auto guessing names from 992 // titles that is standard. We should integrate that here as a 993 // nice standard. 994 // Guess at a couple of our standard fields. 995 if (!empty($this->plugin['export']['admin_title'])) { 996 $form['info'][$this->plugin['export']['admin_title']] = array( 997 '#type' => 'textfield', 998 '#title' => t('Administrative title'), 999 '#description' => t('This will appear in the administrative interface to easily identify it.'), 1000 '#default_value' => $item->{$this->plugin['export']['admin_title']}, 1001 ); 1002 } 1003 1004 $form['info'][$export_key] = array( 1005 '#title' => t($schema['export']['key name']), 1006 '#type' => 'textfield', 1007 '#default_value' => $item->{$export_key}, 1008 '#description' => t('The unique ID for this @export.', array('@export' => $this->plugin['title singular'])), 1009 '#required' => TRUE, 1010 '#maxlength' => 255, 1011 ); 1012 1013 if ($form_state['op'] === 'edit') { 1014 $form['info'][$export_key]['#disabled'] = TRUE; 1015 $form['info'][$export_key]['#value'] = $item->{$export_key}; 1016 } 1017 else { 1018 $form['info'][$export_key]['#element_validate'] = array('ctools_export_ui_edit_name_validate'); 1019 } 1020 1021 if (!empty($this->plugin['export']['admin_description'])) { 1022 $form['info'][$this->plugin['export']['admin_description']] = array( 1023 '#type' => 'textarea', 1024 '#title' => t('Administrative description'), 1025 '#default_value' => $item->{$this->plugin['export']['admin_description']}, 1026 ); 1027 } 1028 1029 // Add plugin's form definitions. 1030 if (!empty($this->plugin['form']['settings'])) { 1031 // Pass $form by reference. 1032 $this->plugin['form']['settings']($form, $form_state); 1033 } 1034 1035 // Add the buttons if the wizard is not in use. 1036 if (empty($form_state['form_info'])) { 1037 // Make sure that whatever happens, the buttons go to the bottom. 1038 $form['buttons']['#weight'] = 100; 1039 1040 // Add buttons. 1041 $form['buttons']['submit'] = array( 1042 '#type' => 'submit', 1043 '#value' => t('Save'), 1044 ); 1045 1046 $form['buttons']['delete'] = array( 1047 '#type' => 'submit', 1048 '#value' => $item->export_type & EXPORT_IN_CODE ? t('Revert') : t('Delete'), 1049 '#access' => $form_state['op'] === 'edit' && $item->export_type & EXPORT_IN_DATABASE, 1050 '#submit' => array('ctools_export_ui_edit_item_form_delete'), 1051 ); 1052 } 1053 } 1054 1055 /** 1056 * Validate callback for the edit form. 1057 */ 1058 function edit_form_validate(&$form, &$form_state) { 1059 if (!empty($this->plugin['form']['validate'])) { 1060 // Pass $form by reference. 1061 $this->plugin['form']['validate']($form, $form_state); 1062 } 1063 } 1064 1065 /** 1066 * Perform a final validation check before allowing the form to be 1067 * finished. 1068 */ 1069 function edit_finish_validate(&$form, &$form_state) { 1070 if ($form_state['op'] != 'edit') { 1071 // Validate the name. Fake an element for form_error(). 1072 $export_key = $this->plugin['export']['key']; 1073 $element = array( 1074 '#value' => $form_state['item']->{$export_key}, 1075 '#parents' => array('name'), 1076 ); 1077 $form_state['plugin'] = $this->plugin; 1078 ctools_export_ui_edit_name_validate($element, $form_state); 1079 } 1080 } 1081 1082 /** 1083 * Handle the submission of the edit form. 1084 * 1085 * At this point, submission is successful. Our only responsibility is 1086 * to copy anything out of values onto the item that we are able to edit. 1087 * 1088 * If the keys all match up to the schema, this method will not need to be 1089 * overridden. 1090 */ 1091 function edit_form_submit(&$form, &$form_state) { 1092 if (!empty($this->plugin['form']['submit'])) { 1093 // Pass $form by reference. 1094 $this->plugin['form']['submit']($form, $form_state); 1095 } 1096 1097 // Transfer data from the form to the $item based upon schema values. 1098 $schema = ctools_export_get_schema($this->plugin['schema']); 1099 foreach (array_keys($schema['fields']) as $key) { 1100 if(isset($form_state['values'][$key])) { 1101 $form_state['item']->{$key} = $form_state['values'][$key]; 1102 } 1103 } 1104 } 1105 1106 // ------------------------------------------------------------------------ 1107 // These methods are the API for 'other' stuff with exportables such as 1108 // enable, disable, import, export, delete 1109 1110 /** 1111 * Callback to enable a page. 1112 */ 1113 function enable_page($js, $input, $item) { 1114 return $this->set_item_state(FALSE, $js, $input, $item); 1115 } 1116 1117 /** 1118 * Callback to disable a page. 1119 */ 1120 function disable_page($js, $input, $item) { 1121 return $this->set_item_state(TRUE, $js, $input, $item); 1122 } 1123 1124 /** 1125 * Set an item's state to enabled or disabled and output to user. 1126 * 1127 * If javascript is in use, this will rebuild the list and send that back 1128 * as though the filter form had been executed. 1129 */ 1130 function set_item_state($state, $js, $input, $item) { 1131 ctools_export_set_object_status($item, $state); 1132 1133 if (!$js) { 1134 drupal_goto(ctools_export_ui_plugin_base_path($this->plugin)); 1135 } 1136 else { 1137 return $this->list_page($js, $input); 1138 } 1139 } 1140 1141 /** 1142 * Page callback to delete an exportable item. 1143 */ 1144 function delete_page($js, $input, $item) { 1145 $form_state = array( 1146 'plugin' => $this->plugin, 1147 'object' => &$this, 1148 'ajax' => $js, 1149 'item' => $item, 1150 'op' => $item->export_type & EXPORT_IN_CODE ? 'revert' : 'delete', 1151 'rerender' => TRUE, 1152 'no_redirect' => TRUE, 1153 ); 1154 1155 ctools_include('form'); 1156 1157 $output = ctools_build_form('ctools_export_ui_delete_confirm_form', $form_state); 1158 if (!empty($form_state['executed'])) { 1159 ctools_export_crud_delete($this->plugin['schema'], $item); 1160 $export_key = $this->plugin['export']['key']; 1161 $message = str_replace('%title', check_plain($item->{$export_key}), $this->plugin['strings']['confirmation'][$form_state['op']]['success']); 1162 drupal_set_message($message); 1163 drupal_goto(ctools_export_ui_plugin_base_path($this->plugin)); 1164 } 1165 1166 return $output; 1167 } 1168 1169 /** 1170 * Page callback to display export information for an exportable item. 1171 */ 1172 function export_page($js, $input, $item) { 1173 drupal_set_title($this->get_page_title('export', $item)); 1174 return drupal_get_form('ctools_export_form', ctools_export_crud_export($this->plugin['schema'], $item), t('Export')); 1175 } 1176 1177 /** 1178 * Page callback to import information for an exportable item. 1179 */ 1180 function import_page($js, $input, $step = NULL) { 1181 drupal_set_title($this->get_page_title('import')); 1182 // Import is basically a multi step wizard form, so let's go ahead and 1183 // use CTools' wizard.inc for it. 1184 1185 // If a step not set, they are trying to create a new item. If a step 1186 // is set, they're in the process of creating an item. 1187 if (!empty($step)) { 1188 $item = $this->edit_cache_get(NULL, 'import'); 1189 } 1190 if (empty($item)) { 1191 $item = ctools_export_crud_new($this->plugin['schema']); 1192 } 1193 1194 $form_state = array( 1195 'plugin' => $this->plugin, 1196 'object' => &$this, 1197 'ajax' => $js, 1198 'item' => $item, 1199 'op' => 'add', 1200 'form type' => 'import', 1201 'rerender' => TRUE, 1202 'no_redirect' => TRUE, 1203 'step' => $step, 1204 // Store these in case additional args are needed. 1205 'function args' => func_get_args(), 1206 ); 1207 1208 // import always uses the wizard. 1209 $output = $this->edit_execute_form_wizard($form_state); 1210 if (!empty($form_state['executed'])) { 1211 $export_key = $this->plugin['export']['key']; 1212 drupal_goto(str_replace('%ctools_export_ui', $form_state['item']->{$export_key}, $this->plugin['redirect']['add'])); 1213 } 1214 1215 return $output; 1216 } 1217 1218 /** 1219 * Import form. Provides simple helptext instructions and textarea for 1220 * pasting a export definition. 1221 */ 1222 function edit_form_import(&$form, &$form_state) { 1223 $form['help'] = array( 1224 '#type' => 'item', 1225 '#value' => $this->plugin['strings']['help']['import'], 1226 ); 1227 1228 $form['import'] = array( 1229 '#title' => t('@plugin code', array('@plugin' => $this->plugin['title singular proper'])), 1230 '#type' => 'textarea', 1231 '#rows' => 10, 1232 '#required' => TRUE, 1233 '#default_value' => !empty($form_state['item']->export_ui_code) ? $form_state['item']->export_ui_code : '', 1234 ); 1235 1236 $form['overwrite'] = array( 1237 '#title' => t('Allow import to overwrite an existing record.'), 1238 '#type' => 'checkbox', 1239 '#default_value' => !empty($form_state['item']->export_ui_allow_overwrite) ? $form_state['item']->export_ui_allow_overwrite : FALSE, 1240 ); 1241 } 1242 1243 /** 1244 * Import form validate handler. 1245 * 1246 * Evaluates code and make sure it creates an object before we continue. 1247 */ 1248 function edit_form_import_validate($form, &$form_state) { 1249 $item = ctools_export_crud_import($this->plugin['schema'], $form_state['values']['import']); 1250 if (is_string($item)) { 1251 form_error($form['import'], t('Unable to get an import from the code. Errors reported: @errors', array('@errors' => $item))); 1252 return; 1253 } 1254 1255 $form_state['item'] = $item; 1256 $form_state['item']->export_ui_allow_overwrite = $form_state['values']['overwrite']; 1257 $form_state['item']->export_ui_code = $form_state['values']['import']; 1258 } 1259 1260 /** 1261 * Submit callback for import form. 1262 * 1263 * Stores the item in the session. 1264 */ 1265 function edit_form_import_submit($form, &$form_state) { 1266 // The validate function already imported and stored the item. This 1267 // function exists simply to prevent it from going to the default 1268 // edit_form_submit() method. 1269 } 1270 } 1271 1272 // ----------------------------------------------------------------------- 1273 // Forms to be used with this class. 1274 // 1275 // Since Drupal's forms are completely procedural, these forms will 1276 // mostly just be pass-throughs back to the object. 1277 1278 /** 1279 * Form callback to handle the filter/sort form when listing items. 1280 * 1281 * This simply loads the object defined in the plugin and hands it off. 1282 */ 1283 function ctools_export_ui_list_form(&$form_state) { 1284 $form = array(); 1285 $form_state['object']->list_form($form, $form_state); 1286 return $form; 1287 } 1288 1289 /** 1290 * Validate handler for ctools_export_ui_list_form. 1291 */ 1292 function ctools_export_ui_list_form_validate(&$form, &$form_state) { 1293 $form_state['object']->list_form_validate($form, $form_state); 1294 } 1295 1296 /** 1297 * Submit handler for ctools_export_ui_list_form. 1298 */ 1299 function ctools_export_ui_list_form_submit(&$form, &$form_state) { 1300 $form_state['object']->list_form_submit($form, $form_state); 1301 } 1302 1303 /** 1304 * Form callback to edit an exportable item. 1305 * 1306 * This simply loads the object defined in the plugin and hands it off. 1307 */ 1308 function ctools_export_ui_edit_item_form(&$form_state) { 1309 $form = array(); 1310 $form_state['object']->edit_form($form, $form_state); 1311 return $form; 1312 } 1313 1314 /** 1315 * Validate handler for ctools_export_ui_edit_item_form. 1316 */ 1317 function ctools_export_ui_edit_item_form_validate(&$form, &$form_state) { 1318 $form_state['object']->edit_form_validate($form, $form_state); 1319 } 1320 1321 /** 1322 * Submit handler for ctools_export_ui_edit_item_form. 1323 */ 1324 function ctools_export_ui_edit_item_form_submit(&$form, &$form_state) { 1325 $form_state['object']->edit_form_submit($form, $form_state); 1326 } 1327 1328 /** 1329 * Submit handler to delete for ctools_export_ui_edit_item_form 1330 * 1331 * @todo Put this on a callback in the object. 1332 */ 1333 function ctools_export_ui_edit_item_form_delete(&$form, &$form_state) { 1334 $export_key = $form_state['plugin']['export']['key']; 1335 $path = $form_state['item']->export_type & EXPORT_IN_CODE ? 'revert' : 'delete'; 1336 1337 drupal_goto(ctools_export_ui_plugin_menu_path($form_state['plugin'], $path, $form_state['item']->{$export_key}), array('cancel_path' => $_GET['q'])); 1338 } 1339 1340 /** 1341 * Validate that an export item name is acceptable and unique during add. 1342 */ 1343 function ctools_export_ui_edit_name_validate($element, &$form_state) { 1344 $plugin = $form_state['plugin']; 1345 // Check for string identifier sanity 1346 if (!preg_match('!^[a-z0-9_]+$!', $element['#value'])) { 1347 form_error($element, t('The export id can only consist of lowercase letters, underscores, and numbers.')); 1348 return; 1349 } 1350 1351 // Check for name collision 1352 if (empty($form_state['item']->export_ui_allow_overwrite) && $exists = ctools_export_crud_load($plugin['schema'], $element['#value'])) { 1353 form_error($element, t('A @plugin with this name already exists. Please choose another name or delete the existing item before creating a new one.', array('@plugin' => $plugin['title singular']))); 1354 } 1355 } 1356 1357 /** 1358 * Delete/Revert confirm form. 1359 * 1360 * @todo -- call back into the object instead. 1361 */ 1362 function ctools_export_ui_delete_confirm_form(&$form_state) { 1363 $plugin = $form_state['plugin']; 1364 $item = $form_state['item']; 1365 1366 $form = array(); 1367 1368 $export_key = $plugin['export']['key']; 1369 $question = str_replace('%title', check_plain($item->{$export_key}), $plugin['strings']['confirmation'][$form_state['op']]['question']); 1370 $path = empty($_REQUEST['cancel_path']) ? ctools_export_ui_plugin_base_path($plugin) : $_REQUEST['cancel_path']; 1371 1372 $form = confirm_form($form, 1373 $question, 1374 $path, 1375 $plugin['strings']['confirmation'][$form_state['op']]['information'], 1376 $plugin['allowed operations'][$form_state['op']]['title'], t('Cancel') 1377 ); 1378 return $form; 1379 } 1380 1381 // -------------------------------------------------------------------------- 1382 // Forms and callbacks for using the edit system with the wizard. 1383 1384 /** 1385 * Form callback to edit an exportable item using the wizard 1386 * 1387 * This simply loads the object defined in the plugin and hands it off. 1388 */ 1389 function ctools_export_ui_edit_item_wizard_form(&$form, &$form_state) { 1390 $method = 'edit_form_' . $form_state['step']; 1391 if (!method_exists($form_state['object'], $method)) { 1392 $method = 'edit_form'; 1393 } 1394 1395 $form_state['object']->$method($form, $form_state); 1396 return $form; 1397 } 1398 1399 /** 1400 * Validate handler for ctools_export_ui_edit_item_wizard_form. 1401 */ 1402 function ctools_export_ui_edit_item_wizard_form_validate(&$form, &$form_state) { 1403 $method = 'edit_form_' . $form_state['step'] . '_validate'; 1404 if (!method_exists($form_state['object'], $method)) { 1405 $method = 'edit_form_validate'; 1406 } 1407 1408 $form_state['object']->$method($form, $form_state); 1409 1410 // Additionally, if there were no errors from that, and we're finishing, 1411 // perform a final validate to make sure everything is ok. 1412 if (isset($form_state['clicked_button']['#wizard type']) && $form_state['clicked_button']['#wizard type'] == 'finish' && !form_get_errors()) { 1413 $form_state['object']->edit_finish_validate($form, $form_state); 1414 } 1415 } 1416 1417 /** 1418 * Submit handler for ctools_export_ui_edit_item_wizard_form. 1419 */ 1420 function ctools_export_ui_edit_item_wizard_form_submit(&$form, &$form_state) { 1421 $method = 'edit_form_' . $form_state['step'] . '_submit'; 1422 if (!method_exists($form_state['object'], $method)) { 1423 $method = 'edit_form_submit'; 1424 } 1425 1426 $form_state['object']->$method($form, $form_state); 1427 } 1428 1429 /** 1430 * Wizard 'back' callback when using a wizard to edit an item. 1431 */ 1432 function ctools_export_ui_wizard_back(&$form_state) { 1433 $form_state['object']->edit_wizard_back($form_state); 1434 } 1435 1436 /** 1437 * Wizard 'next' callback when using a wizard to edit an item. 1438 */ 1439 function ctools_export_ui_wizard_next(&$form_state) { 1440 $form_state['object']->edit_wizard_next($form_state); 1441 } 1442 1443 /** 1444 * Wizard 'cancel' callback when using a wizard to edit an item. 1445 */ 1446 function ctools_export_ui_wizard_cancel(&$form_state) { 1447 $form_state['object']->edit_wizard_cancel($form_state); 1448 } 1449 1450 /** 1451 * Wizard 'finish' callback when using a wizard to edit an item. 1452 */ 1453 function ctools_export_ui_wizard_finish(&$form_state) { 1454 $form_state['object']->edit_wizard_finish($form_state); 1455 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Mon Jul 9 18:01:44 2012 | Cross-referenced by PHPXref 0.7 |