views.module 58.9 KB
Newer Older
1
<?php
2
// $Id$
merlinofchaos's avatar
merlinofchaos committed
3
// $Name$
4 5 6 7

// ---------------------------------------------------------------------------
// Drupal Hooks

8
/**
9 10
 * Implementation of hook_help()
 */
11 12
function views_help($section) {
  switch ($section) {
13
    case 'admin/help#views':
14
      return t('The views module creates customized views of node lists. You may need to activate the Views UI module to get to the user administration pages.');
15
  }
16 17
}

18
/**
19 20
 * Implementation of hook_menu()
 */
21 22 23 24
function views_menu($may_cache) {
  $items = array();

  if ($may_cache) {
25
    views_load_cache();
26 27
    // Invalidate the views cache to ensure that views data gets rebuilt.
    // This is the best way to tell that module configuration has changed.
28
    if (arg(0) == 'admin' && arg(2) == 'modules') {
29 30
      views_invalidate_cache();
    }
31 32 33 34 35 36 37 38 39 40 41 42 43 44
    
    views_menu_standard_items($items);
  }
  else {
    views_menu_inline_items($items);
  }
  return $items;
}

/**
 * Add the menu items for all non-inline views to the menu
 */
function views_menu_standard_items(&$items) {
  $result = db_query("SELECT * FROM {view_view} WHERE page = 1");
45

46 47 48 49
  while ($view = db_fetch_object($result)) {
    // This happens before the next check; even if it's put off for later
    // it is still used.
    $used[$view->name] = true;
50

51 52 53 54 55 56 57 58 59 60 61 62
    // Skip views with inline arguments.
    if ($view->url{0} == '$' || strpos($view->url, '/$') !== FALSE) {
      continue;
    }

    // unpack the array
    $view->access = ($view->access ? explode(', ', $view->access) : array());

    _views_create_menu_item($items, $view, $view->url);
  }
  $default_views = _views_get_default_views();
  $views_status = variable_get('views_defaults', array());
merlinofchaos's avatar
merlinofchaos committed
63

64 65 66 67
  // Process default views
  foreach ($default_views as $name => $view) {
    if ($view->page && !$used[$name] &&
     ($views_status[$name] == 'enabled' || (!$view->disabled && $views_status[$name] != 'disabled'))) {
68

69 70 71 72
      // skip views with inline args
      if ($view->url{0} == '$' || strpos($view->url, '/$') !== FALSE) {
        continue;
      }
merlinofchaos's avatar
merlinofchaos committed
73

74
      _views_create_menu_item($items, $view, $view->url);
75
    }
76 77
  }
}
78

79 80 81 82
function views_menu_inline_items(&$items) {
  // I don't think we gain anything by caching these, there should never
  // be all that many, and caching == a database hit.
  $tokens = module_invoke_all('views_url_tokens');
83

84 85 86 87 88 89
  $args = explode('/', $_GET['q']);
  $urls = views_get_all_urls();
  foreach ($urls as $view_name => $url) {
    if ($url{0} != '$' && strpos($url, '/$') === FALSE) {
      if (module_exists('views_ui') && user_access('administer views')) {
        $view_args = $args;
90

91 92 93 94 95 96 97
        foreach (explode('/', $url) as $num => $element) {
          if ($element != $args[$num]) {
            continue 2;
          }
          unset($view_args[$num]);
        }
        views_menu_admin_items($items, $view_name, $view_args, $args);
98 99
      }
    }
100 101 102
    else {
      // Do substitution on args.
      $use_view = $use_menu = FALSE;
103
      $menu_args = $view_args = $menu_path = array();
104 105 106 107 108 109 110 111 112

      foreach (explode('/', $url) as $num => $element) {
        if ($element{0} == '$') {
          // If we pass the token check, this view is definitely being used.
          list($token, $argument) = explode('-', $element);
          if ($tokens[$token] && function_exists($tokens[$token])) {
            if (!($use_view = $use_menu = $tokens[$token]($element, $argument, arg($num)))) {
              break;
            }
113
          }
114 115 116
          $menu_path[] = $view_args[] = arg($num);
        }
        else {
117
          unset($menu_args[$num]);
118 119 120
          $menu_path[] = $element;
          if ($element != arg($num)) {
            $use_menu = FALSE;
121 122
          }
        }
123 124 125 126 127 128 129
        // we are only using views that match our URL, up to the
        // point where we hit an inline arg.
        if (!$use_view && $element != arg($num)) {
          break;
        }
      }
      if ($use_view) {
130
        $path = implode('/', $menu_path);
131 132
        $view = views_get_view($view_name);
        $view->args = $view_args;
133
        _views_create_menu_item($items, $view, $path, MENU_CALLBACK, $view_args);
134
      }
135 136 137 138

      if (module_exists('views_ui') && user_access('administer views') && $use_menu) {
        views_menu_admin_items($items, $view_name, $menu_args, $args);
      }
139
    }
140 141 142
  }
}

