[ Index ]

PHP Cross Reference of Drupal 6 (yi-drupal)

title

Body

[close]

/sites/all/modules/views/includes/ -> view.inc (source)

   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   */


Generated: Mon Jul 9 18:01:44 2012 Cross-referenced by PHPXref 0.7