| [ Index ] |
PHP Cross Reference of Drupal 6 (yi-drupal) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * @file view.inc 4 * Provides the view object type and associated methods. 5 */ 6 7 /** 8 * @defgroup views_objects Objects that represent a View or part of a view. 9 * @{ 10 * These objects are the core of Views do the bulk of the direction and 11 * storing of data. All database activity is in these objects. 12 */ 13 14 /** 15 * An object to contain all of the data to generate a view, plus the member 16 * functions to build the view query, execute the query and render the output. 17 */ 18 class view extends views_db_object { 19 var $db_table = 'views_view'; 20 var $base_table = 'node'; 21 22 // State variables 23 var $built = FALSE; 24 var $executed = FALSE; 25 26 var $args = array(); 27 var $build_info = array(); 28 29 var $use_ajax = FALSE; 30 31 // Where the results of a query will go. 32 var $result = array(); 33 34 // pager variables 35 var $pager = array( 36 'use_pager' => FALSE, 37 'items_per_page' => 10, 38 'element' => 0, 39 'offset' => 0, 40 'current_page' => 0, 41 ); 42 43 // Places to put attached renderings: 44 var $attachment_before = ''; 45 var $attachment_after = ''; 46 47 // Exposed widget input 48 var $exposed_data = array(); 49 var $exposed_input = array(); 50 51 // Used to store views that were previously running if we recurse. 52 var $old_view = array(); 53 54 /** 55 * Identifier of the current display. 56 * 57 * @var string 58 */ 59 var $current_display; 60 61 /** 62 * Stores all the views substitutions. 63 */ 64 var $substitutions = array(); 65 66 /** 67 * Constructor 68 */ 69 function view() { 70 parent::init(); 71 // Make sure all of our sub objects are arrays. 72 foreach ($this->db_objects() as $object) { 73 $this->$object = array(); 74 } 75 76 $this->query = new stdClass(); 77 } 78 79 /** 80 * Returns a list of the sub-object types used by this view. These types are 81 * stored on the display, and are used in the build process. 82 */ 83 function display_objects() { 84 return array('argument', 'field', 'sort', 'filter', 'relationship'); 85 } 86 87 /** 88 * Returns the complete list of dependent objects in a view, for the purpose 89 * of initialization and loading/saving to/from the database. 90 * 91 * Note: In PHP5 this should be static, but PHP4 doesn't support static 92 * methods. 93 */ 94 function db_objects() { 95 return array('display'); 96 } 97 98 /** 99 * Set the arguments that come to this view. Usually from the URL 100 * but possibly from elsewhere. 101 */ 102 function set_arguments($args) { 103 $this->args = $args; 104 } 105 106 /** 107 * Set the page size for ranged or pager queries 108 */ 109 function set_items_per_page($items_per_page) { 110 $this->pager['items_per_page'] = $items_per_page; 111 if (empty($items_per_page)) { 112 $this->pager['use_pager'] = FALSE; 113 } 114 } 115 116 /** 117 * Change/Set the current page for the pager. 118 */ 119 function set_current_page($page) { 120 $this->pager['current_page'] = $page; 121 } 122 123 /** 124 * Whether or not the pager should be used. 125 */ 126 function set_use_pager($use_pager) { 127 $this->pager['use_pager'] = $use_pager; 128 } 129 130 /** 131 * The pager element id to use if use_apger is on 132 */ 133 function set_pager_element($pager_element) { 134 $this->pager['element'] = $pager_element; 135 } 136 137 /** 138 * Synchronize views pager with global pager variables 139 * 140 */ 141 function synchronize_pager() { 142 if (!empty($this->pager['use_pager'])) { 143 // dump information about what we already know into the globals 144 global $pager_page_array, $pager_total, $pager_total_items; 145 // total rows in query 146 $pager_total_items[$this->pager['element']] = $this->total_rows; 147 // total pages 148 $pager_total[$this->pager['element']] = ceil($pager_total_items[$this->pager['element']] / $this->pager['items_per_page']); 149 150 // What page was requested: 151 $pager_page_array = isset($_GET['page']) ? explode(',', $_GET['page']) : array(); 152 153 // If the requested page was within range. $this->pager['current_page'] 154 // defaults to 0 so we don't need to set it in an out-of-range condition. 155 if (!empty($pager_page_array[$this->pager['element']])) { 156 $page = intval($pager_page_array[$this->pager['element']]); 157 if ($page > 0 && $page < $pager_total[$this->pager['element']]) { 158 $this->pager['current_page'] = $page; 159 } 160 } 161 $pager_page_array[$this->pager['element']] = $this->pager['current_page']; 162 } 163 } 164 165 /** 166 * How many records to skip. This does not function if use_pager is 167 * set. 168 */ 169 function set_offset($offset) { 170 $this->pager['offset'] = $offset; 171 } 172 173 /** 174 * Whether or not AJAX should be used. If AJAX is used, paging, 175 * tablesorting and exposed filters will be fetched via an AJAX call 176 * rather than a page refresh. 177 */ 178 function set_use_ajax($use_ajax) { 179 $this->use_ajax = $use_ajax; 180 } 181 182 /** 183 * Set the exposed filters input to an array. If unset they will be taken 184 * from $_GET when the time comes. 185 */ 186 function set_exposed_input($filters) { 187 $this->exposed_input = $filters; 188 } 189 190 /** 191 * Figure out what the exposed input for this view is. 192 */ 193 function get_exposed_input() { 194 // Fill our input either from $_GET or from something previously set on the 195 // view. 196 if (empty($this->exposed_input)) { 197 $this->exposed_input = $_GET; 198 // unset items that are definitely not our input: 199 foreach (array('page', 'q') as $key) { 200 if (isset($this->exposed_input[$key])) { 201 unset($this->exposed_input[$key]); 202 } 203 } 204 205 // If we have no input at all, check for remembered input via session. 206 207 // If filters are not overridden, store the 'remember' settings on the 208 // default display. If they are, store them on this display. This way, 209 // multiple displays in the same view can share the same filters and 210 // remember settings. 211 $display_id = ($this->display_handler->is_defaulted('filters')) ? 'default' : $this->current_display; 212 213 if (empty($this->exposed_input) && !empty($_SESSION['views'][$this->name][$display_id])) { 214 $this->exposed_input = $_SESSION['views'][$this->name][$display_id]; 215 } 216 } 217 218 return $this->exposed_input; 219 } 220 221 /** 222 * Set the display for this view and initialize the display handler. 223 */ 224 function init_display($reset = FALSE) { 225 // The default display is always the first one in the list. 226 if (isset($this->current_display)) { 227 return TRUE; 228 } 229 230 // Instantiate all displays 231 foreach (array_keys($this->display) as $id) { 232 // Correct for shallow cloning 233 // Often we'll have a cloned view so we don't mess up each other's 234 // displays, but the clone is pretty shallow and doesn't necessarily 235 // clone the displays. We can tell this by looking to see if a handler 236 // has already been set; if it has, but $this->current_display is not 237 // set, then something is dreadfully wrong. 238 if (!empty($this->display[$id]->handler)) { 239 $this->display[$id] = drupal_clone($this->display[$id]); 240 unset($this->display[$id]->handler); 241 } 242 $this->display[$id]->handler = views_get_plugin('display', $this->display[$id]->display_plugin); 243 if (!empty($this->display[$id]->handler)) { 244 // Initialize the new display handler with data. 245 $this->display[$id]->handler->init($this, $this->display[$id]); 246 // If this is NOT the default display handler, let it know which is 247 // since it may well utilize some data from the default. 248 // This assumes that the 'default' handler is always first. It always 249 // is. Make sure of it. 250 if ($id != 'default') { 251 $this->display[$id]->handler->default_display = &$this->display['default']->handler; 252 } 253 } 254 } 255 256 $this->current_display = 'default'; 257 $this->display_handler = &$this->display['default']->handler; 258 259 return TRUE; 260 } 261 262 /** 263 * Get the first display that is accessible to the user. 264 * 265 * @param $displays 266 * Either a single display id or an array of display ids. 267 */ 268 function choose_display($displays) { 269 if (!is_array($displays)) { 270 return $displays; 271 } 272 273 $this->init_display(); 274 275 foreach ($displays as $display_id) { 276 if ($this->display[$display_id]->handler->access()) { 277 return $display_id; 278 } 279 } 280 281 return 'default'; 282 } 283 284 /** 285 * Set the display as current. 286 * 287 * @param $display_id 288 * The id of the display to mark as current. 289 */ 290 function set_display($display_id = NULL) { 291 // If we have not already initialized the display, do so. But be careful. 292 if (empty($this->current_display)) { 293 $this->init_display(); 294 295 // If handlers were not initialized, and no argument was sent, set up 296 // to the default display. 297 if (empty($display_id)) { 298 $display_id = 'default'; 299 } 300 } 301 302 $display_id = $this->choose_display($display_id); 303 304 // If no display id sent in and one wasn't chosen above, we're finished. 305 if (empty($display_id)) { 306 return FALSE; 307 } 308 309 // Ensure the requested display exists. 310 if (empty($this->display[$display_id])) { 311 $display_id = 'default'; 312 if (empty($this->display[$display_id])) { 313 vpr(t('set_display() called with invalid display id @display.', array('@display' => $display_id))); 314 return FALSE; 315 } 316 } 317 318 // Set the current display. 319 $this->current_display = $display_id; 320 321 // Ensure requested display has a working handler. 322 if (empty($this->display[$display_id]->handler)) { 323 return FALSE; 324 } 325 326 // Set a shortcut 327 $this->display_handler = &$this->display[$display_id]->handler; 328 329 return TRUE; 330 } 331 332 /** 333 * Find and initialize the style plugin. 334 * 335 * Note that arguments may have changed which style plugin we use, so 336 * check the view object first, then ask the display handler. 337 */ 338 function init_style() { 339 if (isset($this->style_plugin)) { 340 return is_object($this->style_plugin); 341 } 342 343 if (!isset($this->plugin_name)) { 344 $this->plugin_name = $this->display_handler->get_option('style_plugin'); 345 $this->style_options = $this->display_handler->get_option('style_options'); 346 } 347 348 $this->style_plugin = views_get_plugin('style', $this->plugin_name); 349 350 if (empty($this->style_plugin)) { 351 return FALSE; 352 } 353 354 // init the new style handler with data. 355 $this->style_plugin->init($this, $this->display[$this->current_display], $this->style_options); 356 return TRUE; 357 } 358 359 /** 360 * Acquire and attach all of the handlers. 361 */ 362 function init_handlers() { 363 if (empty($this->inited)) { 364 foreach (views_object_types() as $key => $info) { 365 $this->_init_handler($key, $info); 366 } 367 $this->inited = TRUE; 368 } 369 } 370 371 /** 372 * Create a list of base tables eligible for this view. Used primarily 373 * for the UI. Display must be already initialized. 374 */ 375 function get_base_tables() { 376 $base_tables = array( 377 $this->base_table => TRUE, 378 '#global' => TRUE, 379 ); 380 381 foreach ($this->display_handler->get_handlers('relationship') as $handler) { 382 $base_tables[$handler->definition['base']] = TRUE; 383 } 384 return $base_tables; 385 } 386 387 /** 388 * Run the pre_query() on all active handlers. 389 */ 390 function _pre_query() { 391 foreach (views_object_types() as $key => $info) { 392 $handlers = &$this->$key; 393 $position = 0; 394 foreach ($handlers as $id => $handler) { 395 $handlers[$id]->position = $position; 396 $handlers[$id]->pre_query(); 397 $position++; 398 } 399 } 400 } 401 402 /** 403 * Get's all the substitutions and store them. 404 */ 405 function substitutions($reset = FALSE) { 406 if ($reset || empty($this->substitutions)) { 407 $this->substitutions = module_invoke_all('views_query_substitutions', $this); 408 } 409 return $this->substitutions; 410 } 411 412 /** 413 * Attach all of the handlers for each type. 414 * 415 * @param $key 416 * One of 'argument', 'field', 'sort', 'filter', 'relationship' 417 * @param $info 418 * The $info from views_object_types for this object. 419 */ 420 function _init_handler($key, $info) { 421 // Load the requested items from the display onto the object. 422 $this->$key = $this->display_handler->get_handlers($key); 423 424 // This reference deals with difficult PHP indirection. 425 $handlers = &$this->$key; 426 427 // Run through and test for accessibility. 428 foreach ($handlers as $id => $handler) { 429 if (!$handler->access()) { 430 unset($handlers[$id]); 431 } 432 } 433 } 434 435 /** 436 * Render the exposed filter form. 437 * 438 * This actually does more than that; because it's using FAPI, the form will 439 * also assign data to the appropriate handlers for use in building the 440 * query. 441 */ 442 function render_exposed_form($block = FALSE) { 443 // Deal with any exposed filters we may have, before building. 444 $form_state = array( 445 'view' => &$this, 446 'display' => &$this->display_handler->display, 447 'method' => 'get', 448 'rerender' => TRUE, 449 'no_redirect' => TRUE, 450 ); 451 452 // Some types of displays (eg. attachments) may wish to use the exposed 453 // filters of their parent displays instead of showing an additional 454 // exposed filter form for the attachment as well as that for the parent. 455 if (!$this->display_handler->displays_exposed() || (!$block && $this->display_handler->get_option('exposed_block'))) { 456 unset($form_state['rerender']); 457 } 458 459 if (!empty($this->ajax)) { 460 $form_state['ajax'] = TRUE; 461 } 462 463 $output = drupal_build_form('views_exposed_form', $form_state); 464 if (!empty($form_state['js settings'])) { 465 $this->js_settings = $form_state['js settings']; 466 } 467 468 // Don't render exposed filter form when there's form errors. 469 // Applies when filters are in a block ("exposed_block" option). 470 if (form_get_errors() && empty($form_state['rerender'])) { 471 return NULL; 472 } 473 474 return $output; 475 } 476 477 /** 478 * Build all the arguments. 479 */ 480 function _build_arguments() { 481 // Initially, we want to build sorts and fields. This can change, though, 482 // if we get a summary view. 483 if (empty($this->argument)) { 484 return TRUE; 485 } 486 487 // build arguments. 488 $position = -1; 489 490 // Create a title for use in the breadcrumb trail. 491 $title = $this->display_handler->get_option('title'); 492 493 $this->build_info['breadcrumb'] = array(); 494 $breadcrumb_args = array(); 495 $substitutions = array(); 496 497 $status = TRUE; 498 499 // Iterate through each argument and process. 500 foreach ($this->argument as $id => $arg) { 501 $position++; 502 $argument = &$this->argument[$id]; 503 504 if ($argument->broken()) { 505 continue; 506 } 507 508 $argument->set_relationship(); 509 510 $arg = isset($this->args[$position]) ? $this->args[$position] : NULL; 511 $argument->position = $position; 512 513 if (isset($arg) || $argument->has_default_argument()) { 514 if (!isset($arg)) { 515 $arg = $argument->get_default_argument(); 516 // make sure default args get put back. 517 if (isset($arg)) { 518 $this->args[$position] = $arg; 519 } 520 } 521 522 // Set the argument, which will also validate that the argument can be set. 523 if (!$argument->set_argument($arg)) { 524 $status = $argument->validate_fail($arg); 525 break; 526 } 527 528 if ($argument->is_wildcard()) { 529 $arg_title = $argument->wildcard_title(); 530 } 531 else { 532 $arg_title = $argument->get_title(); 533 $argument->query(); 534 } 535 536 // Add this argument's substitution 537 $substitutions['%' . ($position + 1)] = $arg_title; 538 539 // Since we're really generating the breadcrumb for the item above us, 540 // check the default action of this argument. 541 if ($this->display_handler->uses_breadcrumb() && $argument->uses_breadcrumb()) { 542 $path = $this->get_url($breadcrumb_args); 543 if (strpos($path, '%') === FALSE) { 544 $breadcrumb = !empty($argument->options['breadcrumb'])? $argument->options['breadcrumb'] : $title; 545 $this->build_info['breadcrumb'][$path] = str_replace(array_keys($substitutions), $substitutions, $breadcrumb); 546 } 547 } 548 549 // Allow the argument to muck with this breadcrumb. 550 $argument->set_breadcrumb($this->build_info['breadcrumb']); 551 552 // Test to see if we should use this argument's title 553 if (!empty($argument->options['title'])) { 554 $title = $argument->options['title']; 555 } 556 557 $breadcrumb_args[] = $arg; 558 } 559 else { 560 // determine default condition and handle. 561 $status = $argument->default_action(); 562 break; 563 } 564 565 // Be safe with references and loops: 566 unset($argument); 567 } 568 569 // set the title in the build info. 570 if (!empty($title)) { 571 $this->build_info['title'] = str_replace(array_keys($substitutions), $substitutions, $title); 572 } 573 574 // Store the arguments for later use. 575 $this->build_info['substitutions'] = $substitutions; 576 577 return $status; 578 } 579 580 /** 581 * Do some common building initialization. 582 */ 583 function init_query() { 584 // Create and initialize the query object. 585 $views_data = views_fetch_data($this->base_table); 586 $this->base_field = $views_data['table']['base']['field']; 587 if (!empty($views_data['table']['base']['database'])) { 588 $this->base_database = $views_data['table']['base']['database']; 589 } 590 views_include('query'); 591 $this->query = new views_query($this->base_table, $this->base_field); 592 } 593 594 /** 595 * Build the query for the view. 596 */ 597 function build($display_id = NULL) { 598 if (!empty($this->built)) { 599 return; 600 } 601 602 if (empty($this->current_display) || $display_id) { 603 if (!$this->set_display($display_id)) { 604 return FALSE; 605 } 606 } 607 608 // Let modules modify the view just prior to building it. 609 foreach (module_implements('views_pre_build') as $module) { 610 $function = $module . '_views_pre_build'; 611 $function($this); 612 } 613 614 // Attempt to load from cache. 615 // @todo Load a build_info from cache. 616 617 $start = views_microtime(); 618 // If that fails, let's build! 619 $this->build_info = array( 620 'query' => '', 621 'count_query' => '', 622 'query_args' => array(), 623 ); 624 625 $this->init_query(); 626 627 // Call a module hook and see if it wants to present us with a 628 // pre-built query or instruct us not to build the query for 629 // some reason. 630 // @todo: Implement this. Use the same mechanism Panels uses. 631 632 // Run through our handlers and ensure they have necessary information. 633 $this->init_handlers(); 634 635 // Let the handlers interact with each other if they really want. 636 $this->_pre_query(); 637 638 if ($this->display_handler->uses_exposed()) { 639 $this->exposed_widgets = $this->render_exposed_form(); 640 if (form_set_error() || !empty($this->build_info['abort'])) { 641 $this->built = TRUE; 642 // Don't execute the query, but rendering will still be executed to display the empty text. 643 $this->executed = TRUE; 644 return empty($this->build_info['fail']); 645 } 646 } 647 648 // Build all the relationships first thing. 649 $this->_build('relationship'); 650 651 // Build all the filters. 652 $this->_build('filter'); 653 654 $this->build_sort = TRUE; 655 656 // Arguments can, in fact, cause this whole thing to abort. 657 if (!$this->_build_arguments()) { 658 $this->build_time = views_microtime() - $start; 659 $this->attach_displays(); 660 return $this->built; 661 } 662 663 // Initialize the style; arguments may have changed which style we use, 664 // so waiting as long as possible is important. But we need to know 665 // about the style when we go to build fields. 666 if (!$this->init_style()) { 667 $this->build_info['fail'] = TRUE; 668 return FALSE; 669 } 670 671 if ($this->style_plugin->uses_fields()) { 672 $this->_build('field'); 673 } 674 675 // Build our sort criteria if we were instructed to do so. 676 if (!empty($this->build_sort)) { 677 // Allow the style handler to deal with sorting. 678 if ($this->style_plugin->build_sort()) { 679 $this->_build('sort'); 680 } 681 // allow the plugin to build second sorts as well. 682 $this->style_plugin->build_sort_post(); 683 } 684 685 // Allow display handler to affect the query: 686 $this->display_handler->query(); 687 688 // Allow style handler to affect the query: 689 $this->style_plugin->query(); 690 691 if (variable_get('views_sql_signature', FALSE)) { 692 $this->query->add_field(NULL, "'" . $this->name . ':' . $this->current_display . "'", 'view_name'); 693 } 694 695 // Let modules modify the query just prior to finalizing it. 696 foreach (module_implements('views_query_alter') as $module) { 697 $function = $module . '_views_query_alter'; 698 $function($this, $this->query); 699 } 700 701 $this->build_info['query'] = $this->query->query(); 702 $this->build_info['count_query'] = $this->query->query(TRUE); 703 $this->build_info['query_args'] = $this->query->get_where_args(); 704 $this->built = TRUE; 705 $this->build_time = views_microtime() - $start; 706 707 // Attach displays 708 $this->attach_displays(); 709 710 // Let modules modify the view just after building it. 711 foreach (module_implements('views_post_build') as $module) { 712 $function = $module . '_views_post_build'; 713 $function($this); 714 } 715 716 return TRUE; 717 } 718 719 /** 720 * Internal method to build an individual set of handlers. 721 */ 722 function _build($key) { 723 $handlers = &$this->$key; 724 foreach ($handlers as $id => $data) { 725 if (!empty($handlers[$id]) && is_object($handlers[$id])) { 726 // Give this handler access to the exposed filter input. 727 if (!empty($this->exposed_data)) { 728 $rc = $handlers[$id]->accept_exposed_input($this->exposed_data); 729 $handlers[$id]->store_exposed_input($this->exposed_data, $rc); 730 if (!$rc) { 731 continue; 732 } 733 } 734 $handlers[$id]->set_relationship(); 735 $handlers[$id]->query(); 736 } 737 } 738 } 739 740 /** 741 * Execute the view's query. 742 */ 743 function execute($display_id = NULL) { 744 if (empty($this->built)) { 745 if (!$this->build($display_id)) { 746 return FALSE; 747 } 748 } 749 750 if (!empty($this->executed)) { 751 return TRUE; 752 } 753 754 // Let modules modify the view just prior to executing it. 755 foreach (module_implements('views_pre_execute') as $module) { 756 $function = $module . '_views_pre_execute'; 757 $function($this); 758 } 759 760 $query = db_rewrite_sql($this->build_info['query'], $this->base_table, $this->base_field, array('view' => &$this)); 761 $count_query = db_rewrite_sql($this->build_info['count_query'], $this->base_table, $this->base_field, array('view' => &$this)); 762 763 $args = $this->build_info['query_args']; 764 765 vpr($query); 766 767 768 // Check for already-cached results. 769 if (!empty($this->live_preview)) { 770 $cache = FALSE; 771 } 772 else { 773 $cache = $this->display_handler->get_cache_plugin(); 774 } 775 if ($cache && $cache->cache_get('results')) { 776 $this->synchronize_pager(); 777 vpr('Used cached results'); 778 } 779 else { 780 $items = array(); 781 if ($query) { 782 $replacements = $this->substitutions(); 783 $query = str_replace(array_keys($replacements), $replacements, $query); 784 $count_query = 'SELECT COUNT(*) FROM (' . str_replace(array_keys($replacements), $replacements, $count_query) . ') count_alias'; 785 786 if (is_array($args)) { 787 foreach ($args as $id => $arg) { 788 $args[$id] = str_replace(array_keys($replacements), $replacements, $arg); 789 } 790 } 791 792 // Allow for a view to query an external database. 793 if (isset($this->base_database)) { 794 db_set_active($this->base_database); 795 $external = TRUE; 796 } 797 798 $start = views_microtime(); 799 if (!empty($this->pager['items_per_page'])) { 800 // We no longer use pager_query() here because pager_query() does not 801 // support an offset. This is fine as we don't actually need pager 802 // query; we've already been doing most of what it does, and we 803 // just need to do a little more playing with globals. 804 if (!empty($this->pager['use_pager']) || !empty($this->get_total_rows)) { 805 $this->total_rows = db_result(db_query($count_query, $args)) - $this->pager['offset']; 806 } 807 808 $this->synchronize_pager(); 809 $offset = $this->pager['current_page'] * $this->pager['items_per_page'] + $this->pager['offset']; 810 $result = db_query_range($query, $args, $offset, $this->pager['items_per_page']); 811 812 } 813 else { 814 $result = db_query($query, $args); 815 } 816 817 $this->result = array(); 818 while ($item = db_fetch_object($result)) { 819 $this->result[] = $item; 820 } 821 822 // If we already know how many items we have even if we did not run the 823 // count query, go ahead and set that value: 824 if (empty($this->pager['items_per_page'])) { 825 $this->total_rows = count($this->result); 826 } 827 828 if (!empty($external)) { 829 db_set_active(); 830 } 831 $this->execute_time = views_microtime() - $start; 832 } 833 if ($cache) { 834 $cache->cache_set('results'); 835 } 836 } 837 838 // Let modules modify the view just after executing it. 839 foreach (module_implements('views_post_execute') as $module) { 840 $function = $module . '_views_post_execute'; 841 $function($this); 842 } 843 844 $this->executed = TRUE; 845 } 846 847 /** 848 * Render this view for display. 849 */ 850 function render($display_id = NULL) { 851 $this->execute($display_id); 852 853 // Check to see if the build failed. 854 if (!empty($this->build_info['fail'])) { 855 return; 856 } 857 858 init_theme(); 859 860 $start = views_microtime(); 861 if (!empty($this->live_preview) && variable_get('views_show_additional_queries', FALSE)) { 862 $this->start_query_capture(); 863 } 864 865 // Check for already-cached output. 866 if (!empty($this->live_preview)) { 867 $cache = FALSE; 868 } 869 else { 870 $cache = $this->display_handler->get_cache_plugin(); 871 } 872 if ($cache && $cache->cache_get('output')) { 873 vpr('Used cached output'); 874 } 875 else { 876 if ($cache) { 877 $cache->cache_start(); 878 } 879 880 // Initialize the style plugin. 881 $this->init_style(); 882 883 $this->style_plugin->pre_render($this->result); 884 885 // Let modules modify the view just prior to rendering it. 886 foreach (module_implements('views_pre_render') as $module) { 887 $function = $module . '_views_pre_render'; 888 $function($this); 889 } 890 891 // Let the theme play too, because pre render is a very themey thing. 892 $function = $GLOBALS['theme'] . '_views_pre_render'; 893 if (function_exists($function)) { 894 $function($this); 895 } 896 897 // Give field handlers the opportunity to perform additional queries 898 // using the entire resultset prior to rendering. 899 if ($this->style_plugin->uses_fields()) { 900 foreach ($this->field as $id => $handler) { 901 if (!empty($this->field[$id])) { 902 $this->field[$id]->pre_render($this->result); 903 } 904 } 905 } 906 $this->display_handler->output = $this->display_handler->render(); 907 if ($cache) { 908 $cache->cache_set('output'); 909 } 910 } 911 912 if ($cache) { 913 $cache->post_render($this->display_handler->output); 914 } 915 916 // Let modules modify the view output after it is rendered. 917 foreach (module_implements('views_post_render') as $module) { 918 $function = $module . '_views_post_render'; 919 $function($this, $this->display_handler->output, $cache); 920 } 921 922 // Let the theme play too, because post render is a very themey thing. 923 $function = $GLOBALS['theme'] . '_views_post_render'; 924 if (function_exists($function)) { 925 $function($this, $this->display_handler->output, $cache); 926 } 927 928 if (!empty($this->live_preview) && variable_get('views_show_additional_queries', FALSE)) { 929 $this->end_query_capture(); 930 } 931 $this->render_time = views_microtime() - $start; 932 933 return $this->display_handler->output; 934 } 935 936 /** 937 * Render a specific field via the field ID and the row #. 938 */ 939 function render_field($field, $row) { 940 if (isset($this->field[$field]) && isset($this->result[$row])) { 941 return $this->field[$field]->advanced_render($this->result[$row]); 942 } 943 } 944 945 /** 946 * Execute the given display, with the given arguments. 947 * To be called externally by whatever mechanism invokes the view, 948 * such as a page callback, hook_block, etc. 949 * 950 * This function should NOT be used by anything external as this 951 * returns data in the format specified by the display. It can also 952 * have other side effects that are only intended for the 'proper' 953 * use of the display, such as setting page titles and breadcrumbs. 954 * 955 * If you simply want to view the display, use view::preview() instead. 956 */ 957 function execute_display($display_id = NULL, $args = array()) { 958 if (empty($this->current_display) || $this->current_display != $this->choose_display($display_id)) { 959 if (!$this->set_display($display_id)) { 960 return FALSE; 961 } 962 } 963 964 $this->pre_execute($args); 965 966 // Execute the view 967 $output = $this->display_handler->execute(); 968 969 $this->post_execute(); 970 return $output; 971 } 972 973 /** 974 * Preview the given display, with the given arguments. 975 * 976 * To be called externally, probably by an AJAX handler of some flavor. 977 * Can also be called when views are embedded, as this guarantees 978 * normalized output. 979 */ 980 function preview($display_id = NULL, $args = array()) { 981 if (empty($this->current_display) || ((!empty($display_id)) && $this->current_display != $display_id)) { 982 if (!$this->set_display($display_id)) { 983 return FALSE; 984 } 985 } 986 987 $this->preview = TRUE; 988 $this->pre_execute($args); 989 // Preview the view. 990 $output = $this->display_handler->preview(); 991 992 $this->post_execute(); 993 return $output; 994 } 995 996 /** 997 * Run attachments and let the display do what it needs to do prior 998 * to running. 999 */ 1000 function pre_execute($args = array()) { 1001 $this->old_view[] = views_get_current_view(); 1002 views_set_current_view($this); 1003 $display_id = $this->current_display; 1004 1005 // Let modules modify the view just prior to executing it. 1006 foreach (module_implements('views_pre_view') as $module) { 1007 $function = $module . '_views_pre_view'; 1008 $function($this, $display_id, $args); 1009 } 1010 1011 // Prepare the view with the information we have, but only if we were 1012 // passed arguments, as they may have been set previously. 1013 if ($args) { 1014 $this->set_arguments($args); 1015 } 1016 1017 // $this->attach_displays(); 1018 1019 // Allow the display handler to set up for execution 1020 $this->display_handler->pre_execute(); 1021 } 1022 1023 /** 1024 * Unset the current view, mostly. 1025 */ 1026 function post_execute() { 1027 // unset current view so we can be properly destructed later on. 1028 // Return the previous value in case we're an attachment. 1029 1030 if ($this->old_view) { 1031 $old_view = array_pop($this->old_view); 1032 } 1033 1034 views_set_current_view(isset($old_view) ? $old_view : FALSE); 1035 } 1036 1037 /** 1038 * Run attachment displays for the view. 1039 */ 1040 function attach_displays() { 1041 if (!empty($this->is_attachment)) { 1042 return; 1043 } 1044 1045 if (!$this->display_handler->accept_attachments()) { 1046 return; 1047 } 1048 1049 $this->is_attachment = TRUE; 1050 // Give other displays an opportunity to attach to the view. 1051 foreach ($this->display as $id => $display) { 1052 if (!empty($this->display[$id]->handler)) { 1053 $this->display[$id]->handler->attach_to($this->current_display); 1054 } 1055 } 1056 $this->is_attachment = FALSE; 1057 } 1058 1059 /** 1060 * Called to get hook_menu() information from the view and the named display handler. 1061 * 1062 * @param $display_id 1063 * A display id. 1064 * @param $callbacks 1065 * A menu callback array passed from views_menu_alter(). 1066 */ 1067 function execute_hook_menu($display_id = NULL, $callbacks = array()) { 1068 // Prepare the view with the information we have. 1069 1070 // This was probably already called, but it's good to be safe. 1071 if (!$this->set_display($display_id)) { 1072 return FALSE; 1073 } 1074 1075 // Execute the view 1076 if (isset($this->display_handler)) { 1077 return $this->display_handler->execute_hook_menu($callbacks); 1078 } 1079 } 1080 1081 /** 1082 * Called to get hook_block information from the view and the 1083 * named display handler. 1084 */ 1085 function execute_hook_block($display_id = NULL) { 1086 // Prepare the view with the information we have. 1087 1088 // This was probably already called, but it's good to be safe. 1089 if (!$this->set_display($display_id)) { 1090 return FALSE; 1091 } 1092 1093 // Execute the view 1094 if (isset($this->display_handler)) { 1095 return $this->display_handler->execute_hook_block(); 1096 } 1097 } 1098 1099 /** 1100 * Determine if the given user has access to the view. Note that 1101 * this sets the display handler if it hasn't been. 1102 */ 1103 function access($displays = NULL, $account = NULL) { 1104 // Noone should have access to disabled views. 1105 if (!empty($this->disabled)) { 1106 return FALSE; 1107 } 1108 1109 if (!isset($this->current_display)) { 1110 $this->init_display(); 1111 } 1112 1113 if (!$account) { 1114 $account = $GLOBALS['user']; 1115 } 1116 1117 // We can't use choose_display() here because that function 1118 // calls this one. 1119 $displays = (array)$displays; 1120 foreach ($displays as $display_id) { 1121 if (!empty($this->display[$display_id]->handler)) { 1122 if ($this->display[$display_id]->handler->access($account)) { 1123 return TRUE; 1124 } 1125 } 1126 } 1127 1128 return FALSE; 1129 } 1130 1131 /** 1132 * Get the view's current title. This can change depending upon how it 1133 * was built. 1134 */ 1135 function get_title() { 1136 if (empty($this->display_handler)) { 1137 if (!$this->set_display('default')) { 1138 return FALSE; 1139 } 1140 } 1141 1142 // During building, we might find a title override. If so, use it. 1143 if (!empty($this->build_info['title'])) { 1144 $title = $this->build_info['title']; 1145 } 1146 else { 1147 $title = $this->display_handler->get_option('title'); 1148 } 1149 1150 return $title; 1151 } 1152 1153 /** 1154 * Force the view to build a title. 1155 */ 1156 function build_title() { 1157 $this->init_display(); 1158 if (empty($this->built)) { 1159 $this->init_query(); 1160 } 1161 $this->init_handlers(); 1162 1163 $this->_build_arguments(); 1164 } 1165 1166 /** 1167 * Get the URL for the current view. 1168 * 1169 * This URL will be adjusted for arguments. 1170 */ 1171 function get_url($args = NULL, $path = NULL) { 1172 if (!isset($path)) { 1173 $path = $this->get_path(); 1174 } 1175 if (!isset($args)) { 1176 $args = $this->args; 1177 } 1178 // Don't bother working if there's nothing to do: 1179 if (empty($path) || (empty($args) && strpos($path, '%') === FALSE)) { 1180 return $path; 1181 } 1182 1183 $pieces = array(); 1184 $arguments = isset($arguments) ? $arguments : $this->display_handler->get_option('arguments'); 1185 $argument_keys = isset($arguments) ? array_keys($arguments) : array(); 1186 $id = current($argument_keys); 1187 foreach (explode('/', $path) as $piece) { 1188 if ($piece != '%') { 1189 $pieces[] = $piece; 1190 } 1191 else { 1192 if (empty($args)) { 1193 // Try to never put % in a url; use the wildcard instead. 1194 if ($id && !empty($arguments[$id]['wildcard'])) { 1195 $pieces[] = $arguments[$id]['wildcard']; 1196 } 1197 else { 1198 $pieces[] = '*'; // gotta put something if there just isn't one. 1199 } 1200 1201 } 1202 else { 1203 $pieces[] = array_shift($args); 1204 } 1205 1206 if ($id) { 1207 $id = next($argument_keys); 1208 } 1209 } 1210 } 1211 1212 if (!empty($args)) { 1213 $pieces = array_merge($pieces, $args); 1214 } 1215 return implode('/', $pieces); 1216 } 1217 1218 /** 1219 * Get the base path used for this view. 1220 */ 1221 function get_path() { 1222 if (!empty($this->override_path)) { 1223 return $this->override_path; 1224 } 1225 1226 if (empty($this->display_handler)) { 1227 if (!$this->set_display('default')) { 1228 return FALSE; 1229 } 1230 } 1231 return $this->display_handler->get_path(); 1232 } 1233 1234 /** 1235 * Get the breadcrumb used for this view. 1236 * 1237 * @param $set 1238 * If true, use drupal_set_breadcrumb() to install the breadcrumb. 1239 */ 1240 function get_breadcrumb($set = FALSE) { 1241 // Now that we've built the view, extract the breadcrumb. 1242 $base = TRUE; 1243 $breadcrumb = array(); 1244 1245 if (!empty($this->build_info['breadcrumb'])) { 1246 foreach ($this->build_info['breadcrumb'] as $path => $title) { 1247 // Check to see if the frontpage is in the breadcrumb trail; if it 1248 // is, we'll remove that from the actual breadcrumb later. 1249 if ($path == variable_get('site_frontpage', 'node')) { 1250 $base = FALSE; 1251 $title = t('Home'); 1252 } 1253 if ($title) { 1254 $breadcrumb[] = l($title, $path, array('html' => true)); 1255 } 1256 } 1257 1258 if ($set) { 1259 if ($base) { 1260 $breadcrumb = array_merge(drupal_get_breadcrumb(), $breadcrumb); 1261 } 1262 drupal_set_breadcrumb($breadcrumb); 1263 } 1264 } 1265 return $breadcrumb; 1266 } 1267 1268 /** 1269 * Is this view cacheable? 1270 */ 1271 function is_cacheable() { 1272 return $this->is_cacheable; 1273 } 1274 1275 /** 1276 * Set up query capturing. 1277 * 1278 * db_query() stores the queries that it runs in global $queries, 1279 * bit only if dev_query is set to true. In this case, we want 1280 * to temporarily override that setting if it's not and we 1281 * can do that without forcing a db rewrite by just manipulating 1282 * $conf. This is kind of evil but it works. 1283 */ 1284 function start_query_capture() { 1285 global $conf, $queries; 1286 if (empty($conf['dev_query'])) { 1287 $this->fix_dev_query = TRUE; 1288 $conf['dev_query'] = TRUE; 1289 } 1290 1291 // Record the last query key used; anything already run isn't 1292 // a query that we are interested in. 1293 $this->last_query_key = NULL; 1294 1295 if (!empty($queries)) { 1296 $keys = array_keys($queries); 1297 $this->last_query_key = array_pop($keys); 1298 } 1299 } 1300 1301 /** 1302 * Add the list of queries run during render to buildinfo. 1303 * 1304 * @see view::start_query_capture() 1305 */ 1306 function end_query_capture() { 1307 global $conf, $queries; 1308 if (!empty($this->fix_dev_query)) { 1309 $conf['dev_query'] = FALSE; 1310 } 1311 1312 // make a copy of the array so we can manipulate it with array_splice. 1313 $temp = $queries; 1314 1315 // Scroll through the queries until we get to our last query key. 1316 // Unset anything in our temp array. 1317 if (isset($this->last_query_key)) { 1318 while (list($id, $query) = each($queries)) { 1319 if ($id == $this->last_query_key) { 1320 break; 1321 } 1322 1323 unset($temp[$id]); 1324 } 1325 } 1326 1327 $this->additional_queries = $temp; 1328 } 1329 1330 /** 1331 * Load a view from the database based upon either vid or name. 1332 * 1333 * This is a static factory method that implements internal caching for 1334 * view objects. 1335 * 1336 * @param $arg 1337 * The name of the view or its internal view id (vid) 1338 * @param $reset 1339 * If TRUE, reset this entry in the load cache. 1340 * @return A view object or NULL if it was not available. 1341 */ 1342 function &load($arg, $reset = FALSE) { 1343 static $cache = array(); 1344 1345 // We want a NULL value to return TRUE here, so we can't use isset() or empty(). 1346 if (!array_key_exists($arg, $cache) || $reset) { 1347 $where = (is_numeric($arg) ? "vid = %d" : "name = '%s'"); 1348 $data = db_fetch_object(db_query("SELECT * FROM {views_view} WHERE $where", $arg)); 1349 if (empty($data)) { 1350 $cache[$arg] = NULL; 1351 } 1352 else { 1353 $view = new view(); 1354 $view->load_row($data); 1355 $view->type = t('Normal'); 1356 // Load all of our subtables. 1357 foreach ($view->db_objects() as $key) { 1358 $object_name = "views_$key"; 1359 $result = db_query("SELECT * FROM {{$object_name}} WHERE vid = %d ORDER BY position", $view->vid); 1360 1361 while ($data = db_fetch_object($result)) { 1362 $object = new $object_name(FALSE); 1363 $object->load_row($data); 1364 1365 // Because it can get complicated with this much indirection, 1366 // make a shortcut reference. 1367 $location = &$view->$key; 1368 1369 // If we have a basic id field, load the item onto the view based on 1370 // this ID, otherwise push it on. 1371 if (!empty($object->id)) { 1372 $location[$object->id] = $object; 1373 } 1374 else { 1375 $location[] = $object; 1376 } 1377 } 1378 } 1379 1380 $view->loaded = TRUE; 1381 $cache[$arg] = $view; 1382 } 1383 } 1384 1385 return $cache[$arg]; 1386 } 1387 1388 /** 1389 * Static factory method to load a list of views based upon a $where clause. 1390 * 1391 * Although this method could be implemented to simply iterate over views::load(), 1392 * that would be very slow. Buiding the views externally from unified queries is 1393 * much faster. 1394 */ 1395 function load_views() { 1396 $result = db_query("SELECT DISTINCT v.* FROM {views_view} v"); 1397 $views = array(); 1398 $vids = array(); 1399 1400 // Load all the views. 1401 while ($data = db_fetch_object($result)) { 1402 $view = new view; 1403 $view->load_row($data); 1404 $view->loaded = TRUE; 1405 $view->type = t('Normal'); 1406 $views[$view->name] = $view; 1407 $names[$view->vid] = $view->name; 1408 } 1409 1410 // Stop if we didn't get any views. 1411 if (!$views) { 1412 return array(); 1413 } 1414 1415 $vids = implode(', ', array_keys($names)); 1416 // Now load all the subtables: 1417 foreach (view::db_objects() as $key) { 1418 $object_name = "views_$key"; 1419 $result = db_query("SELECT * FROM {{$object_name}} WHERE vid IN ($vids) ORDER BY vid, position"); 1420 1421 while ($data = db_fetch_object($result)) { 1422 $object = new $object_name(FALSE); 1423 $object->load_row($data); 1424 1425 // Because it can get complicated with this much indirection, 1426 // make a shortcut reference. 1427 $location = &$views[$names[$object->vid]]->$key; 1428 1429 // If we have a basic id field, load the item onto the view based on 1430 // this ID, otherwise push it on. 1431 if (!empty($object->id)) { 1432 $location[$object->id] = $object; 1433 } 1434 else { 1435 $location[] = $object; 1436 } 1437 } 1438 } 1439 return $views; 1440 } 1441 1442 /** 1443 * Save the view to the database. If the view does not already exist, 1444 * A vid will be assigned to the view and also returned from this function. 1445 */ 1446 function save() { 1447 if ($this->vid == 'new') { 1448 $this->vid = NULL; 1449 } 1450 1451 // If we have no vid or our vid is a string, this is a new view. 1452 if (!empty($this->vid)) { 1453 // remove existing table entries 1454 foreach ($this->db_objects() as $key) { 1455 db_query("DELETE from {views_" . $key . "} WHERE vid = %d", $this->vid); 1456 } 1457 } 1458 1459 $this->save_row(!empty($this->vid) ? 'vid' : FALSE); 1460 1461 // Save all of our subtables. 1462 foreach ($this->db_objects() as $key) { 1463 $this->_save_rows($key); 1464 } 1465 1466 cache_clear_all('views_urls', 'cache_views'); 1467 cache_clear_all(); // clear the page cache as well. 1468 } 1469 1470 /** 1471 * Save a row to the database for the given key, which is one of the 1472 * keys from view::db_objects() 1473 */ 1474 function _save_rows($key) { 1475 $count = 0; 1476 foreach ($this->$key as $position => $object) { 1477 $object->position = ++$count; 1478 $object->vid = $this->vid; 1479 $object->save_row(); 1480 } 1481 } 1482 1483 /** 1484 * Delete the view from the database. 1485 */ 1486 function delete($clear = TRUE) { 1487 if (empty($this->vid)) { 1488 return; 1489 } 1490 1491 db_query("DELETE FROM {views_view} WHERE vid = %d", $this->vid); 1492 // Delete from all of our subtables as well. 1493 foreach ($this->db_objects() as $key) { 1494 db_query("DELETE from {views_" . $key . "} WHERE vid = %d", $this->vid); 1495 } 1496 1497 cache_clear_all('views_query:' . $this->name, 'cache_views'); 1498 1499 if ($clear) { 1500 cache_clear_all(); // this clears the block and page caches only. 1501 menu_rebuild(); // force a menu rebuild when a view is deleted. 1502 } 1503 } 1504 1505 /** 1506 * Export a view as PHP code. 1507 */ 1508 function export($indent = '') { 1509 $this->init_display(); 1510 $output = ''; 1511 $output .= $this->export_row('view', $indent); 1512 // Set the API version 1513 $output .= $indent . '$view->api_version = \'' . views_api_version() . "';\n"; 1514 $output .= $indent . '$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */' . "\n"; 1515 1516 foreach ($this->display as $id => $display) { 1517 $output .= $indent . '$handler = $view->new_display(' . views_var_export($display->display_plugin, $indent) . ', ' . views_var_export($display->display_title, $indent) . ', \'' . $id . "');\n"; 1518 if (empty($display->handler)) { 1519 // @todo -- probably need a method of exporting broken displays as 1520 // they may simply be broken because a module is not installed. That 1521 // does not invalidate the display. 1522 continue; 1523 } 1524 1525 foreach ($display->handler->option_definition() as $option => $definition) { 1526 // Special handling for some items 1527 switch ($option) { 1528 case 'defaults': 1529 // skip these 1530 break; 1531 default: 1532 if (!$display->handler->is_defaulted($option)) { 1533 $value = $display->handler->get_option($option); 1534 if ($id == 'default') { 1535 $default = isset($definition['default']) ? $definition['default'] : NULL; 1536 } 1537 else { 1538 $default = $this->display['default']->handler->get_option($option); 1539 } 1540 1541 if ($value !== $default) { 1542 $output .= $indent . '$handler->override_option(\'' . $option . '\', ' . views_var_export($value, $indent) . ");\n"; 1543 } 1544 } 1545 } 1546 } 1547 } 1548 1549 return $output; 1550 } 1551 1552 /** 1553 * Make a copy of this view that has been sanitized of all database IDs 1554 * and handlers and other stuff. 1555 * 1556 * I'd call this clone() but it's reserved. 1557 */ 1558 function copy() { 1559 $code = $this->export(); 1560 eval($code); 1561 return $view; 1562 } 1563 1564 /** 1565 * Safely clone a view. 1566 * 1567 * Because views are complicated objects within objects, and PHP loves to 1568 * do references to everything, if a View is not properly and safely 1569 * cloned it will still have references to the original view, and can 1570 * actually cause the original view to point to objects in the cloned 1571 * view. This gets ugly fast. 1572 * 1573 * This will completely wipe a view clean so it can be considered fresh. 1574 */ 1575 function clone_view() { 1576 $clone = version_compare(phpversion(), '5.0') < 0 ? $this : clone($this); 1577 1578 $keys = array('current_display', 'display_handler', 'build_info', 'built', 'executed', 'attachment_before', 'attachment_after', 'field', 'argument', 'filter', 'sort', 'relationship', 'query', 'inited', 'style_plugin', 'plugin_name', 'exposed_data', 'exposed_input', 'exposed_widgets', 'many_to_one_tables', 'feed_icon'); 1579 foreach ($keys as $key) { 1580 if (isset($clone->$key)) { 1581 unset($clone->$key); 1582 } 1583 } 1584 $clone->built = $clone->executed = FALSE; 1585 $clone->build_info = array(); 1586 $clone->attachment_before = ''; 1587 $clone->attachment_after = ''; 1588 $clone->result = array(); 1589 1590 // shallow cloning means that all the display objects 1591 // *were not cloned*. We must clone them ourselves. 1592 $displays = array(); 1593 foreach ($clone->display as $id => $display) { 1594 $displays[$id] = drupal_clone($display); 1595 if (isset($displays[$id]->handler)) { 1596 unset($displays[$id]->handler); 1597 } 1598 } 1599 $clone->display = $displays; 1600 1601 return $clone; 1602 } 1603 1604 /** 1605 * Unset references so that a $view object may be properly garbage 1606 * collected. 1607 */ 1608 function destroy() { 1609 foreach (array_keys($this->display) as $display_id) { 1610 if (isset($this->display[$display_id]->handler)) { 1611 $this->display[$display_id]->handler->destroy(); 1612 unset($this->display[$display_id]->handler); 1613 } 1614 } 1615 1616 foreach (views_object_types() as $type => $info) { 1617 if (isset($this->$type)) { 1618 $handlers = &$this->$type; 1619 foreach ($handlers as $id => $item) { 1620 $handlers[$id]->destroy(); 1621 } 1622 unset($handlers); 1623 } 1624 } 1625 1626 if (isset($this->style_plugin)) { 1627 $this->style_plugin->destroy(); 1628 unset($this->style_plugin); 1629 } 1630 1631 // Clear these to make sure the view can be processed/used again. 1632 if (isset($this->display_handler)) { 1633 unset($this->display_handler); 1634 } 1635 1636 if (isset($this->current_display)) { 1637 unset($this->current_display); 1638 } 1639 1640 if (isset($this->query)) { 1641 unset($this->query); 1642 } 1643 } 1644 1645 /** 1646 * Make sure the view is completely valid. 1647 * 1648 * @return 1649 * TRUE if the view is valid; an array of error strings if it is not. 1650 */ 1651 function validate() { 1652 $this->init_display(); 1653 1654 $errors = array(); 1655 1656 $current_display = $this->current_display; 1657 foreach ($this->display as $id => $display) { 1658 if ($display->handler) { 1659 if (!empty($display->deleted)) { 1660 continue; 1661 } 1662 $this->set_display($id); 1663 1664 $result = $this->display[$id]->handler->validate(); 1665 if (!empty($result) && is_array($result)) { 1666 $errors = array_merge($errors, $result); 1667 } 1668 } 1669 } 1670 1671 $this->set_display($current_display); 1672 return $errors ? $errors : TRUE; 1673 } 1674 } 1675 1676 /** 1677 * Base class for views' database objects. 1678 */ 1679 class views_db_object { 1680 /** 1681 * Initialize this object, setting values from schema defaults. 1682 * 1683 * @param $init 1684 * If an array, this is a set of values from db_fetch_object to 1685 * load. Otherwse, if TRUE values will be filled in from schema 1686 * defaults. 1687 */ 1688 function init($init = TRUE) { 1689 if (is_array($init)) { 1690 return $this->load_row($init); 1691 } 1692 1693 if (!$init) { 1694 return; 1695 } 1696 1697 $schema = drupal_get_schema($this->db_table); 1698 1699 if (!$schema) { 1700 return; 1701 } 1702 1703 // Go through our schema and build correlations. 1704 foreach ($schema['fields'] as $field => $info) { 1705 if ($info['type'] == 'serial') { 1706 $this->$field = NULL; 1707 } 1708 if (!isset($this->$field)) { 1709 if (!empty($info['serialize']) && isset($info['serialized default'])) { 1710 $this->$field = unserialize($info['serialized default']); 1711 } 1712 else if (isset($info['default'])) { 1713 $this->$field = $info['default']; 1714 } 1715 else { 1716 $this->$field = ''; 1717 } 1718 } 1719 } 1720 } 1721 1722 /** 1723 * Write the row to the database. 1724 * 1725 * @param $update 1726 * If true this will be an UPDATE query. Otherwise it will be an INSERT. 1727 */ 1728 function save_row($update = NULL) { 1729 $schema = drupal_get_schema($this->db_table); 1730 $fields = $defs = $values = $serials = array(); 1731 1732 // Go through our schema and build correlations. 1733 foreach ($schema['fields'] as $field => $info) { 1734 // special case -- skip serial types if we are updating. 1735 if ($info['type'] == 'serial') { 1736 $serials[] = $field; 1737 continue; 1738 } 1739 $fields[] = $field; 1740 switch ($info['type']) { 1741 case 'blob': 1742 $defs[] = '%b'; 1743 break; 1744 case 'int': 1745 $defs[] = '%d'; 1746 break; 1747 case 'float': 1748 case 'numeric': 1749 $defs[] = '%f'; 1750 break; 1751 default: 1752 $defs[] = "'%s'"; 1753 } 1754 if (empty($info['serialize'])) { 1755 $values[] = $this->$field; 1756 } 1757 else { 1758 $values[] = serialize($this->$field); 1759 } 1760 } 1761 $query = ''; 1762 if (!$update) { 1763 $query = "INSERT INTO {" . $this->db_table . "} (" . implode(', ', $fields) . ') VALUES (' . implode(', ', $defs) . ')'; 1764 } 1765 else { 1766 $query = ''; 1767 foreach ($fields as $id => $field) { 1768 if ($query) { 1769 $query .= ', '; 1770 } 1771 $query .= $field . ' = ' . $defs[$id]; 1772 } 1773 $query = "UPDATE {" . $this->db_table . "} SET " . $query . " WHERE $update = " . $this->$update; 1774 } 1775 db_query($query, $values); 1776 1777 if ($serials && !$update) { 1778 // get last insert ids and fill them in. 1779 foreach ($serials as $field) { 1780 $this->$field = db_last_insert_id($this->db_table, $field); 1781 } 1782 } 1783 } 1784 1785 /** 1786 * Load the object with a row from the database. 1787 * 1788 * This method is separate from the constructor in order to give us 1789 * more flexibility in terms of how the view object is built in different 1790 * contexts. 1791 * 1792 * @param $data 1793 * An object from db_fetch_object. It should contain all of the fields 1794 * that are in the schema. 1795 */ 1796 function load_row($data) { 1797 $schema = drupal_get_schema($this->db_table); 1798 1799 // Go through our schema and build correlations. 1800 foreach ($schema['fields'] as $field => $info) { 1801 $this->$field = empty($info['serialize']) ? $data->$field : unserialize(db_decode_blob($data->$field)); 1802 } 1803 } 1804 1805 /** 1806 * Export a loaded row, such as an argument, field or the view itself to PHP code. 1807 * 1808 * @param $identifier 1809 * The variable to assign the PHP code for this object to. 1810 * @param $indent 1811 * An optional indentation for prettifying nested code. 1812 */ 1813 function export_row($identifier = NULL, $indent = '') { 1814 if (!$identifier) { 1815 $identifier = $this->db_table; 1816 } 1817 $schema = drupal_get_schema($this->db_table); 1818 1819 $output = $indent . '$' . $identifier . ' = new ' . get_class($this) . ";\n"; 1820 // Go through our schema and build correlations. 1821 foreach ($schema['fields'] as $field => $info) { 1822 if (!empty($info['no export'])) { 1823 continue; 1824 } 1825 if (!isset($this->$field)) { 1826 if (isset($info['default'])) { 1827 $this->$field = $info['default']; 1828 } 1829 else { 1830 $this->$field = ''; 1831 } 1832 1833 // serialized defaults must be set as serialized. 1834 if (isset($info['serialize'])) { 1835 $this->$field = unserialize(db_decode_blob($this->$field)); 1836 } 1837 } 1838 $value = $this->$field; 1839 if ($info['type'] == 'int') { 1840 $value = ($info['size'] == 'tiny') ? (bool) $value : (int) $value; 1841 } 1842 1843 $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . views_var_export($value, $indent) . ";\n"; 1844 } 1845 return $output; 1846 } 1847 1848 /** 1849 * Add a new display handler to the view, automatically creating an id. 1850 * 1851 * @param $type 1852 * The plugin type from the views plugin data. Defaults to 'page'. 1853 * @param $title 1854 * The title of the display; optional, may be filled in from default. 1855 * @param $id 1856 * The id to use. 1857 * @return 1858 * The key to the display in $view->display, so that the new display 1859 * can be easily located. 1860 */ 1861 function add_display($type = 'page', $title = NULL, $id = NULL) { 1862 if (empty($type)) { 1863 return FALSE; 1864 } 1865 1866 $plugin = views_fetch_plugin_data('display', $type); 1867 if (empty($plugin)) { 1868 $plugin['title'] = t('Broken'); 1869 } 1870 1871 1872 if (empty($id)) { 1873 $title = $plugin['title']; 1874 1875 $id = $this->generate_display_id($type); 1876 $count = str_replace($type . '_', '', $id); 1877 if (empty($title)) { 1878 $title = $plugin['title'] . ' ' . $count; 1879 } 1880 } 1881 1882 // Create the new display object 1883 $display = new views_display; 1884 $display->options($type, $id, $title); 1885 1886 // Add the new display object to the view. 1887 $this->display[$id] = $display; 1888 return $id; 1889 } 1890 1891 /** 1892 * Generate a display id of a certain plugin type. 1893 * 1894 * @param $type 1895 * Which plugin should be used for the new display id. 1896 */ 1897 function generate_display_id($type) { 1898 // 'default' is singular and is unique, so just go with 'default' 1899 // for it. For all others, start counting. 1900 if ($type == 'default') { 1901 return 'default'; 1902 } 1903 // Initial id. 1904 $id = $type . '_1'; 1905 $count = 1; 1906 1907 // Loop through IDs based upon our style plugin name until 1908 // we find one that is unused. 1909 while (!empty($this->display[$id])) { 1910 $id = $type . '_' . ++$count; 1911 } 1912 1913 return $id; 1914 } 1915 1916 /** 1917 * Create a new display and a display handler for it. 1918 * @param $type 1919 * The plugin type from the views plugin data. Defaults to 'page'. 1920 * @param $title 1921 * The title of the display; optional, may be filled in from default. 1922 * @param $id 1923 * The id to use. 1924 * @return 1925 * A reference to the new handler object. 1926 */ 1927 function &new_display($type = 'page', $title = NULL, $id = NULL) { 1928 $id = $this->add_display($type, $title, $id); 1929 1930 // Create a handler 1931 $this->display[$id]->handler = views_get_plugin('display', $this->display[$id]->display_plugin); 1932 if (empty($this->display[$id]->handler)) { 1933 // provide a 'default' handler as an emergency. This won't work well but 1934 // it will keep things from crashing. 1935 $this->display[$id]->handler = views_get_plugin('display', 'default'); 1936 } 1937 1938 if (!empty($this->display[$id]->handler)) { 1939 // Initialize the new display handler with data. 1940 $this->display[$id]->handler->init($this, $this->display[$id]); 1941 // If this is NOT the default display handler, let it know which is 1942 if ($id != 'default') { 1943 $this->display[$id]->handler->default_display = &$this->display['default']->handler; 1944 } 1945 } 1946 1947 return $this->display[$id]->handler; 1948 } 1949 1950 /** 1951 * Add an item with a handler to the view. 1952 * 1953 * These items may be fields, filters, sort criteria, or arguments. 1954 */ 1955 function add_item($display_id, $type, $table, $field, $options = array(), $id = NULL) { 1956 $types = views_object_types(); 1957 $this->set_display($display_id); 1958 1959 $fields = $this->display[$display_id]->handler->get_option($types[$type]['plural']); 1960 1961 if (empty($id)) { 1962 $count = 0; 1963 $id = $field; 1964 while (!empty($fields[$id])) { 1965 $id = $field . '_' . ++$count; 1966 } 1967 } 1968 1969 $new_item = array( 1970 'id' => $id, 1971 'table' => $table, 1972 'field' => $field, 1973 ) + $options; 1974 1975 $handler = views_get_handler($table, $field, $type); 1976 1977 $fields[$id] = $new_item; 1978 $this->display[$display_id]->handler->set_option($types[$type]['plural'], $fields); 1979 1980 return $id; 1981 } 1982 1983 /** 1984 * Get an array of items for the current display. 1985 */ 1986 function get_items($type, $display_id = NULL) { 1987 $this->set_display($display_id); 1988 1989 if (!isset($display_id)) { 1990 $display_id = $this->current_display; 1991 } 1992 1993 // Get info about the types so we can get the right data. 1994 $types = views_object_types(); 1995 return $this->display[$display_id]->handler->get_option($types[$type]['plural']); 1996 } 1997 1998 /** 1999 * Get the configuration of an item (field/sort/filter/etc) on a given 2000 * display. 2001 */ 2002 function get_item($display_id, $type, $id) { 2003 // Get info about the types so we can get the right data. 2004 $types = views_object_types(); 2005 // Initialize the display 2006 $this->set_display($display_id); 2007 2008 // Get the existing configuration 2009 $fields = $this->display[$display_id]->handler->get_option($types[$type]['plural']); 2010 2011 return isset($fields[$id]) ? $fields[$id] : NULL; 2012 } 2013 2014 /** 2015 * Get the configuration of an item (field/sort/filter/etc) on a given 2016 * display. 2017 * 2018 * Pass in NULL for the $item to remove an item. 2019 */ 2020 function set_item($display_id, $type, $id, $item) { 2021 // Get info about the types so we can get the right data. 2022 $types = views_object_types(); 2023 // Initialize the display 2024 $this->set_display($display_id); 2025 2026 // Get the existing configuration 2027 $fields = $this->display[$display_id]->handler->get_option($types[$type]['plural']); 2028 if (isset($item)) { 2029 $fields[$id] = $item; 2030 } 2031 else { 2032 unset($fields[$id]); 2033 } 2034 2035 // Store. 2036 $this->display[$display_id]->handler->set_option($types[$type]['plural'], $fields); 2037 } 2038 2039 /** 2040 * Set an option on an item. 2041 * 2042 * Use this only if you have just 1 or 2 options to set; if you have 2043 * many, consider getting the item, adding the options and doing 2044 * set_item yourself. 2045 */ 2046 function set_item_option($display_id, $type, $id, $option, $value) { 2047 $item = $this->get_item($display_id, $type, $id); 2048 $item[$option] = $value; 2049 $this->set_item($display_id, $type, $id, $item); 2050 } 2051 } 2052 2053 /** 2054 * A display type in a view. 2055 * 2056 * This is just the database storage mechanism, and isn't terribly important 2057 * to the behavior of the display at all. 2058 */ 2059 class views_display extends views_db_object { 2060 var $db_table = 'views_display'; 2061 function views_display($init = TRUE) { 2062 parent::init($init); 2063 } 2064 2065 function options($type, $id, $title) { 2066 $this->display_plugin = $type; 2067 $this->id = $id; 2068 $this->display_title = $title; 2069 } 2070 } 2071 2072 /** 2073 * Provide a list of views object types used in a view, with some information 2074 * about them. 2075 */ 2076 function views_object_types() { 2077 static $retval = NULL; 2078 2079 // statically cache this so t() doesn't run a bajillion times. 2080 if (!isset($retval)) { 2081 $retval = array( 2082 'field' => array( 2083 'title' => t('Fields'), // title 2084 'ltitle' => t('fields'), // lowercase title for mid-sentence 2085 'stitle' => t('Field'), // singular title 2086 'lstitle' => t('field'), // singular lowercase title for mid sentence 2087 'plural' => 'fields', 2088 ), 2089 'argument' => array( 2090 'title' => t('Arguments'), 2091 'ltitle' => t('arguments'), 2092 'stitle' => t('Argument'), 2093 'lstitle' => t('Argument'), 2094 'plural' => 'arguments', 2095 ), 2096 'sort' => array( 2097 'title' => t('Sort criteria'), 2098 'ltitle' => t('sort criteria'), 2099 'stitle' => t('Sort criterion'), 2100 'lstitle' => t('sort criterion'), 2101 'plural' => 'sorts', 2102 ), 2103 'filter' => array( 2104 'title' => t('Filters'), 2105 'ltitle' => t('filters'), 2106 'stitle' => t('Filter'), 2107 'lstitle' => t('filter'), 2108 'plural' => 'filters', 2109 'options' => 'views_ui_config_filters_form', 2110 ), 2111 'relationship' => array( 2112 'title' => t('Relationships'), 2113 'ltitle' => t('relationships'), 2114 'stitle' => t('Relationship'), 2115 'lstitle' => t('Relationship'), 2116 'plural' => 'relationships', 2117 ), 2118 ); 2119 } 2120 2121 return $retval; 2122 } 2123 2124 /** 2125 * @} 2126 */
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 |