merlinofchaos's avatar
merlinofchaos committed
143
/**
144
 * Add the adminstrative items to a view.
merlinofchaos's avatar
merlinofchaos committed
145
 */
146 147 148 149 150 151 152 153 154
function views_menu_admin_items(&$items, $view_name, $view_args, $args) {
  // See what the last arg is.
  $last_arg = array_pop($args);
  if (in_array($last_arg, array('edit', 'view', 'clone', 'export'))) {
    array_pop($view_args);
  }
  else {
    $args[] = $last_arg;
  }
merlinofchaos's avatar
merlinofchaos committed
155

156 157 158 159
  $path = implode('/', $args);
  $view = views_get_view($view_name);
  views_ui_add_menu_items($items, $view, $path, $path != $_GET['q'] && !empty($view_args), $view_args);
}
merlinofchaos's avatar
merlinofchaos committed
160

161 162 163 164 165 166 167 168 169 170 171
/**
 * Load all of the URLs we use; this is cached in a special manner
 * in an attempt to make the menu system both flexible and yet not
 * overly intensive.
 */
function views_get_all_urls() {
  $cache = cache_get("views_urls", 'cache_views');
  if ($cache == 0) {
    $views = array();
    $used = array();
    $result = db_query("SELECT name, url FROM {view_view} WHERE page = 1");
merlinofchaos's avatar
merlinofchaos committed
172

173 174 175
    while ($view = db_fetch_object($result)) {
      $used[$view->name] = TRUE;
      $views[$view->name] = $view->url;
merlinofchaos's avatar
merlinofchaos committed
176 177
    }

178 179 180
    views_load_cache();
    $default_views = _views_get_default_views();
    $views_status = variable_get('views_defaults', array());
merlinofchaos's avatar
merlinofchaos committed
181

182 183 184 185 186
    foreach ($default_views as $name => $view) {
      if ($view->page && !$used[$name] && ($views_status[$name] == 'enabled' || (!$view->disabled && $views_status[$name] != 'disabled'))) {
        if ($view->url{0} == '$' || strpos($view->url, '/$') !== FALSE) {
          $views[$view->name] = $view->url;
        }
merlinofchaos's avatar
merlinofchaos committed
187 188
      }
    }
189 190 191 192
    cache_set("views_urls", 'cache_views', serialize($views));
  }
  else {
    $views = unserialize($cache->data);
merlinofchaos's avatar
merlinofchaos committed
193
  }
194 195

  return $views;
merlinofchaos's avatar
merlinofchaos committed
196 197
}

198 199 200
/**
 * Helper function to add a menu item for a view.
 */
201
function _views_create_menu_item(&$items, $view, $path, $local_task_type = MENU_NORMAL_ITEM, $args = array()) {
202
  static $roles = NULL;
203
  if ($roles === NULL) {
204 205
    global $user;
    $roles = array_keys($user->roles);
206 207 208 209 210 211
    if ($user->uid) {
      $roles[] = DRUPAL_AUTHENTICATED_RID;
    }
    else {
      $roles[] = DRUPAL_ANONYMOUS_RID;
    }
212
  }
213
  $title = filter_xss_admin(views_get_title($view, 'menu'));
214
  $type = _views_menu_type($view);
215 216 217
  if ($type == MENU_LOCAL_TASK || $type == MENU_DEFAULT_LOCAL_TASK) {
    $weight = $view->menu_tab_weight;
  }
218
  $access = user_access('access all views') || !$view->access || array_intersect($view->access, $roles);
219
  $items[] = _views_menu_item($path, $title, $view, $args, $access, $type, $weight);
merlinofchaos's avatar
merlinofchaos committed
220

221
  if ($type == MENU_DEFAULT_LOCAL_TASK) {
222
    $items[] = _views_menu_item(dirname($path), $title, $view, $args, $access, $local_task_type, $weight);
223 224 225
  }
}

