Seditio Source
Root |
./othercms/b2evolution_7.2.3/inc/items/model/_itemlist.class.php
<?php
/**
 * This file implements the ItemList class 2.
 *
 * This is the object handling item/post/article lists.
 *
 * This file is part of the evoCore framework - {@link http://evocore.net/}
 * See also {@link https://github.com/b2evolution/b2evolution}.
 *
 * @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license}
 *
 * @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}
 *
 * @package evocore
 */
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );

load_class( '/items/model/_itemlistlight.class.php', 'ItemListLight' );


/**
 * Item List Class 2
 *
 * This SECOND implementation will deprecate the first one when finished.
 *
 * @package evocore
 */
class ItemList2 extends ItemListLight
{
   
/**
     * @var array
     */
   
var $prevnext_Item = array();

   
/**
     * Navigate through this target items
     * It can be an Chapter ID, a User ID or Tag name, depends from the post_navigation coll settings
     *
     * @var integer
     */
   
var $nav_target;

   
/**
     * Constructor
     *
     * @todo  add param for saved session filter set
     *
     * @param Blog
     * @param mixed Default filter set: Do not show posts before this timestamp, can be 'now'
     * @param mixed Default filter set: Do not show posts after this timestamp, can be 'now'
     * @param integer|NULL Limit
     * @param string name of cache to be used
     * @param string prefix to differentiate page/order params when multiple Results appear one same page
     * @param string Name to be used when saving the filterset (leave empty to use default for collection)
     * @param array restrictions for itemlist (position, contact, firm, ...) key: restriction name, value: ID of the restriction
     */
   
function __construct(
            &
$Blog,
           
$timestamp_min = NULL,       // Do not show posts before this timestamp
           
$timestamp_max = NULL,            // Do not show posts after this timestamp
           
$limit = 20,
           
$cache_name = 'ItemCache',     // name of cache to be used
           
$param_prefix = '',
           
$filterset_name = ''                // Name to be used when saving the filterset (leave empty to use default for collection)
       
)
    {
        global
$Settings;

       
// Call parent constructor:
       
parent::__construct( $Blog, $timestamp_min, $timestamp_max, $limit, $cache_name, $param_prefix, $filterset_name );
    }


   
/**
     * We want to preview a single post, we are going to fake a lot of things...
     */
   
function preview_from_request()
    {
        if( !
is_logged_in() )
        {
// dh> only logged in user's can preview. Alternatively we need those checks where $current_User gets used below.
           
return;
        }

        global
$current_User, $DB, $localtimenow, $Blog, $Plugins;

       
$post_ID = param( 'post_ID', 'integer', 0 );

       
// Get Item by ID or create new Item object:
       
$ItemCache = & get_ItemCache();
        if( ! (
$Item = & $ItemCache->get_by_ID( $post_ID, false, false ) ) )
        {    
// Initialize new creating Item:
           
$Item = new Item();
        }

       
param( 'item_typ_ID', 'integer', true );

       
$Item->status = param( 'post_status', 'string', NULL ); // 'published' or 'draft' or ...
        // We know we can use at least one status,
        // but we need to make sure the requested/default one is ok:
       
$Item->status = $Blog->get_allowed_item_status( $Item->status, $Item );

       
// Check if new category was started to create. If yes then set up parameters for next page:
       
check_categories_nosave( $post_category, $post_extracats, $Item );

       
// Set main category to avoid error with new creating Item:
       
$Item->set( 'main_cat_ID', $post_category );

       
// Switch to current user's locale to display errors:
       
locale_temp_switch( $current_User->locale );

       
// Set Item params from request:
       
$Item->load_from_Request();

       
// Use only first slug on PREVIEW mode in order to initialize correct permanent URL:
       
$urltitles = $Item->get( 'urltitle' );
        if(
$urltitles === '' )
        {    
// If slugs are empty on preview form, try to get previous of from title:
           
$urltitles = ( empty( $this->previous_urltitle ) ? $Item->get( 'title' ) : $this->previous_urltitle );
        }
       
$urltitles = explode( ',', $urltitles );
       
$Item->set( 'urltitle', get_urltitle( $urltitles[0] ) );

        if( isset(
$Item->previous_status ) )
        {    
// Restrict Item status by Collection access restriction AND by CURRENT USER write perm:
            // (ONLY if current request is updating item status)
           
$Item->restrict_status( true );
        }

       
locale_restore_previous();

       
// Initialize SQL query to preview Item:
       
$post_fields = $DB->get_col( 'SHOW COLUMNS FROM T_items__item', 0, 'Get all item columns to init SQL query for preview the creating/editing Item' );
       
$sql_post_fields = array();
       
$post_dbprefix_length = strlen( $Item->dbprefix );
        foreach(
$post_fields as $post_field )
        {
            switch(
$post_field )
            {
                case
'post_lastedit_user_ID':
                   
$post_field_value = $current_User->ID;
                    break;

                case
'post_datemodified':
                case
'post_last_touched_ts':
                case
'post_contents_last_updated_ts':
                   
$post_field_value = date2mysql( $localtimenow );
                    break;

                default:
                   
$post_field_name = substr( $post_field, $post_dbprefix_length );
                   
$post_field_value = isset( $Item->$post_field_name ) ? $Item->$post_field_name : NULL;
                    break;
            }
           
$sql_post_fields[] = "\n".$DB->quote( $post_field_value ).' AS '.$post_field;
        }
       
// Create a fake SQL query(to initialize the previewing Item) like "SELECT 123 AS post_ID, 'Post Title text' AS post_title, ...":
       
$this->sql = 'SELECT '.implode( ', ', $sql_post_fields );

       
$this->total_rows = 1;
       
$this->total_pages = 1;
       
$this->page = 1;

       
// Skip the function of this class and call it of the parent because we have already initialized SQL query above in this function:
       
DataObjectList2::run_query( false, false, false, 'ItemList2::preview_from_request() PREVIEW QUERY' );

       
// Trigger plugin event, allowing to manipulate or validate the item before it gets previewed
       
$Plugins->trigger_event( 'AppendItemPreviewTransact', array( 'Item' => & $Item ) );

       
// little funky fix for IEwin, rawk on that code
       
global $Hit;
        if( (
$Hit->is_winIE()) && (!isset($IEWin_bookmarklet_fix)) )
        {
// QUESTION: Is this still needed? What about $IEWin_bookmarklet_fix? (blueyed)
           
$Item->content = preg_replace('/\%u([0-9A-F]{4,4})/e', "'&#'.base_convert('\\1',16,10). ';'", $Item->content);
        }
    }


   
/**
     * Run Query: GET DATA ROWS *** HEAVY ***
     *
     * We need this query() stub in order to call it from restart() and still
     * let derivative classes override it
     *
     * @deprecated Use new function run_query()
     */
   
function query( $create_default_cols_if_needed = true, $append_limit = true, $append_order_by = true )
    {
       
$this->run_query( $create_default_cols_if_needed, $append_limit, $append_order_by );
    }


   
/**
     * Run Query: GET DATA ROWS *** HEAVY ***
     */
   
function run_query( $create_default_cols_if_needed = true, $append_limit = true, $append_order_by = true,
                                           
$query_title = 'Results::run_query()' )
    {
        global
$DB;

        if( !
is_null( $this->rows ) )
        {
// Query has already executed:
           
return;
        }

       
// INIT THE QUERY:
       
$this->query_init();

       
// Check the number of totla rows after it was initialized in the query_init() function
       
if( isset( $this->total_rows ) && ( intval( $this->total_rows ) === 0 ) )
        {
// Count query was already executed and returned 0
           
return;
        }

       
// asimo> This must be removed if the option to 'always move the null values in the end of the result' will be implemented
       
$select_temp_order = '';
        if( !empty(
$this->ItemQuery->order_by ) && preg_match( '/'.preg_quote( 'postcatsorders.postcat_order' ).' (DESC|ASC)/i', $this->ItemQuery->order_by, $order_dir_match ) )
        {    
// Move the items with NULL order to the end of the list
           
$null_orders = ( $order_dir_match[1] == 'DESC' ? '-' : '' ).'999999999'; // Always keep the not ordered posts at the end
           
$select_temp_order = ', IF( postmaincat.cat_blog_ID != T_categories.cat_blog_ID, '
               
.'( SELECT SUM( subpostcatorder.postcat_order ) FROM T_postcats AS subpostcatorder INNER JOIN T_categories AS subpostmaincat ON subpostcatorder.postcat_cat_ID = subpostmaincat.cat_ID WHERE subpostmaincat.cat_blog_ID = T_categories.cat_blog_ID AND subpostcatorder.postcat_post_ID = post_ID ), '
               
.'IF( postcatsorders.postcat_order IS NULL, '.$null_orders.', postcatsorders.postcat_order ) ) AS temp_order';
           
$this->ItemQuery->ORDER_BY( str_replace( 'postcatsorders.postcat_order', 'temp_order', $this->ItemQuery->get_order_by( '' ) ) );
        }

       
// Results style orders:
        // $this->ItemQuery->ORDER_BY_prepend( $this->get_order_field_list() );


        // We are going to proceed in two steps (we simulate a subquery)
        // 1) we get the IDs we need
        // 2) we get all the other fields matching these IDs
        // This is more efficient than manipulating all fields at once.

        // *** STEP 1 ***
        // walter> Accordding to the standart, to DISTINCT queries, all columns used
        // in ORDER BY must appear in the query. This make que query work with PostgreSQL and
        // other databases.
        // fp> That can dramatically fatten the returned data. You must handle this in the postgres class (check that order fields are in select)
       
$step1_sql = 'SELECT DISTINCT '.$this->Cache->dbIDname // .', '.implode( ', ', $order_cols_to_select )
                                   
.$select_temp_order
                                   
.$this->ItemQuery->get_orderby_select()
                                    .
$this->ItemQuery->get_from()
                                    .
$this->ItemQuery->get_orderby_from( ' ' )
                                    .
$this->ItemQuery->get_where()
                                    .
$this->ItemQuery->get_group_by()
                                    .
$this->ItemQuery->get_order_by()
                                    .
$this->ItemQuery->get_limit();

       
// echo DB::format_query( $step1_sql );

        // Get list of the IDs we need:
       
$ID_list = implode( ',', $DB->get_col( $step1_sql, 0, ( empty( $this->query_title_prefix ) ? '' : $this->query_title_prefix.' - ' ).'ItemList2::Query() Step 1: Get ID list' ) );

       
// *** STEP 2 ***
       
$this->sql = 'SELECT * FROM '.$this->Cache->dbtablename.$this->ItemQuery->get_orderby_from( ' ' );

        if( isset(
$this->filters['orderby'] ) && $this->filters['orderby'] == 'numviews' )
        {
// special case for order by number of views
            //$this->sql .= ' LEFT JOIN ( SELECT itud_item_ID, COUNT(*) AS '.$this->Cache->dbprefix.'numviews FROM T_items__user_data GROUP BY itud_item_ID ) AS numviews
            //        ON '.$this->Cache->dbIDname.' = numviews.itud_item_ID';
       
}

        if( !empty(
$ID_list) )
        {
           
$this->sql .= ' WHERE '.$this->Cache->dbIDname.' IN ('.$ID_list.')
                ORDER BY FIND_IN_SET( post_ID, "'
.$ID_list.'" )';
        }
        else
        {
           
$this->sql .= ' WHERE 0';
        }

       
//echo DB::format_query( $this->sql );

        // Skip the function of first parent and call it of main parent because we have already initialized SQL query above in this function:
       
DataObjectList2::run_query( false, false, false, 'ItemList2::Query() Step 2' );
    }


   
/**
     * If the list is sorted by category...
      *
      * This is basically just a stub for backward compatibility
     */
   
function & get_item()
    {
        if(
$this->group_by_cat == 1 )
        {    
// This is the first call to get_item() after get_category_group()
           
$this->group_by_cat = 2;
           
// Return the object we already got in get_category_group():
           
return $this->current_Obj;
        }

       
$Item = & parent::get_next();

        if( !empty(
$Item) && $this->group_by_cat == 2 && $Item->main_cat_ID != $this->main_cat_ID )
        {    
// We have just hit a new category!
           
$this->group_by_cat == 0; // For info only.
           
$r = false;
            return
$r;
        }

        return
$Item;
    }


   
/**
     * Get all tags used in current ItemList
     *
     * @todo caching in case of multiple calls
     *
     * @return array
     */
   
function get_all_tags()
    {
       
$all_tags = array();

        for(
$i=0; $i<$this->result_num_rows; $i++ )
        {
           
/**
             * @var Item
             */
           
$l_Item = & $this->get_by_idx( $i );
           
$l_tags = $l_Item->get_tags();
           
$all_tags = array_merge( $all_tags, $l_tags );
        }

       
// Keep each tag only once:
       
$all_tags = array_unique( $all_tags );

        return
$all_tags;
    }



   
/**
     * Returns values needed to make sort links for a given column
     * Needed because the order is not handled by the result class.
     * Reason: Sometimes the item list needs to be ordered without having a display table, and columns. The result class order is based on columns.
     *
     * Returns an array containing the following values:
     *  - current_order : 'ASC', 'DESC' or ''
     *  - order_asc : url needed to order in ascending order
     *  - order_desc
     *  - order_toggle : url needed to toggle sort order
     *
     * @param integer column to sort
     * @return array
     */
   
function get_col_sort_values( $col_idx )
    {
       
$col_order_fields = $this->cols[$col_idx]['order'];

       
// Current order:
       
if( $this->filters['orderby'] == $col_order_fields || $this->param_prefix.$this->filters['orderby'] == $col_order_fields )
        {
           
$col_sort_values['current_order'] = $this->filters['order'];
        }
        else
        {
           
$col_sort_values['current_order'] = '';
        }


       
// Generate sort values to use for sorting on the current column:
       
$col_sort_values['order_asc'] = regenerate_url( array($this->param_prefix.'order',$this->param_prefix.'orderby'),
                                                                           
$this->param_prefix.'order=ASC&amp;'.$this->param_prefix.'orderby='.$col_order_fields );
       
$col_sort_values['order_desc'] = regenerate_url(  array($this->param_prefix.'order',$this->param_prefix.'orderby'),
                                                                           
$this->param_prefix.'order=DESC&amp;'.$this->param_prefix.'orderby='.$col_order_fields );

        if( !
$col_sort_values['current_order'] && isset( $this->cols[$col_idx]['default_dir'] ) )
        {    
// There is no current order on this column and a default order direction is set for it
            // So set a default order direction for it

           
if( $this->cols[$col_idx]['default_dir'] == 'A' )
            {    
// The default order direction is A, so set its toogle  order to the order_asc
               
$col_sort_values['order_toggle'] = $col_sort_values['order_asc'];
            }
            else
            {
// The default order direction is A, so set its toogle order to the order_desc
               
$col_sort_values['order_toggle'] = $col_sort_values['order_desc'];
            }
        }
        elseif(
$col_sort_values['current_order'] == 'ASC' )
        {    
// There is an ASC current order on this column, so set its toogle order to the order_desc
           
$col_sort_values['order_toggle'] = $col_sort_values['order_desc'];
        }
        else
        {
// There is a DESC or NO current order on this column,  so set its toogle order to the order_asc
           
$col_sort_values['order_toggle'] = $col_sort_values['order_asc'];
        }

       
// pre_dump( $col_sort_values );

       
return $col_sort_values;
    }



   
/**
     * Link to previous and next link in collection
     */
   
function prevnext_item_links( $params )
    {
       
$params = array_merge( array(
                                   
'template' => '$prev$$separator$$next$',
                                   
'prev_start' => '',
                                   
'prev_text' => '&laquo; $title$',
                                   
'prev_end' => '',
                                   
'prev_no_item' => '',
                                   
'prev_class' => '',
                                   
'separator' => '',
                                   
'next_start' => '',
                                   
'next_text' => '$title$ &raquo;',
                                   
'next_end' => '',
                                   
'next_no_item' => '',
                                   
'next_class' => '',
                                   
'target_blog' => '',
                                   
'post_navigation' => $this->Blog->get_setting( 'post_navigation' ),
                                   
'itemtype_usage' => 'post', // Include only post with type usage "post"
                                   
'featured' => NULL,
                                ),
$params );

       
$current_Item = & $this->get_by_idx(0);
       
// Note: current_Item may be null when User doesn't have permission
       
if( $current_Item )
        {
// current Item is available, init navigation target
           
switch( $params['post_navigation'] )
            {
                case
'same_category': // sometimes requires the 'cat' param because a post may belong to multiple categories
                   
if( empty( $this->nav_target ) )
                    {
                       
$this->nav_target = $current_Item->main_cat_ID;
                    }
                   
$this->filters['cat_array'][] = $this->nav_target;
                   
// Note: If there will be other navigation type ( like tag ) with params, those filters must be removed.
                   
break;

                case
'same_author': // This doesn't require extra param because a post always has only one author
                   
$this->filters['authors'] = $current_Item->creator_user_ID;
                   
// reset cat filters because only the authors are important
                   
$this->filters['cat_array'] = array();
                    break;

                case
'same_tag': // sometimes requires the 'tag' param because a post may belong to multiple tags
                   
if( empty( $this->nav_target ) )
                    {
                       
$tags = $current_Item->get_tags();
                        if(
count( $tags ) > 0 )
                        {
                           
$this->nav_target = $tags[0];
                        }
                    }
                    if( !empty(
$this->nav_target ) )
                    {
                       
$this->filters['tags'] = $this->nav_target;
                    }
                   
// reset cat filters because only the tags are important
                   
$this->filters['cat_array'] = array();
                    break;

                default:
                    break;
            }
        }

       
$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['itemtype_usage'], $params['featured'], $params['post_navigation'] );
       
$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['itemtype_usage'], $params['featured'], $params['post_navigation'] );

