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