226 227 228
/**
 * Helper function to create a menu item for a view.
 */
229 230
function _views_menu_item($path, $title, $view, $args, $access, $type, $weight = NULL) {
  array_unshift($args, $view->name);
231
  $retval = array('path' => $path,
232 233 234
    'title' => $title,
    'callback' => 'views_view_page',
    'callback arguments' => $args,
235
    'access' => user_access('access content') && $access,
236 237
    'type' => $type,
  );
238 239 240 241
  if ($weight !== NULL) {
    $retval['weight'] = $weight;
  }
  return $retval;
242 243
}

244 245 246
/**
 * Determine what menu type a view needs to use.
 */
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
function _views_menu_type($view) {
  if ($view->menu) {
    if ($view->menu_tab_default) {
      $type = MENU_DEFAULT_LOCAL_TASK;
    }
    else if ($view->menu_tab) {
      $type = MENU_LOCAL_TASK;
    }
    else {
      $type = MENU_NORMAL_ITEM;
    }
  }
  else {
    $type = MENU_CALLBACK;
  }
  return $type;
}
264

265 266 267 268 269 270 271
/**
 * Implementation of hook_perm
 */
function views_perm() {
  return array('access all views');
}

272
/**
273 274
 * Implementation of hook_block()
 */
275 276 277
function views_block($op = 'list', $delta = 0) {
  $block = array();
  if ($op == 'list') {
278
    views_load_cache();
279
    // Grab views from the database and provide them as blocks.
280
    $result = db_query("SELECT vid, block_title, page_title, name FROM {view_view} WHERE block = 1");
281
    while ($view = db_fetch_object($result)) {
merlinofchaos's avatar
merlinofchaos committed
282
      $block[$view->name]['info'] = filter_xss_admin(views_get_title($view, 'block-info'));
283
    }
284

285
    $default_views = _views_get_default_views();
286 287
    $views_status = variable_get('views_defaults', array());

288
    foreach ($default_views as $name => $view) {
289 290
      if (!isset($block[$name]) && $view->block &&
        ($views_status[$name] == 'enabled' || (!$view->disabled && $views_status[$name] != 'disabled'))) {
291 292
        $title = filter_xss_admin(views_get_title($view, 'block'));
        $block[$name]['info'] = empty($title) ? $name : $title;
293
      }
294 295 296 297 298 299 300 301
    }
    return $block;
  }
  else if ($op == 'view') {
    return views_view_block($delta);
  }
}

302 303 304
// ---------------------------------------------------------------------------
// View Construction

305
/**
306 307 308
 * Ensure that all the arrays in a view exist so we don't run into array
 * operations on a non-array error.
 */
merlinofchaos's avatar
merlinofchaos committed
309
function _views_check_arrays(&$view) {
merlinofchaos's avatar
merlinofchaos committed
310
  $fields = array('field', 'sort', 'argument', 'filter', 'exposed_filter', 'access');
311 312 313 314 315

  foreach($fields as $field) {
    if (!is_array($view->$field)) {
      $view->$field = array();
    }
316 317
  }
  return $view;
318 319
}

320
/**
321 322 323
 * This function loads a view by name or vid; if not found in db, it looks
 * for a default view by that name.
 */
324
function views_get_view($view_name) {
325
  views_load_cache();
326
  $view = _views_load_view($view_name);
327
  if ($view) {
merlinofchaos's avatar
merlinofchaos committed
328
    return $view;
329
  }
merlinofchaos's avatar
merlinofchaos committed
330

331 332
  if (is_int($view_name)) {
    return; // don't bother looking if view_name is an int!
333
  }
merlinofchaos's avatar
merlinofchaos committed
334

335
  $default_views = _views_get_default_views();
merlinofchaos's avatar
merlinofchaos committed
336

337
  if (isset($default_views[$view_name])) {
338 339 340
    $view = $default_views[$view_name];
    $view->is_cacheable = _views_is_cacheable($view);
    return $view;
341
  }
merlinofchaos's avatar
merlinofchaos committed
342 343
}

