b2evolution PHP Cross Reference Blogging Systems

Source: /inc/items/model/_itemlist.class.php - 855 lines - 29533 bytes - Summary - Text - Print

Description: This file implements the ItemList class 2. This is the object handling item/post/article lists.

   1  <?php
   2  /**
   3   * This file implements the ItemList class 2.
   4   *
   5   * This is the object handling item/post/article lists.
   6   *
   7   * This file is part of the evoCore framework - {@link http://evocore.net/}
   8   * See also {@link http://sourceforge.net/projects/evocms/}.
   9   *
  10   * @copyright (c)2003-2014 by Francois Planque - {@link http://fplanque.com/}
  11   *
  12   * {@internal License choice
  13   * - If you have received this file as part of a package, please find the license.txt file in
  14   *   the same folder or the closest folder above for complete license terms.
  15   * - If you have received this file individually (e-g: from http://evocms.cvs.sourceforge.net/)
  16   *   then you must choose one of the following licenses before using the file:
  17   *   - GNU General Public License 2 (GPL) - http://www.opensource.org/licenses/gpl-license.php
  18   *   - Mozilla Public License 1.1 (MPL) - http://www.opensource.org/licenses/mozilla1.1.php
  19   * }}
  20   *
  21   * {@internal Open Source relicensing agreement:
  22   * }}
  23   *
  24   * @package evocore
  25   *
  26   * {@internal Below is a list of authors who have contributed to design/coding of this file: }}
  27   * @author fplanque: Francois PLANQUE.
  28   *
  29   * @version $Id: _itemlist.class.php 6136 2014-03-08 07:59:48Z manuel $
  30   */
  31  if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );
  32  
  33  load_class( '/items/model/_itemlistlight.class.php', 'ItemListLight' );
  34  
  35  
  36  /**
  37   * Item List Class 2
  38   *
  39   * This SECOND implementation will deprecate the first one when finished.
  40   *
  41   * @package evocore
  42   */
  43  class ItemList2 extends ItemListLight
  44  {
  45      /**
  46       * @var array
  47       */
  48      var $prevnext_Item = array();
  49  
  50      /**
  51       * Navigate through this target items
  52       * It can be an Chapter ID or a User ID, depends from the post_navigation coll settings
  53       *
  54       * @var integer
  55       */
  56      var $nav_target;
  57  
  58      /**
  59       * Constructor
  60       *
  61       * @todo  add param for saved session filter set
  62       *
  63       * @param Blog
  64       * @param mixed Default filter set: Do not show posts before this timestamp, can be 'now'
  65       * @param mixed Default filter set: Do not show posts after this timestamp, can be 'now'
  66       * @param integer|NULL Limit
  67       * @param string name of cache to be used
  68       * @param string prefix to differentiate page/order params when multiple Results appear one same page
  69       * @param string Name to be used when saving the filterset (leave empty to use default for collection)
  70       * @param array restrictions for itemlist (position, contact, firm, ...) key: restriction name, value: ID of the restriction
  71       */
  72  	function ItemList2(
  73              & $Blog,
  74              $timestamp_min = NULL,       // Do not show posts before this timestamp
  75              $timestamp_max = NULL,            // Do not show posts after this timestamp
  76              $limit = 20,
  77              $cache_name = 'ItemCache',     // name of cache to be used
  78              $param_prefix = '',
  79              $filterset_name = ''                // Name to be used when saving the filterset (leave empty to use default for collection)
  80          )
  81      {
  82          global $Settings;
  83  
  84          // Call parent constructor:
  85          parent::ItemListLight( $Blog, $timestamp_min, $timestamp_max, $limit, $cache_name, $param_prefix, $filterset_name );
  86      }
  87  
  88  
  89      /**
  90       * We want to preview a single post, we are going to fake a lot of things...
  91       */
  92  	function preview_from_request()
  93      {
  94          global $current_User;
  95  
  96          if( empty($current_User) )
  97          { // dh> only logged in user's can preview. Alternatively we need those checks where $current_User gets used below.
  98              return;
  99          }
 100  
 101          global $DB, $localtimenow, $Messages, $BlogCache;
 102          global $Plugins;
 103  
 104          if( $this->Blog->get_setting( 'allow_html_post' ) )
 105          {    // HTML is allowed for this post
 106              $text_format = 'html';
 107          }
 108          else
 109          {    // HTML is disallowed for this post
 110              $text_format = 'htmlspecialchars';
 111          }
 112  
 113          $preview_userid = param( 'preview_userid', 'integer', true );
 114          $post_status = param( 'post_status', 'string', true );
 115          $post_locale = param( 'post_locale', 'string', $current_User->locale );
 116          $content = param( 'content', $text_format, true );
 117          $post_title = param( 'post_title', $text_format, true );
 118          $post_titletag = param( 'titletag', 'string', true );
 119          $post_excerpt = param( 'post_excerpt', 'string', true );
 120          $post_url = param( 'post_url', 'string', '' );
 121          check_categories_nosave( $post_category, $post_extracats );
 122          $post_views = param( 'post_views', 'integer', 0 );
 123          $renderers = param( 'renderers', 'array/string', array('default') );
 124          if( ! is_array($renderers) )
 125          { // dh> workaround for param() bug. See rev 1.93 of /inc/_misc/_misc.funcs.php
 126              $renderers = array('default');
 127          }
 128          if( $post_category == 0 )
 129          {
 130              $post_category = $this->Blog->get_default_cat_ID();
 131          }
 132          $comment_Blog = & $BlogCache->get_by_ID( get_catblog( $post_category ) );
 133          if( ( $comment_Blog->get_setting( 'allow_comments' ) != 'never' ) && ( $comment_Blog->get_setting( 'disable_comments_bypost' ) ) )
 134          { // param is required
 135              $post_comment_status = param( 'post_comment_status', 'string', true );
 136          }
 137          else
 138          {
 139              $post_comment_status = $comment_Blog->get_setting( 'allow_comments' );
 140          }
 141  
 142  
 143          // Get issue date, using the user's locale (because it's entered like this in the form):
 144          locale_temp_switch( $current_User->locale );
 145  
 146          param_date( 'item_issue_date', T_('Please enter a valid issue date.'), false );
 147          // TODO: dh> get_param() is always true here, also on invalid dates:
 148          if( strlen(get_param('item_issue_date')) )
 149          { // only set it, if a date was given:
 150              param_time( 'item_issue_time' );
 151              $item_issue_date = form_date( get_param( 'item_issue_date' ), get_param( 'item_issue_time' ) ); // TODO: cleanup...
 152          }
 153          else
 154          {
 155              $item_issue_date = date( 'Y-m-d H:i:s', $localtimenow );
 156          }
 157          locale_restore_previous();
 158  
 159          $item_typ_ID = param( 'item_typ_ID', 'integer', NULL );
 160          $item_st_ID = param( 'item_st_ID', 'integer', NULL );
 161          $item_assigned_user_ID = param( 'item_assigned_user_ID', 'integer', NULL );
 162          $item_deadline = param( 'item_deadline', 'string', NULL );
 163          $item_priority = param( 'item_priority', 'integer', NULL ); // QUESTION: can this be also empty/NULL?
 164  
 165          // Do some optional filtering on the content
 166          // Typically stuff that will help the content to validate
 167          // Useful for code display.
 168          // Will probably be used for validation also.
 169          $Plugins_admin = & get_Plugins_admin();
 170          $params = array( 'object_type' => 'Item', 'object_Blog' => & $comment_Blog );
 171          $Plugins_admin->filter_contents( $post_title /* by ref */, $content /* by ref */, $renderers, $params );
 172  
 173          $post_title = format_to_post( $post_title );
 174          $content = format_to_post( $content );
 175  
 176          $post_ID = param('post_ID', 'integer', 0);
 177  
 178          $this->sql = "SELECT
 179              $post_ID AS post_ID,
 180              $preview_userid AS post_creator_user_ID,
 181              $preview_userid AS post_lastedit_user_ID,
 182              '$item_issue_date' AS post_datestart,
 183              '$item_issue_date' AS post_datecreated,
 184              '$item_issue_date' AS post_datemodified,
 185              '$item_issue_date' AS post_last_touched_ts,
 186              0 AS post_dateset,
 187              '".$DB->escape($post_status)."' AS post_status,
 188              '".$DB->escape($post_locale)."' AS post_locale,
 189              '".$DB->escape($content)."' AS post_content,
 190              '".$DB->escape($post_title)."' AS post_title,
 191              '".$DB->escape($post_titletag)."' AS post_titletag,
 192              '".$DB->escape($post_excerpt)."' AS post_excerpt,
 193              NULL AS post_excerpt_autogenerated,
 194              NULL AS post_urltitle,
 195              NULL AS post_canonical_slug_ID,
 196              NULL AS post_tiny_slug_ID,
 197              '".$DB->escape($post_url)."' AS post_url,
 198              $post_category AS post_main_cat_ID,
 199              $post_views AS post_views,
 200              '' AS post_flags,
 201              'noreq' AS post_notifications_status,
 202              NULL AS post_notifications_ctsk_ID,
 203              ".bpost_count_words( $content )." AS post_wordcount,
 204              ".$DB->quote($post_comment_status)." AS post_comment_status,
 205              '".$DB->escape( implode( '.', $renderers ) )."' AS post_renderers,
 206              ".$DB->quote($item_assigned_user_ID)." AS post_assigned_user_ID,
 207              ".$DB->quote($item_typ_ID)." AS post_ptyp_ID,
 208              ".$DB->quote($item_st_ID)." AS post_pst_ID,
 209              ".$DB->quote($item_deadline)." AS post_datedeadline,
 210              ".$DB->quote($item_priority)." AS post_priority,";
 211  
 212          $this->sql .= $DB->quote(param( 'item_order', 'double', NULL )).' AS post_order'.",\n"
 213                                  .$DB->quote(param( 'item_featured', 'integer', NULL )).' AS post_featured'."\n";
 214          $this->total_rows = 1;
 215          $this->total_pages = 1;
 216          $this->page = 1;
 217  
 218          // ATTENTION: we skip the parent on purpose here!! fp> refactor
 219          DataObjectList2::query( false, false, false, 'PREVIEW QUERY' );
 220  
 221          $Item = & $this->Cache->instantiate( $this->rows[0] );
 222  
 223          // set Item settings
 224          $Item->set_setting( 'hide_teaser', param( 'item_hideteaser', 'integer', 0 ) );
 225          $Item->set_setting( 'post_metadesc', param( 'metadesc', 'string', true ) );
 226          $Item->set_setting( 'post_custom_headers', param( 'custom_headers', 'string', true ) );
 227  
 228          // set custom Item settings
 229          foreach( array( 'double', 'varchar' ) as $type )
 230          {
 231              $count_custom_field = $comment_Blog->get_setting( 'count_custom_'.$type );
 232              $param_type = ( $type == 'varchar' ) ? 'string' : $type;
 233              for( $i = 1; $i <= $count_custom_field; $i++ )
 234              { // For each custom double field:
 235                  $field_guid = $comment_Blog->get_setting( 'custom_'.$type.$i );
 236                  $Item->set_setting( 'custom_'.$type.'_'.$field_guid, param( 'item_'.$type.'_'.$field_guid, $param_type, NULL ) );
 237              }
 238          }
 239  
 240          // Trigger plugin event, allowing to manipulate or validate the item before it gets previewed
 241          $Plugins->trigger_event( 'AppendItemPreviewTransact', array( 'Item' => & $Item ) );
 242  
 243          if( $Messages->has_errors() )
 244          {
 245              $errcontent = $Messages->display( T_('Invalid post, please correct these errors:'), '', false );
 246              $Item->content = $errcontent."\n<hr />\n".$content;
 247          }
 248  
 249          // little funky fix for IEwin, rawk on that code
 250          global $Hit;
 251          if( ($Hit->is_winIE()) && (!isset($IEWin_bookmarklet_fix)) )
 252          { // QUESTION: Is this still needed? What about $IEWin_bookmarklet_fix? (blueyed)
 253              $Item->content = preg_replace('/\%u([0-9A-F]{4,4})/e', "'&#'.base_convert('\\1',16,10). ';'", $Item->content);
 254          }
 255      }
 256  
 257  
 258      /**
 259       * Run Query: GET DATA ROWS *** HEAVY ***
 260       */
 261  	function query()
 262      {
 263          global $DB;
 264  
 265          if( !is_null( $this->rows ) )
 266          { // Query has already executed:
 267              return;
 268          }
 269  
 270          // INIT THE QUERY:
 271          $this->query_init();
 272  
 273          $select_temp_order = '';
 274          if( !empty( $this->ItemQuery->order_by ) && strpos( $this->ItemQuery->order_by, 'post_order' ) !== false )
 275          {    // Move the items with NULL order to the end of the list
 276              $select_temp_order = ', IF( post_order IS NULL, 999999999, post_order ) AS temp_order';
 277              $this->ItemQuery->ORDER_BY( str_replace( 'post_order', 'temp_order', $this->ItemQuery->get_order_by( '' ) ) );
 278          }
 279  
 280          // Results style orders:
 281          // $this->ItemQuery->ORDER_BY_prepend( $this->get_order_field_list() );
 282  
 283  
 284          // We are going to proceed in two steps (we simulate a subquery)
 285          // 1) we get the IDs we need
 286          // 2) we get all the other fields matching these IDs
 287          // This is more efficient than manipulating all fields at once.
 288  
 289          // *** STEP 1 ***
 290          // walter> Accordding to the standart, to DISTINCT queries, all columns used
 291          // in ORDER BY must appear in the query. This make que query work with PostgreSQL and
 292          // other databases.
 293          // fp> That can dramatically fatten the returned data. You must handle this in the postgres class (check that order fields are in select)
 294          $step1_sql = 'SELECT DISTINCT '.$this->Cache->dbIDname // .', '.implode( ', ', $order_cols_to_select )
 295                                      .$select_temp_order
 296                                      .$this->ItemQuery->get_from()
 297                                      .$this->ItemQuery->get_where()
 298                                      .$this->ItemQuery->get_group_by()
 299                                      .$this->ItemQuery->get_order_by()
 300                                      .$this->ItemQuery->get_limit();
 301  
 302          // echo $DB->format_query( $step1_sql );
 303  
 304          // Get list of the IDs we need:
 305          $ID_list = implode( ',', $DB->get_col( $step1_sql, 0, 'ItemList2::Query() Step 1: Get ID list' ) );
 306  
 307          // *** STEP 2 ***
 308          $this->sql = 'SELECT *'.$select_temp_order.'
 309                            FROM '.$this->Cache->dbtablename;
 310          if( !empty($ID_list) )
 311          {
 312              $this->sql .= ' WHERE '.$this->Cache->dbIDname.' IN ('.$ID_list.') '
 313                                          .$this->ItemQuery->get_order_by();
 314          }
 315          else
 316          {
 317              $this->sql .= ' WHERE 0';
 318          }
 319  
 320          //echo $DB->format_query( $this->sql );
 321  
 322          // ATTENTION: we skip the parent on purpose here!! fp> refactor
 323          DataObjectList2::query( false, false, false, 'ItemList2::Query() Step 2' );
 324      }
 325  
 326  
 327      /**
 328       * If the list is sorted by category...
 329        *
 330        * This is basically just a stub for backward compatibility
 331       */
 332      function & get_item()
 333      {
 334          if( $this->group_by_cat == 1 )
 335          {    // This is the first call to get_item() after get_category_group()
 336              $this->group_by_cat = 2;
 337              // Return the object we already got in get_category_group():
 338              return $this->current_Obj;
 339          }
 340  
 341          $Item = & parent::get_next();
 342  
 343          if( !empty($Item) && $this->group_by_cat == 2 && $Item->main_cat_ID != $this->main_cat_ID )
 344          {    // We have just hit a new category!
 345              $this->group_by_cat == 0; // For info only.
 346              $r = false;
 347              return $r;
 348          }
 349  
 350          //pre_dump( $Item );
 351  
 352          return $Item;
 353      }
 354  
 355  
 356      /**
 357       * Get all tags used in current ItemList
 358       *
 359       * @todo caching in case of multiple calls
 360       *
 361       * @return array
 362       */
 363  	function get_all_tags()
 364      {
 365          $all_tags = array();
 366  
 367          for( $i=0; $i<$this->result_num_rows; $i++ )
 368          {
 369              /**
 370               * @var Item
 371               */
 372              $l_Item = & $this->get_by_idx( $i );
 373              $l_tags = $l_Item->get_tags();
 374              $all_tags = array_merge( $all_tags, $l_tags );
 375          }
 376  
 377          // Keep each tag only once:
 378          $all_tags = array_unique( $all_tags );
 379  
 380          return $all_tags;
 381      }
 382  
 383  
 384  
 385      /**
 386       * Returns values needed to make sort links for a given column
 387       * Needed because the order is not handled by the result class.
 388       * Reason: Sometimes the item list needs to be ordered without having a display table, and columns. The result class order is based on columns.
 389       *
 390       * Returns an array containing the following values:
 391       *  - current_order : 'ASC', 'DESC' or ''
 392       *  - order_asc : url needed to order in ascending order
 393       *  - order_desc
 394       *  - order_toggle : url needed to toggle sort order
 395       *
 396       * @param integer column to sort
 397       * @return array
 398       */
 399  	function get_col_sort_values( $col_idx )
 400      {
 401          $col_order_fields = $this->cols[$col_idx]['order'];
 402  
 403          // Current order:
 404          if( $this->filters['orderby'] == $col_order_fields || $this->param_prefix.$this->filters['orderby'] == $col_order_fields )
 405          {
 406              $col_sort_values['current_order'] = $this->filters['order'];
 407          }
 408          else
 409          {
 410              $col_sort_values['current_order'] = '';
 411          }
 412  
 413  
 414          // Generate sort values to use for sorting on the current column:
 415          $col_sort_values['order_asc'] = regenerate_url( array($this->param_prefix.'order',$this->param_prefix.'orderby'),
 416                                                                              $this->param_prefix.'order=ASC&amp;'.$this->param_prefix.'orderby='.$col_order_fields );
 417          $col_sort_values['order_desc'] = regenerate_url(  array($this->param_prefix.'order',$this->param_prefix.'orderby'),
 418                                                                              $this->param_prefix.'order=DESC&amp;'.$this->param_prefix.'orderby='.$col_order_fields );
 419  
 420          if( !$col_sort_values['current_order'] && isset( $this->cols[$col_idx]['default_dir'] ) )
 421          {    // There is no current order on this column and a default order direction is set for it
 422              // So set a default order direction for it
 423  
 424              if( $this->cols[$col_idx]['default_dir'] == 'A' )
 425              {    // The default order direction is A, so set its toogle  order to the order_asc
 426                  $col_sort_values['order_toggle'] = $col_sort_values['order_asc'];
 427              }
 428              else
 429              { // The default order direction is A, so set its toogle order to the order_desc
 430                  $col_sort_values['order_toggle'] = $col_sort_values['order_desc'];
 431              }
 432          }
 433          elseif( $col_sort_values['current_order'] == 'ASC' )
 434          {    // There is an ASC current order on this column, so set its toogle order to the order_desc
 435              $col_sort_values['order_toggle'] = $col_sort_values['order_desc'];
 436          }
 437          else
 438          { // There is a DESC or NO current order on this column,  so set its toogle order to the order_asc
 439              $col_sort_values['order_toggle'] = $col_sort_values['order_asc'];
 440          }
 441  
 442          // pre_dump( $col_sort_values );
 443  
 444          return $col_sort_values;
 445      }
 446  
 447  
 448  
 449      /**
 450       * Link to previous and next link in collection
 451       */
 452  	function prevnext_item_links( $params )
 453      {
 454          global $posttypes_specialtypes;
 455  
 456          $params = array_merge( array(
 457                                      'template' => '$prev$$separator$$next$',
 458                                      'prev_start' => '',
 459                                      'prev_text' => '&laquo; $title$',
 460                                      'prev_end' => '',
 461                                      'prev_no_item' => '',
 462                                      'prev_class' => '',
 463                                      'separator' => '',
 464                                      'next_start' => '',
 465                                      'next_text' => '$title$ &raquo;',
 466                                      'next_end' => '',
 467                                      'next_no_item' => '',
 468                                      'next_class' => '',
 469                                      'target_blog' => '',
 470                                      'post_navigation' => $this->Blog->get_setting( 'post_navigation' ),
 471                                      'types' => '-'.implode(',',$posttypes_specialtypes), // Exclude pages, intros & sidebar stuff
 472                                      'featured' => NULL,
 473                                  ), $params );
 474  
 475          $current_Item = & $this->get_by_idx(0);
 476          // Note: current_Item may be null when User doesn't have permission
 477          if( $current_Item )
 478          { // current Item is available, init navigation target
 479              switch( $params['post_navigation'] )
 480              {
 481                  case 'same_category': // sometimes requires the 'cat' param because a post may belong to multiple categories
 482                      if( empty( $this->nav_target ) )
 483                      {
 484                          $this->nav_target = $current_Item->main_cat_ID;
 485                      }
 486                      $this->filters['cat_array'][] = $this->nav_target;
 487                      // Note: If there will be other navigation type ( like tag ) with params, those filters must be removed. 
 488                      break;
 489  
 490                  case 'same_author': // This doesn't require extra param because a post always has only one author
 491                      $this->filters['authors'] = $current_Item->creator_user_ID;
 492                      // reset cat filters because only the authors are important
 493                      $this->filters['cat_array'] = array();
 494                      break;
 495  
 496                  default:
 497                      break;
 498              }
 499          }
 500  
 501          $prev = $this->prev_item_link( $params['prev_start'], $params['prev_end'], $params[ 'prev_text' ], $params[ 'prev_no_item' ], false, $params[ 'target_blog'], $params['prev_class'], $params['types'], $params['featured'], $params['post_navigation'] );
 502          $next = $this->next_item_link( $params['next_start'], $params['next_end'], $params[ 'next_text' ], $params[ 'next_no_item' ], false, $params[ 'target_blog'], $params['next_class'], $params['types'], $params['featured'], $params['post_navigation'] );
 503  
 504          if( empty( $prev ) || empty( $next ) )
 505          {    // Use separator text only when prev & next are not empty
 506              $params['separator'] = '';
 507          }
 508  
 509          $output = str_replace( '$prev$', $prev, $params['template'] );
 510          $output = str_replace( '$next$', $next, $output );
 511          $output = str_replace( '$separator$', $params['separator'], $output );
 512  
 513          if( !empty( $output ) )
 514          {    // we have some output, lets wrap it
 515              echo( $params['block_start'] );
 516              echo $output;
 517              echo( $params['block_end'] );
 518          }
 519      }
 520  
 521  
 522      /**
 523       * Skip to previous
 524       */
 525  	function prev_item_link( $before = '', $after = '', $text = '&laquo; $title$', $no_item = '', $display = true, $target_blog = '', $class = '', $types = '', $featured = NULL, $post_navigation = NULL )
 526      {
 527          /**
 528           * @var Item
 529           */
 530          $prev_Item = & $this->get_prevnext_Item( 'prev', $types, $featured, $post_navigation );
 531  
 532          if( !is_null($prev_Item) )
 533          {
 534              $output = $before;
 535              $output .= $prev_Item->get_permanent_link( $text, '#', $class, $target_blog, $post_navigation, $this->nav_target );
 536              $output .= $after;
 537          }
 538          else
 539          {
 540              $output = $no_item;
 541          }
 542          if( $display ) echo $output;
 543          return $output;
 544      }
 545  
 546  
 547      /**
 548       * Skip to next
 549       */
 550  	function next_item_link( $before = '', $after = '', $text = '$title$ &raquo;', $no_item = '', $display = true, $target_blog = '', $class = '', $types = '', $featured = true, $post_navigation = NULL )
 551      {
 552          /**
 553           * @var Item
 554           */
 555          $next_Item = & $this->get_prevnext_Item( 'next', $types, $featured, $post_navigation );
 556  
 557          if( !is_null($next_Item) )
 558          {
 559              $output = $before;
 560              $output .= $next_Item->get_permanent_link( $text, '#', $class, $target_blog, $post_navigation, $this->nav_target );
 561              $output .= $after;
 562          }
 563          else
 564          {
 565              $output = $no_item;
 566          }
 567          if( $display ) echo $output;
 568          return $output;
 569      }
 570  
 571  
 572      /**
 573       * Generate the permalink for the previous item in collection.
 574       *
 575       * Note: Each item has an unique permalink at any given time.
 576       * Some admin settings may however change the permalinks for previous items.
 577       * Note: This actually only returns the URL, to get a real link, use {@link ItemList::prev_item_link()}
 578       *
 579       * @param string single, archive, subchap
 580       * @param string base url to use
 581       * @param string glue between url params
 582       */
 583  	function get_prev_item_url( $permalink_type = '', $blogurl = '', $glue = '&amp;' )
 584      {
 585          /**
 586           * @var Item
 587           */
 588          $prev_Item = & $this->get_prevnext_Item( 'prev' );
 589  
 590          if( !is_null($prev_Item) )
 591          {
 592              return $prev_Item->get_permanent_url( $permalink_type, $blogurl, $glue );
 593          }
 594          return '';
 595      }
 596  
 597  
 598      /**
 599       * Generate the permalink for the next item in collection.
 600       *
 601       * Note: Each item has an unique permalink at any given time.
 602       * Some admin settings may however change the permalinks for previous items.
 603       * Note: This actually only returns the URL, to get a real link, use {@link ItemList::next_item_link()}
 604       *
 605       * @param string single, archive, subchap
 606       * @param string base url to use
 607       * @param string glue between url params
 608       */
 609  	function get_next_item_url( $permalink_type = '', $blogurl = '', $glue = '&amp;' )
 610      {
 611          /**
 612           * @var Item
 613           */
 614          $next_Item = & $this->get_prevnext_Item( 'next' );
 615  
 616          if( !is_null($next_Item) )
 617          {
 618              return $next_Item->get_permanent_url( $permalink_type, $blogurl, $glue );
 619          }
 620          return '';
 621      }
 622  
 623  
 624      /**
 625       * Skip to previous/next Item
 626       *
 627       * If several items share the same spot (like same issue datetime) then they'll get all skipped at once.
 628       *
 629       * @param string prev | next  (relative to the current sort order)
 630       */
 631      function & get_prevnext_Item( $direction = 'next', $types = '', $featured = NULL, $post_navigation = 'same_blog' )
 632      {
 633          global $DB, $ItemCache, $posttypes_specialtypes;
 634  
 635          if( ! $this->single_post )
 636          {    // We are not on a single post:
 637              $r = NULL;
 638              return $r;
 639          }
 640  
 641          /**
 642           * @var Item
 643           */
 644          $current_Item = $this->get_by_idx(0);
 645  
 646          if( is_null($current_Item) )
 647          {    // This happens if we are on a single post that we do not actually have permission to view
 648              $r = NULL;
 649              return $r;
 650          }
 651  
 652          if( in_array( $current_Item->ptyp_ID, $posttypes_specialtypes ) ) // page, intros, ads
 653          {    // We are not on a REGULAR post -- we cannot navigate:
 654              $r = NULL;
 655              return $r;
 656          }
 657  
 658          if( !empty( $this->prevnext_Item[$direction][$post_navigation] ) )
 659          {
 660              return $this->prevnext_Item[$direction][$post_navigation];
 661          }
 662  
 663          $next_Query = new ItemQuery( $this->Cache->dbtablename, $this->Cache->dbprefix, $this->Cache->dbIDname );
 664  
 665          // GENERATE THE QUERY:
 666  
 667          /*
 668           * filtering stuff:
 669           */
 670          $next_Query->where_chapter2( $this->Blog, $this->filters['cat_array'], $this->filters['cat_modifier'],
 671                                                                   $this->filters['cat_focus'] );
 672          $next_Query->where_author( $this->filters['authors'] );
 673          $next_Query->where_author_logins( $this->filters['authors_login'] );
 674          $next_Query->where_assignees( $this->filters['assignees'] );
 675          $next_Query->where_assignees_logins( $this->filters['assignees_login'] );
 676          $next_Query->where_author_assignee( $this->filters['author_assignee'] );
 677          $next_Query->where_locale( $this->filters['lc'] );
 678          $next_Query->where_statuses( $this->filters['statuses'] );
 679          // types param is kept only for the case when some custom types should be displayed
 680          $next_Query->where_types( !empty( $types ) ? $types : $this->filters['types'] );
 681          $next_Query->where_keywords( $this->filters['keywords'], $this->filters['phrase'], $this->filters['exact'] );
 682          // $next_Query->where_ID( $this->filters['post_ID'], $this->filters['post_title'] );
 683          $next_Query->where_datestart( $this->filters['ymdhms'], $this->filters['week'],
 684                                             $this->filters['ymdhms_min'], $this->filters['ymdhms_max'],
 685                                             $this->filters['ts_min'], $this->filters['ts_max'] );
 686          $next_Query->where_visibility( $this->filters['visibility_array'] );
 687          $next_Query->where_featured( $featured );
 688  
 689          /*
 690           * ORDER BY stuff:
 691           */
 692          if( ($direction == 'next' && $this->filters['order'] == 'DESC')
 693              || ($direction == 'prev' && $this->filters['order'] == 'ASC') )
 694          {
 695              $order = 'DESC';
 696              $operator = ' < ';
 697          }
 698          else
 699          {
 700              $order = 'ASC';
 701              $operator = ' > ';
 702          }
 703  
 704          $orderby = str_replace( ' ', ',', $this->filters['orderby'] );
 705          $orderby_array = explode( ',', $orderby );
 706  
 707          // Format each order param with default column names:
 708          $orderbyorder_array = preg_replace( '#^(.+)$#', $this->Cache->dbprefix.'$1 '.$order, $orderby_array );
 709  
 710          // Add an ID parameter to make sure there is no ambiguity in ordering on similar items:
 711          $orderbyorder_array[] = $this->Cache->dbIDname.' '.$order;
 712  
 713          $order_by = implode( ', ', $orderbyorder_array );
 714  
 715          // Special case for RAND:
 716          $order_by = str_replace( $this->Cache->dbprefix.'RAND ', 'RAND() ', $order_by );
 717  
 718          $next_Query->order_by( $order_by );
 719  
 720  
 721          // LIMIT to 1 single result
 722          $next_Query->LIMIT( '1' );
 723  
 724          // fp> TODO: I think some additional limits need to come back here (for timespans)
 725  
 726  
 727          /*
 728           * Position right after the current element depending on current sorting params
 729           *
 730           * If there are several items on the same issuedatetime for example, we'll then differentiate on post ID
 731           * WARNING: you cannot combine criterias with AND here; you need stuf like a>a0 OR (a=a0 AND b>b0)
 732           */
 733          switch( $orderby_array[0] )
 734          {
 735              case 'datestart':
 736                  // special var name:
 737                  $next_Query->WHERE_and( $this->Cache->dbprefix.$orderby_array[0]
 738                                                                  .$operator
 739                                                                  .$DB->quote($current_Item->issue_date)
 740                                                                  .' OR ( '
 741                                    .$this->Cache->dbprefix.$orderby_array[0]
 742                                                                      .' = '
 743                                                                      .$DB->quote($current_Item->issue_date)
 744                                                                      .' AND '
 745                                                                      .$this->Cache->dbIDname
 746                                                                      .$operator
 747                                                                      .$current_Item->ID
 748                                                                  .')'
 749                                                           );
 750                  break;
 751  
 752              case 'title':
 753              case 'ptyp_ID':
 754              case 'datecreated':
 755              case 'datemodified':
 756              case 'last_touched_ts':
 757              case 'urltitle':
 758              case 'priority':
 759                  $next_Query->WHERE_and( $this->Cache->dbprefix.$orderby_array[0]
 760                                                                  .$operator
 761                                                                  .$DB->quote($current_Item->{$orderby_array[0]})
 762                                                                  .' OR ( '
 763                                    .$this->Cache->dbprefix.$orderby_array[0]
 764                                                                      .' = '
 765                                                                      .$DB->quote($current_Item->{$orderby_array[0]})
 766                                                                      .' AND '
 767                                                                      .$this->Cache->dbIDname
 768                                                                      .$operator
 769                                                                      .$current_Item->ID
 770                                                                  .')'
 771                                                              );
 772                  break;
 773  
 774              case 'order':
 775                  // We have to integrate a rounding error margin
 776                  $comp_order_value = $current_Item->order;
 777                  $and_clause = '';
 778  
 779                  if( is_null($comp_order_value) )
 780                  {    // current Item has NULL order
 781                      if( $operator == ' < ' )
 782                      {    // This is needed when browsing through a descending ordered list and we reach the limit where orders are not set/NULL (ex: b2evo screenshots)
 783                          $and_clause .= $this->Cache->dbprefix.$orderby_array[0].' IS NULL AND ';
 784                      }
 785                      else
 786                      { // This is needed when browsing through a descending ordered list and we want to browse back into the posts that have numbers (pb appears if first NULL posts is the highest ID)
 787                          $and_clause .= $this->Cache->dbprefix.$orderby_array[0].' IS NOT NULL OR ';
 788                      }
 789                      $and_clause .= $this->Cache->dbIDname
 790                          .$operator
 791                          .$current_Item->ID;
 792                  }
 793                  else
 794                  {
 795                      if( $operator == ' < ' )
 796                      {    // This is needed when browsing through a descending ordered list and we reach the limit where orders are not set/NULL (ex: b2evo screenshots)
 797                          $and_clause .= $this->Cache->dbprefix.$orderby_array[0].' IS NULL OR ';
 798                      }
 799                      $and_clause .= $this->Cache->dbprefix.$orderby_array[0]
 800                                                      .$operator
 801                                                      .( $operator == ' < ' ? $comp_order_value-0.000000001 : $comp_order_value+0.000000001 )
 802                                                      .' OR ( '
 803                                .$this->Cache->dbprefix.$orderby_array[0]
 804                                                          .( $operator == ' < ' ? ' <= '.($comp_order_value+0.000000001) : ' >= '.($comp_order_value-0.000000001) )
 805                                                          .' AND '
 806                                                          .$this->Cache->dbIDname
 807                                                          .$operator
 808                                                          .$current_Item->ID
 809                                                      .')';
 810                  }
 811  
 812                  $next_Query->WHERE_and( $and_clause );
 813                  break;
 814  
 815              case 'RAND':
 816                  // Random order. Don't show current item again.
 817                  $next_Query->WHERE_and( $this->Cache->dbprefix.'ID <> '.$current_Item->ID );
 818                  break;
 819  
 820              default:
 821                  echo 'WARNING: unhandled sorting: '.htmlspecialchars( $orderby_array[0] );
 822          }
 823  
 824          // GET DATA ROWS:
 825  
 826  
 827          // We are going to proceed in two steps (we simulate a subquery)
 828          // 1) we get the IDs we need
 829          // 2) we get all the other fields matching these IDs
 830          // This is more efficient than manipulating all fields at once.
 831  
 832          // Step 1:
 833          $step1_sql = 'SELECT DISTINCT '.$this->Cache->dbIDname
 834                                      .$next_Query->get_from()
 835                                      .$next_Query->get_where()
 836                                      .$next_Query->get_group_by()
 837                                      .$next_Query->get_order_by()
 838                                      .$next_Query->get_limit();
 839  
 840          //echo $DB->format_query( $step1_sql );
 841  
 842          // Get list of the IDs we need:
 843          $next_ID = $DB->get_var( $step1_sql, 0, 0, 'Get ID of next item' );
 844  
 845          //pre_dump( $next_ID );
 846  
 847          // Step 2: get the item (may be NULL):
 848          $this->prevnext_Item[$direction][$post_navigation] = & $ItemCache->get_by_ID( $next_ID, true, false );
 849  
 850          return $this->prevnext_Item[$direction][$post_navigation];
 851  
 852      }
 853  }
 854  
 855  ?>

title

Description

title

Description

title

Description

title

title

Body