        if( empty(
$prev ) || empty( $next ) )
        {    
// Use separator text only when prev & next are not empty
           
$params['separator'] = '';
        }

       
$output = str_replace( '$prev$', $prev, $params['template'] );
       
$output = str_replace( '$next$', $next, $output );
       
$output = str_replace( '$separator$', $params['separator'], $output );

        if( !empty(
$output ) )
        {    
// we have some output, lets wrap it
           
echo( $params['block_start'] );
            echo
$output;
            echo(
$params['block_end'] );
        }
    }


   
/**
     * Skip to previous
     */
   
function prev_item_link( $before = '', $after = '', $text = '&laquo; $title$', $no_item = '', $display = true, $target_blog = '', $class = '', $itemtype_usage = '', $featured = NULL, $post_navigation = NULL )
    {
       
/**
         * @var Item
         */
       
$prev_Item = & $this->get_prevnext_Item( 'prev', $itemtype_usage, $featured, $post_navigation );

        if( !
is_null($prev_Item) )
        {
           
$output = $before;
           
$output .= $prev_Item->get_permanent_link( $text, '', $class, $target_blog, $post_navigation, $this->nav_target );
           
$output .= $after;
        }
        else
        {
           
$output = $no_item;
        }
        if(
$display ) echo $output;
        return
$output;
    }


   