344
/**
345
 * This views a view by page, and should only be used as a callback.
346 347 348 349 350 351 352 353
 *
 * @param $view_name
 *   The name or id of the view to load
 * @param $args
 *   The arguments from the end of the url. Usually passed from the menu system.
 *
 * @return
 *   The view page.
354
 */
355 356
function views_view_page() {
  $args = func_get_args();
357 358
  $view_name = array_shift($args);
  $view = views_get_view($view_name);
359

360 361 362 363
  if (!$view) {
    drupal_not_found();
    exit;
  }
364

365
  $output = views_build_view('page', $view, $args, $view->use_pager, $view->nodes_per_page);
366
  if ($output === FALSE) {
367 368 369 370
    drupal_not_found();
    exit;
  }

371
  return $output;
372 373
}

374
/**
375 376
 * This views a view by block. Can be used as a callback or programmatically.
 */
377
function views_view_block($vid) {
378
  views_load_cache();
379
  $view = views_get_view($vid);
380

381
  if (!$view || !$view->block) {
382
    return NULL;
383
  }
384

385
  global $user;
386 387 388 389
  if (!$user->roles) {
    return NULL;
  }

390 391 392 393 394
  $roles = array_keys($user->roles);
  if ($view->access && !array_intersect($roles, $view->access)) {
    return NULL;
  }

395
  $content = views_build_view('block', $view, array(), false, $view->nodes_per_block);
396 397
  if ($content) {
    $block['content'] = $content;
merlinofchaos's avatar
merlinofchaos committed
398
    $block['subject'] = filter_xss_admin(views_get_title($view, 'block'));
399 400 401 402 403
    return $block;
  }
  else {
    return NULL;
  }
404 405
}

406
function &views_set_current_view(&$view) {
407 408 409 410 411 412 413 414 415 416 417
  static $current_view = NULL;
  if ($view !== NULL) {
    unset($current_view);
    $current_view = &$view;
    unset($GLOBALS['current_view']);
    $GLOBALS['current_view'] = &$view;
  }
  return $current_view;
}

function &views_get_current_view() {
418 419
  $dummy = NULL;
  return views_set_current_view($dummy);
420 421
}

