<?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&'.$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&'.$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' => '« $title$',
'prev_end' => '',
'prev_no_item' => '',
'prev_class' => '',
'separator' => '',
'next_start' => '',
'next_text' => '$title$ »',
'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 = '« $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$ »', $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 = '&' )
{
/**
* @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 = '&' )
{
/**
* @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 );
}
}
?>