/**
     * Skip to next
     */
   
function next_item_link( $before = '', $after = '', $text = '$title$ &raquo;', $no_item = '', $display = true, $target_blog = '', $class = '', $itemtype_usage = '', $featured = true, $post_navigation = NULL )
    {
       
/**
         * @var Item
         */
       
$next_Item = & $this->get_prevnext_Item( 'next', $itemtype_usage, $featured, $post_navigation );

        if( !
is_null($next_Item) )
        {
           
$output = $before;
           
$output .= $next_Item->get_permanent_link( $text, '', $class, $target_blog, $post_navigation, $this->nav_target );
           
$output .= $after;
        }
        else
        {
           
$output = $no_item;
        }
        if(
$display ) echo $output;
        return
$output;
    }


   
/**
     * Generate the permalink for the previous item in collection.
     *
     * Note: Each item has an unique permalink at any given time.
     * Some admin settings may however change the permalinks for previous items.
     * Note: This actually only returns the URL, to get a real link, use {@link ItemList::prev_item_link()}
     *
     * @param string single, archive, subchap
     * @param string base url to use
     * @param string glue between url params
     */
   
function get_prev_item_url( $permalink_type = '', $blogurl = '', $glue = '&amp;' )
    {
       
/**
         * @var Item
         */
       
$prev_Item = & $this->get_prevnext_Item( 'prev' );

        if( !
is_null($prev_Item) )
        {
            return
$prev_Item->get_permanent_url( $permalink_type, $blogurl, $glue );
        }
        return
'';
    }


   