422
/**
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
 * This builds the basic view.
 * @param $type
 *    'page' -- Produce output as a page, sent through theme.
 *      The only real difference between this and block is that
 *      a page uses drupal_set_title to change the page title.
 *    'block' -- Produce output as a block, sent through theme.
 *    'embed' -- Use this if you want to embed a view onto another page,
 *      and don't want any block or page specific things to happen to it.
 *    'result' -- return an $info array. The array contains:
 *      query: The actual query ran.
 *      countquery: The count query that would be run if limiting was required.
 *      summary: True if an argument was missing and a summary was generated.
 *      level: What level the missing argument was at.
 *      result: Database object you can use db_fetch_object on.
 *    'items' -- return info array as above, except instead of result,
 *      items: An array of objects containing the results of the query.
merlinofchaos's avatar
merlinofchaos committed
439 440
 *    'queries' -- returns an array, summarizing the queries, but does not
 *      run them.
441 442 443 444 445 446 447 448
 * @param $view
 *   The actual view object. Use views_get_view() if you only have the name or
 *   vid.
 * @param $args
 *   args taken from the URL. Not relevant for many views. Can be null.
 * @param $use_pager
 *   If set, use a pager. Set this to the pager id you want it
 *   to use if you plan on using multiple pagers on a page. To go with the
449 450
 *   default setting, set to $view->use_pager. Note that the pager element
 *   id will be decremented in order to have the IDs start at 0.
451 452 453 454 455 456 457
 * @param $limit
 *   Required if $use_pager is set; if $limit is set and $use_pager is
 *   not, this will be the maximum number of records returned. This is ignored
 *   if using a view set to return a random result. To go with the default
 *   setting set to $view->nodes_per_page or $view->nodes_per_block. If
 *   $use_pager is set and this field is not, you'll get a SQL error. Don't
 *   do that!
458 459 460
 * @param $page
 *   $use_pager is false, and $limit is !0, $page tells it what page to start
 *   on, in case for some reason a particular section of view is needed,
merlinofchaos's avatar
merlinofchaos committed
461 462 463
 * @param $offset
 *   If $use_pager == false, skip the first $offset results. Does not work
 *   with pager.
464
 *   without paging on.
465
*/
merlinofchaos's avatar
merlinofchaos committed
466
function views_build_view($type, &$view, $args = array(), $use_pager = false, $limit = 0, $page = 0, $offset = 0) {
467 468
  views_load_cache();

469 470 471 472 473
  // Fix a number of annoying whines when NULL is passed in..
  if ($args == NULL) {
    $args = array();
  }

474
  views_set_current_view($view);
merlinofchaos's avatar
merlinofchaos committed
475

476 477
  $view->build_type = $type;
  $view->type = ($type == 'block' ? $view->block_type : $view->page_type);
478

479 480
  if ($view->view_args_php) {
    ob_start();
481 482 483 484
    $result = eval($view->view_args_php);
    if (is_array($result)) {
      $args = $result;
    }
485 486 487
    ob_end_clean();
  }

merlinofchaos's avatar
merlinofchaos committed
488 489 490 491 492 493
  // Call a hook that'll let modules modify the view query before it is created
  foreach (module_implements('views_pre_query') as $module) {
    $function = $module .'_views_pre_query';
    $output .= $function($view);
  }

494
  $info = _views_get_query($view, $args);
495

496 497
  if ($info['fail']) {
    return FALSE;
498
  }
499

merlinofchaos's avatar
merlinofchaos committed
500 501 502
  if ($type == 'queries') {
    return $info;
  }
503 504 505

  $query = db_rewrite_sql($info['query'], 'node');

merlinofchaos's avatar
merlinofchaos committed
506
  $items = array();
507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524
  if ($query) {
    if ($use_pager) {
      $cquery = db_rewrite_sql($info['countquery'], 'node', 'nid', $info['rewrite_args']);
      $result = pager_query($query, $limit, $use_pager - 1, $cquery, $info['args']);
      $view->total_rows = $GLOBALS['pager_total_items'][$use_pager - 1];
    }
    else {
      $result = ($limit ? db_query_range($query, $info['args'], $page * $limit + $offset, $limit) : db_query($query, $info['args']));
    }
    $view->num_rows = db_num_rows($result);
    if ($type == 'result') {
      $info['result'] = $result;
      return $info;
    }

    while ($item = db_fetch_object($result)) {
      $items[] = $item;
    }
525
  }
526 527 528 529 530

  if ($type == 'items') {
    $info['items'] = $items;
    return $info;
  }
531

532 533 534
  // Call a hook that'll let modules modify the view just before it is displayed.
  foreach (module_implements('views_pre_view') as $module) {
    $function = $module .'_views_pre_view';
535
    $output .= $function($view, $items);
536 537
  }

538 539
  $view->real_url = views_get_url($view, $args);

540
  $view->use_pager = $use_pager;
541
  $view->pager_limit = $limit;
542
  $output .= views_theme('views_view', $view, $type, $items, $info['level'], $args);
merlinofchaos's avatar
merlinofchaos committed
543

544 545 546
  // Call a hook that'll let modules modify the view just after it is displayed.
  foreach (module_implements('views_post_view') as $module) {
    $function = $module .'_views_post_view';
547
    $output .= $function($view, $items, $output);
548 549
  }

merlinofchaos's avatar
merlinofchaos committed
550
  return $output;
551 552
}

553 554 555
// ---------------------------------------------------------------------------
// Utility

556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571
/**
 * Load the cache sub-module
 */
function views_load_cache() {
  $path = drupal_get_path('module', 'views');
  require_once("./$path/views_cache.inc");
}

/**
 * Load the query sub-module
 */
function views_load_query() {
  $path = drupal_get_path('module', 'views');
  require_once("./$path/views_query.inc");
}

merlinofchaos's avatar
merlinofchaos committed
572
/**
573
 * Easily theme any item to a view.
merlinofchaos's avatar
merlinofchaos committed
574
 * @param $function
575 576 577 578 579 580 581
 *   The name of the function to call.
 * @param $view
 *   The view being themed.
 */
