[ Index ]

PHP Cross Reference of Drupal 6 (gatewave)

title

Body

[close]

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

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


Generated: Thu Mar 24 11:18:33 2011 Cross-referenced by PHPXref 0.7