/**
     * Generate the permalink for the next item in collection.
     *
     * Note: Each item has an unique permalink at any given time.
     * Some admin settings may however change the permalinks for previous items.
     * Note: This actually only returns the URL, to get a real link, use {@link ItemList::next_item_link()}
     *
     * @param string single, archive, subchap
     * @param string base url to use
     * @param string glue between url params
     */
   
function get_next_item_url( $permalink_type = '', $blogurl = '', $glue = '&amp;' )
    {
       
/**
         * @var Item
         */
       
$next_Item = & $this->get_prevnext_Item( 'next' );

        if( !
is_null($next_Item) )
        {
            return
$next_Item->get_permanent_url( $permalink_type, $blogurl, $glue );
        }
        return
'';
    }


   
/**
     * Skip to previous/next Item
     *
     * If several items share the same spot (like same issue datetime) then they'll get all skipped at once.
     *
     * @param string prev | next  (relative to the current sort order)
     */
   
function & get_prevnext_Item( $direction = 'next', $itemtype_usage = '', $featured = NULL, $post_navigation = 'same_blog' )
    {
        global
$DB, $ItemCache;

        if( !
$this->single_post )
        {    
// We are not on a single post:
           
$r = NULL;
            return
$r;
        }

       
/**
         * @var Item
         */
       
$current_Item = $this->get_by_idx(0);

        if(
is_null($current_Item) )
        {    
// This happens if we are on a single post that we do not actually have permission to view
           
$r = NULL;
            return
$r;
        }

        if(
$current_Item->get_type_setting( 'usage' ) != 'post' )
        {    
// We are not on a REGULAR post -- we cannot navigate:
           
$r = NULL;
            return
$r;
        }

        if( !empty(
$this->prevnext_Item[$direction][$post_navigation] ) )
        {
            return
$this->prevnext_Item[$direction][$post_navigation];
        }

       
$next_Query = new ItemQuery( $this->Cache->dbtablename, $this->Cache->dbprefix, $this->Cache->dbIDname );

       
// GENERATE THE QUERY:

        /*
         * filtering stuff:
         */
       
$next_Query->where_chapter2( $this->Blog, $this->filters['cat_array'], $this->filters['cat_modifier'],
                                                                 
$this->filters['cat_focus'], $this->filters['coll_IDs'] );
       
$next_Query->where_author( $this->filters['authors'] );
       
$next_Query->where_author_logins( $this->filters['authors_login'] );
       
$next_Query->where_assignees( $this->filters['assignees'] );
       
$next_Query->where_assignees_logins( $this->filters['assignees_login'] );
       
$next_Query->where_author_assignee( $this->filters['author_assignee'] );
       
$next_Query->where_involves( $this->filters['involves'] );
       
$next_Query->where_involves_logins( $this->filters['involves_login'] );
       
$next_Query->where_locale( $this->filters['lc'] );
       
$next_Query->where_statuses( $this->filters['statuses'] );
       
$next_Query->where_statuses_array( $this->filters['statuses_array'] );
       
// itemtype_usage param is kept only for the case when some custom types should be displayed
       
$next_Query->where_itemtype_usage( ! empty( $itemtype_usage ) ? $itemtype_usage : $this->filters['itemtype_usage'] );
       
$next_Query->where_keywords( $this->filters['keywords'], $this->filters['phrase'], $this->filters['exact'] );
       
// $next_Query->where_ID( $this->filters['post_ID'], $this->filters['post_title'] );
       
$next_Query->where_datestart( $this->filters['ymdhms'], $this->filters['week'],
                                           
$this->filters['ymdhms_min'], $this->filters['ymdhms_max'],
                                           
$this->filters['ts_min'], $this->filters['ts_max'] );
       
$next_Query->where_visibility( $this->filters['visibility_array'] );
       
$next_Query->where_featured( $featured );
       
$next_Query->where_tags( $this->filters['tags'] );
       
$next_Query->where_flagged( $this->filters['flagged'] );
       
$next_Query->where_locale_visibility();
       
$next_Query->where_mustread( $this->filters['mustread'] );
       
$next_Query->where_renderers( $this->filters['renderers'] );

       
/*
         * ORDER BY stuff:
         */
       
$orderdir = str_replace( ' ', ',',  $this->filters['order'] );
       
$orderdir = explode( ',', $orderdir );
        foreach(
$orderdir as $index => $order )
        {
// Set the corresponding order by direction depending the original value and the next/prev request
           
$orderdir[$index] = ( ( $direction == 'next' && $orderdir[$index] == 'DESC' )
                || (
$direction == 'prev' && $orderdir[$index] == 'ASC' ) ) ? 'DESC' : 'ASC';
        }
       
$orderdir = implode( ',', $orderdir );

       
// Init and set the order by condition and the addition required tables ( in case of custom fields )
       
$order_clause = $next_Query->gen_order_clause( $this->filters['orderby'], $orderdir, $this->Cache->dbprefix, $this->Cache->dbIDname );
       
$next_Query->order_by( $order_clause );
       
// Add additionally required tables because of the ordering ( ordering by cutsom fields )
       
$next_Query->FROM_add( $next_Query->get_orderby_from() );

       
// LIMIT to 1 single result
       
$next_Query->LIMIT( '1' );

       
// fp> TODO: I think some additional limits need to come back here (for timespans)


        /*
         * Position right after the current element depending on current sorting params
         *
         * If there are several items on the same issuedatetime for example, we'll then differentiate on post ID
         * WARNING: you cannot combine criterias with AND here; you need stuf like a>a0 OR (a=a0 AND b>b0)
         */

       
$select_temp_order = '';

       
$orderby_fields = explode( ',', $order_clause );
       
$where_condition = '';
       
$condition_separator = '';
       
$equal_condition = '';
       
// Loop through each order by field and set a where condition corresponding to the next/prev and order directions
       
foreach( $orderby_fields as $orderby )
        {
            if(
strpos( $orderby, 'CASE WHEN' ) !== false )
            {
// This handles those cases when the order fields may be null values and we want all null values at the end of the list
               
continue;
            }
            if(
strpos( $orderby, 'DESC' ) )
            {
// Descending order
               
$operator = ' < ';
               
// It is the field name in a format how it must be compared in the query
               
$compare_field = substr( $orderby, 0, -5 );
            }
            else
            {
// Ascending order
               
$operator = ' > ';
               
// It is the field name in a format how it must be compared in the query
               
$compare_field = substr( $orderby, 0, -4 );
            }
           
$compare_field = trim( $compare_field );
            if(
strpos( $compare_field, 'postcatsorders.postcat_order' ) !== false )
            {    
// This is an order field per category:
               
$post_field_name = 'postcat_order';
            }
            elseif( (
$table_separator = strpos( $compare_field, '.' ) ) )
            {
// This is a custom field from the item settings table
                // The field name must be get from a string like 'custom_[varchar | double]_fieldname_table.iset_value'
               
$field_name_position = $table_separator - 6 /* the length of '_table' */ - strlen( $compare_field );
               
$compare_field_name = substr( $compare_field, 0, $field_name_position );
               
$compare_field_name = substr( $compare_field_name, strrpos( $compare_field_name, '_' ) + 1 );
               
$post_field_name = 'custom_'.$compare_field_name;
            }
            elseif(
$compare_field == 'RAND()' )
            {
// Random order
               
$post_field_name = 'RAND';
            }
            else
            {
// Normal post field ( not custom and not special field RAND )
                // It is the field name in a format how should be requestd from the item
               
$post_field_name = substr( $compare_field, strlen( $this->Cache->dbprefix ) );
            }

            if(
$condition_separator !== '' )
            {
// Concatenate the conditions
               
$equal_condition .= ' AND ';
               
$where_condition .= $condition_separator.'( '.$equal_condition;
            }

           
// Set condition corresponding to the order by field name
           
switch( $post_field_name )
            {
                case
'numviews':
                   
// we need to get the number of members who has viewed the post
                   
$numviews = get_item_numviews( $current_Item );
                   
$where_condition .= $this->Cache->dbprefix.$post_field_name
                                                                   
.$operator
                                                                   
.$DB->quote($numviews)
                                                                    .
' OR ( '
                                                                       
.$this->Cache->dbprefix.$post_field_name
                                                                       
.' = '
                                                                       
.$DB->quote($numviews)
                                                                        .
' AND '
                                                                       
.$this->Cache->dbIDname
                                                                       
.$operator
                                                                       
.$current_Item->ID
                                                                   
.')';
                    break;

                case
'datestart':
                   
// special var name:
                   
$post_field_name = 'issue_date';
                case
'ID':
                case
'title':
                case
'ityp_ID':
                case
'datecreated':
                case
'datemodified':
                case
'last_touched_ts':
                case
'contents_last_updated_ts':
                case
'urltitle':
                case
'priority':
                   
$where_condition .= $compare_field.$operator.$DB->quote( $current_Item->{$post_field_name} );
                   
$equal_condition .= $compare_field.' = '.$DB->quote( $current_Item->{$post_field_name} );
                    break;

                case
'postcat_order3':
                   
$order_cat_ID = ( isset( $this->filters['cat_array'] ) && count ( $this->filters['cat_array'] ) == 1 ) ? $this->filters['cat_array'][0] : NULL;
                   
$order_field_value = $current_Item->get_order( $order_cat_ID );

                    if(
$operator == ' > ' )
                    {
                       
$where_condition .= '( '.$compare_field.$operator.$DB->quote( $order_field_value ).' OR '.$compare_field.' IS NULL )';
                    }
                    else
                    {
                       
$where_condition .= $compare_field.$operator.$DB->quote( $order_field_value );
                    }
                   
$equal_condition .= $compare_field.' = '.$DB->quote( $order_field_value );
                    break;

                case
'postcat_order':
                   
// We have to integrate a rounding error margin
                   
$order_cat_ID = ( isset( $this->filters['cat_array'] ) && count ( $this->filters['cat_array'] ) == 1 ) ? $this->filters['cat_array'][0] : NULL;
                   
$order_field_value = $current_Item->get_order( $order_cat_ID );
                    if(
$order_field_value === NULL )
                    {    
// NULL is ordered in the end:
                       
$order_field_value = '999999999';
                    }

                   
// Decide the items with NULL order in the end of the list:
                   
preg_match( '/'.preg_quote( 'postcatsorders.postcat_order' ).' (DESC|ASC)/i', $next_Query->order_by, $order_dir_match );
                   
$null_orders = ( isset( $order_dir_match[1] ) && $order_dir_match[1] == 'DESC' ? '-' : '' ).'999999999'; // Always keep the not ordered posts at the end
                   
$select_temp_order = ', IF( postcatsorders.postcat_order IS NULL, '.$null_orders.', postcatsorders.postcat_order ) AS temp_order';
                   
$next_Query->order_by = str_replace( 'postcatsorders.postcat_order', 'temp_order', $next_Query->order_by );

                   
// asimo> If we would like to order the null values into the end of the result, then we must check the current direction
                    // asimo> In that case NULL values should be allowed only if the direction is 'NEXT' no matter what is the current $operator value

                       
if( $operator == ' > ' )
                        {
                           
$where_condition .= '( ( '.$compare_field.' IS NULL AND post_ID != '.$current_Item->ID.' ) OR '
                                               
.$compare_field.$operator.( $order_field_value + 0.000000001 ).' )';
                        }
                        else
                        {
                           
$where_condition .= '('
                                               
.$compare_field.$operator.( $order_field_value - 0.000000001 ).' )';
                        }
                       
$equal_condition .= '( ( '.$compare_field.' <= '.( $order_field_value + 0.000000001 ).' )
                                             AND ( '
.$compare_field.' >= '.( $order_field_value - 0.000000001 ).' ) )';
                    break;

                case
'RAND':
                   
// Random order. Don't show current item again.
                   
$where_condition .= $this->Cache->dbprefix.'ID <> '.$current_Item->ID;
                   
// There can't be two equal
                   
$equal_condition .= 'FALSE';
                    break;

                default:
                    if(
strpos( $post_field_name, 'custom_' ) === 0 )
                    {
                       
// asimo> If we would like to order the null values into the end of the result, then we must check the current direction
                        // asimo> In that case NULL values should be allowed only if the direction is 'NEXT' no matter what is the current $operator value

                       
$custom_field_value = $current_Item->get_custom_field_value( $compare_field_name );
                        if( (
$custom_field_value === false ) || ( $custom_field_value === NULL ) )
                        {
// This custom field is not set for the current Item
                           
$where_condition .= ( $operator == ' > ' ? $compare_field.' IS NOT NULL' : 'FALSE' );
                           
$equal_condition .= $compare_field.' IS NULL';
                        }
                        else
                        {
                           
$where_condition .= ( $operator == ' < ' ) ? '( ' : '';
                           
$where_condition .= '( '.$compare_field.' IS NOT NULL AND '
                                               
.$compare_field.$operator.$DB->quote( $custom_field_value )
                                                .
' )';
                           
// All null values are < than a not null value
                           
$where_condition .= ( $operator == ' < ' ) ? ' OR ( '.$compare_field.' IS NULL ) )' : '';
                           
$equal_condition .= $compare_field.' = '.$DB->quote( $custom_field_value );
                        }
                        break;
                    }
                    echo
'WARNING: unhandled sorting: '.htmlspecialchars( $post_field_name );
            }

            if(
$condition_separator == '' )
            {
// The first where condition was set, we need to set an 'OR' separator in case if we will have further conditions
               
$condition_separator = ' OR ';
            }
            else
            {
// More than one WHERE condition was added, the last one always needs a ')'
               
$where_condition .= ' )';
            }
        }
       
// Add conditions to get result only from the next/prev items
       
$next_Query->WHERE_and( $where_condition );

       
// GET DATA ROWS:


        // We are going to proceed in two steps (we simulate a subquery)
        // 1) we get the IDs we need
        // 2) we get all the other fields matching these IDs
        // This is more efficient than manipulating all fields at once.

        // Step 1:
       
$step1_sql = 'SELECT DISTINCT '.$this->Cache->dbIDname.$select_temp_order
                                   
.$next_Query->get_from()
                                    .
$next_Query->get_where()
                                    .
$next_Query->get_group_by()
                                    .
$next_Query->get_order_by()
                                    .
$next_Query->get_limit();

       
//echo DB::format_query( $step1_sql );

        // Get list of the IDs we need:
       
$next_ID = $DB->get_var( $step1_sql, 0, 0, ( empty( $this->query_title_prefix ) ? '' : $this->query_title_prefix.' - ' ).'Get ID of next item' );

       
//pre_dump( $next_ID );

        // Step 2: get the item (may be NULL):
       
$this->prevnext_Item[$direction][$post_navigation] = & $ItemCache->get_by_ID( $next_ID, true, false );

        return
$this->prevnext_Item[$direction][$post_navigation];

    }


   
/**
     * Load data of Items from the current page at once to cache variables.
     * For each loading we use only single query to optimize performance.
     * By default it loads all Items of current list page into global $ItemCache,
     * Other data are loaded depending on $params, see below:
     *
     * @param array Params:
     *        - 'load_user_data' - use TRUE to load all data from table T_users__postreadstatus(dates of last read
     *                             post and comments) of the current logged in User for all Items of current list page.
     *                             (ONLY when a tracking unread content is enabled for the collection)
     *        - 'load_postcats'  - use TRUE to load all category associations for all Items of current list page.
     */
   
function load_list_data( $params = array() )
    {
       
$params = array_merge( array(
               
'load_user_data' => true,
               
'load_postcats'  => true,
            ),
$params );

       
$page_post_ids = $this->get_page_ID_array();
        if( empty(
$page_post_ids ) )
        {    
// There are no items on this list:
           
return;
        }

       
// Load all items of the current page in single query:
       
$ItemCache = & get_ItemCache();
       
$ItemCache->load_list( $page_post_ids );

        if(
$params['load_user_data'] )
        {    
// Load the user data for items:
           
$this->load_user_data_for_items();
        }

        if(
$params['load_postcats'] )
        {    
// Load category associations for the items of current page:
           
postcats_get_by_IDs( $page_post_ids );
        }
    }


   
/**
     * Load user data (posts/comments read statuses) for current User for each post of the current ItemList page
     *
     * @deprecated Use new function load_user_data_for_items() instead
     */
   
function load_content_read_statuses()
    {
       
$this->load_user_data_for_items();
    }


   
/**
     * Load user data (posts/comments read statuses) for current User for each post of the current ItemList page
     */
   
function load_user_data_for_items()
    {
        if( !
$this->Blog->get_setting( 'track_unread_content' ) )
        {
// tracking unread content in this blog is turned off
           
return;
        }

       
$page_post_ids = $this->get_page_ID_array();
        if( empty(
$page_post_ids ) )
        {
// There are no items on this list
           
return;
        }

       
// Delegate query:
       
load_user_data_for_items( $page_post_ids );
    }
}

?>