function views_theme() {
  $args = func_get_args();
  $function = array_shift($args);
582
  $view = $args[0];
583 584 585 586 587 588 589 590 591 592

  if (!($func = theme_get_function($function . "_" . $view->name))) {
    $func = theme_get_function($function);
  }

  if ($func) {
    return call_user_func_array($func, $args);
  }
}

593 594 595 596 597 598 599 600 601 602 603 604 605 606
/**
 * Easily theme any item to a field name.
 * field name will be in the format of TABLENAME_FIELDNAME
 * You have to understand a bit about the views data to utilize this.
 *
 * @param $function
 *   The name of the function to call.
 * @param $field_name
 *   The field being themed.
 */
function views_theme_field() {
  $args = func_get_args();
  $function = array_shift($args);
  $field_name = array_shift($args);
607
  $view = array_pop($args);
608

609 610 611 612
  if (!($func = theme_get_function($function . '_' . $view->name . '_' . $field_name))) {
    if (!($func = theme_get_function($function . '_' . $field_name))) {
      $func = theme_get_function($function);
    }
613 614 615 616 617 618 619
  }

  if ($func) {
    return call_user_func_array($func, $args);
  }
}

620
/**
621
 * Figure out what timezone we're in; needed for some date manipulations.
622
 */
623 624 625 626 627 628 629
function _views_get_timezone() {
  global $user;
  if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
    $timezone = $user->timezone;
  }
  else {
    $timezone = variable_get('date_default_timezone', 0);
merlinofchaos's avatar
merlinofchaos committed
630 631
  }

632 633 634 635
  // set up the database timezone
  if (in_array($GLOBALS['db_type'], array('mysql', 'mysqli'))) {
    static $already_set = false;
    if (!$already_set) {
merlinofchaos's avatar
merlinofchaos committed
636
      if ($GLOBALS['db_type'] == 'mysqli' || version_compare(mysql_get_server_info(), '4.1.3', '>=')) {
637 638 639 640 641 642
        db_query("SET @@session.time_zone = '+00:00'");
      } 
      $already_set = true;
    }
  }

643
  return $timezone;
644
}
645 646 647 648 649 650 651

/**
 * Figure out what the URL of the view we're currently looking at is.
 */
function views_get_url($view, $args) {
  $url = $view->url;

652 653 654 655 656
  if (!empty($url)) {
    $where = 1;
    foreach ($args as $arg) {
      // This odd construct prevents us from strposing once there is no
      // longer an $arg to replace.
657
      if ($where && $where = strpos($url, '$arg')) {
658 659 660 661 662
        $url = substr_replace($url, $arg, $where, 4);
      }
      else {
        $url .= "/$arg";
      }
663 664 665 666 667 668
    }
  }

  return $url;
}

669
/**
670 671
 * Figure out what the title of a view should be.
 */
672
function views_get_title($view, $context = 'menu', $args = NULL) {
673
  if (($context == 'menu' || $context == 'admin') && $view->menu_title)
674 675
    return $view->menu_title;

676 677 678 679
  if ($context == 'block-info') {
    return $view->description ? $view->description : $view->name;
  }

680
  if ($args === NULL) {
681
    $args = $view->args;
682
  }
683 684 685 686 687 688 689 690 691 692
  // Grab the title from the highest argument we got. If there is no such
  // title, track back until we find a title.

  if (is_array($args)) {
    $rargs = array_reverse(array_keys($args));
    foreach ($rargs as $arg_id) {
      if ($title = $view->argument[$arg_id]['title']) {
        break;
      }
    }
693
  }
694

695
  if (!$title && ($context == 'menu' || $context == 'page' || $context == 'admin')) {
696
    $title = $view->page_title;
697 698
  }

699
  if (!$title && ($context == 'block' || $context == 'admin')) {
700
    $title = $view->block_title;
701 702
  }

703 704 705 706
  if (!$view->argument) {
    return $title;
  }

merlinofchaos's avatar
merlinofchaos committed
707
  views_load_cache();
708 709
  $arginfo = _views_get_arguments();
  foreach ($view->argument as $i => $arg) {
710 711 712
    if (!isset($args[$i])) {
      break;
    }
713
    $argtype = $arg['type'];
714 715 716 717
    if ($arg['wildcard'] == $args[$i] && $arg['wildcard_substitution'] != '') {
      $title = str_replace("%" . ($i + 1), $arg['wildcard_substitution'], $title);
    }
    else if (function_exists($arginfo[$argtype]['handler'])) {
718
      // call the handler
719
      $rep = $arginfo[$argtype]['handler']('title', $args[$i], $argtype);
720 721 722 723 724 725
      $title = str_replace("%" . ($i + 1), $rep, $title);
    }
  }
  return $title;
}

726
/**
727 728
 * Determine whether or not a view is cacheable. A view is not cacheable if
 * there is some kind of user input or data required. For example, views
729
 * that need to restrict to the 'current' user, or any views that require
730 731
 * arguments or allow click-sorting are not cacheable.
 */
merlinofchaos's avatar
merlinofchaos committed
732 733
function _views_is_cacheable(&$view) {
  // views with arguments are immediately not cacheable.
734
  if (!empty($view->argument) || !empty($view->exposed_filter) || !empty($view->no_cache)) {
merlinofchaos's avatar
merlinofchaos committed
735
    return false;
736
  }
merlinofchaos's avatar
merlinofchaos committed
737

738
  views_load_cache();
merlinofchaos's avatar
merlinofchaos committed
739 740 741
  $filters = _views_get_filters();

  foreach ($view->filter as $i => $filter) {
742
    if ($filters[$filter['field']]['cacheable'] == 'no')  {
merlinofchaos's avatar
merlinofchaos committed
743
      return false;
744
    }
merlinofchaos's avatar
merlinofchaos committed
745
  }
746

747
  foreach ($view->field as $i => $field) {
748
    if ($field['sortable']) {
749
      return false;
750 751
    }
  }
merlinofchaos's avatar
merlinofchaos committed
752 753 754
  return true;
}

755 756 757 758
/**
 * Invalidate the views cache, forcing a rebuild on the next grab of table data.
 */
function views_invalidate_cache() {
759
  cache_clear_all('*', 'cache_views', true);
760 761
}

762 763 764
// ---------------------------------------------------------------------------
// Database functions

765
/**
766 767
 * Provide all the fields in a view.
 */
768
function _views_view_fields() {
769
  return array('vid', 'name', 'description', 'access', 'page', 'page_title', 'page_header', 'page_header_format', 'page_footer', 'page_footer_format', 'page_empty', 'page_empty_format', 'page_type', 'use_pager', 'nodes_per_page', 'url', 'menu', 'menu_tab', 'menu_tab_default', 'menu_tab_weight', 'menu_title', 'block', 'block_title', 'block_use_page_header', 'block_header', 'block_header_format', 'block_use_page_footer', 'block_footer', 'block_footer_format', 'block_use_page_empty', 'block_empty', 'block_empty_format', 'block_type', 'nodes_per_block', 'block_more', 'url', 'breadcrumb_no_home', 'changed', 'view_args_php', 'is_cacheable');
770 771
}

772
/**
773 774
 * Delete a view from the database.
 */
775 776
function _views_delete_view($view) {
  $view->vid = intval($view->vid);
777
  if (!$view->vid) {
778
    return;
779
  }
780

merlinofchaos's avatar
merlinofchaos committed
781 782 783 784
  db_query("DELETE FROM {view_view} where vid=%d", $view->vid);
  db_query("DELETE FROM {view_sort} where vid=%d", $view->vid);
  db_query("DELETE FROM {view_argument} where vid=%d", $view->vid);
  db_query("DELETE FROM {view_tablefield} where vid=%d", $view->vid);
785

786
  cache_clear_all('views_query:' . $view->name, 'cache_views');
787 788
}

789 790 791 792 793 794 795
/**
 * Load a view from the database -- public version of the function.
 */
function views_load_view($arg) {
  return _views_load_view($arg);
}

796
/**
797
 * Load a view from the database.
798
 * (deprecated; use views_load_view in favor of this function).
799
 */
800
function _views_load_view($arg) {
801 802 803 804 805
  static $cache = array();
  $which = is_numeric($arg) ? 'vid' : 'name';
  if (isset($cache[$which][$arg])) {
    return $cache[$which][$arg];
  }
806

807
  $where = (is_numeric($arg) ? "v.vid =  %d" : "v.name = '%s'");
808
  $view = db_fetch_object(db_query("SELECT v.* FROM {view_view} v WHERE $where", $arg));
809

810
  if (!$view->name) {
811
    return NULL;
812
  }
813

814 815
  $view->access = ($view->access ? explode(', ', $view->access) : array());

816 817 818
  // load the sorting criteria too.
  $result = db_query("SELECT * FROM {view_sort} vs WHERE vid = $view->vid ORDER BY position ASC");

merlinofchaos's avatar
merlinofchaos committed
819 820
  $view->sort = array();
  while ($sort = db_fetch_array($result)) {
821 822 823
    if (substr($sort['field'], 0, 2) == 'n.') {
      $sort['field'] = 'node' . substr($sort['field'], 1);
    }
824
    $sort['id'] = $sort['field'];
merlinofchaos's avatar
merlinofchaos committed
825
    $view->sort[] = $sort;
826 827
  }

828
  $result = db_query("SELECT * FROM {view_argument} WHERE vid = $view->vid ORDER BY position ASC");
829 830 831

  $view->argument = array();
  while ($arg = db_fetch_array($result)) {
832
    $arg['id'] = $arg['type'];
833 834 835
    $view->argument[] = $arg;
  }

836
  $result = db_query("SELECT * FROM {view_tablefield} WHERE vid = $view->vid ORDER BY position ASC");
837

838
  $view->field = array();
839
  while ($arg = db_fetch_array($result)) {
840 841 842
    if ($arg['tablename'] == 'n') {
      $arg['tablename'] = 'node';
    }
843
    $arg['id'] = $arg['fullname'] = "$arg[tablename].$arg[field]";
844 845
    $arg['queryname'] = "$arg[tablename]_$arg[field]";
    $view->field[] = $arg;
846 847
  }

848
  $result = db_query("SELECT * FROM {view_filter} WHERE vid = $view->vid ORDER BY position ASC");
849

850
  views_load_cache();
851
  $filters = _views_get_filters();
852
  $view->filter = array();
853 854 855
  while ($filter = db_fetch_array($result)) {
    if (substr($filter['field'], 0, 2) == 'n.') {
      $filter['field'] = 'node' . substr($filter['field'], 1);
856
    }
857

merlinofchaos's avatar
merlinofchaos committed
858 859 860
    if ($filter['operator'] == 'AND' ||
        $filter['operator'] == 'OR' ||
        $filter['operator'] == 'NOR' ||
861 862 863 864 865 866 867
        $filters[$filter['field']]['value-type'] == 'array' ) {
      if ($filter['value'] !== NULL && $filter['value'] !== '') {
        $filter['value'] = explode(',', $filter['value']);
      }
      else {
        $filter['value'] = array();
      }
868
    }
869
    $filter['id'] = $filter['field'];
870
    $view->filter[] = $filter;
871 872
  }

873 874 875 876 877 878 879 880
  $result = db_query("SELECT * FROM {view_exposed_filter} WHERE vid = $view->vid ORDER BY position ASC");

  $view->exposed_filter = array();
  while ($arg = db_fetch_array($result)) {
    $arg['id'] = $arg['field'];
    $view->exposed_filter[] = $arg;
  }

881
  $cache['vid'][$view->vid] = $view;
merlinofchaos's avatar
merlinofchaos committed
882
  $cache['name'][$view->name] = $view;
883

884 885 886
  return $view;
}

887
/**
888 889
 * Save a view to the database.
 */
890
function _views_save_view($view) {
merlinofchaos's avatar
merlinofchaos committed
891
  _views_check_arrays($view);
merlinofchaos's avatar
merlinofchaos committed
892

893
  $view->is_cacheable = _views_is_cacheable($view);
894

895 896
  $view->access = implode(', ', $view->access);

merlinofchaos's avatar
merlinofchaos committed
897
  $view->changed = time();
898 899 900 901 902 903 904 905 906 907 908 909
  $fields = _views_view_fields();
  if ($view->vid) {
    // update
    // Prepare the query:
    foreach ($view as $key => $value) {
      if (in_array($key, $fields)) {
        $q[] = db_escape_string($key) ." = '%s'";
        $v[] = $value;
      }
    }

    // Update the view in the database: