<?php
/**
 * This file implements the Archives plugin.
 *
 * Displays a list of post archives.
 *
 * This file is part of the b2evolution project - {@link http://b2evolution.net/}
 *
 * @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license}
 *
 * @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}
 * Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}.
 *
 * @package plugins
 */
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );


load_class( '_core/ui/results/_results.class.php', 'Results' );
load_class( '/items/model/_itemlistlight.class.php', 'ItemListLight' );


/**
 * Archives Plugin
 *
 * This plugin displays
 */
class archives_plugin extends Plugin
{
	/**
	 * Variables below MUST be overriden by plugin implementations,
	 * either in the subclass declaration or in the subclass constructor.
	 */

	var $name;
	var $code = 'evo_Arch';
	var $priority = 50;
	var $version = '7.2.3';
	var $author = 'The b2evo Group';
	var $group = 'widget';
	var $subgroup = 'navigation';
	var $widget_icon = 'archive';


	/**
	 * Init
	 */
	function PluginInit( & $params )
	{
		$this->name = T_( 'Date Archives' );
		$this->short_desc = T_('This skin tag displays a list of post archives.');
		$this->long_desc = T_('Archives can be grouped monthly, daily, weekly or post by post.');

		$this->dbtable = 'T_items__item';
		$this->dbprefix = 'post_';
		$this->dbIDname = 'post_ID';
	}


	/**
	 * Get keys for block/widget caching
	 *
	 * Maybe be overriden by some widgets, depending on what THEY depend on..
	 *
	 * @param integer Widget ID
	 * @return array of keys this widget depends on
	 */
	function get_widget_cache_keys( $widget_ID = 0 )
	{
		global $Collection, $Blog;

		return array(
				'wi_ID'        => $widget_ID, // Have the widget settings changed ?
				'set_coll_ID'  => $Blog->ID, // Have the settings of the blog changed ? (ex: new skin)
				'cont_coll_ID' => empty( $this->disp_params['blog_ID'] ) ? $Blog->ID : $this->disp_params['blog_ID'], // Has the content of the displayed blog changed ?
			);
	}


	/**
	 * Event handler: SkinTag
	 *
	 * @param array Associative array of parameters. Valid keys are:
	 *                - 'block_start' : (Default: '<div class="bSideItem">')
	 *                - 'block_end' : (Default: '</div>')
	 *                - 'block_body_start' : (Default: '')
	 *                - 'block_body_end' : (Default: '')
	 *                - 'title' : (Default: T_('Archives'))
	 *                - 'mode' : 'monthly'|'daily'|'weekly'|'postbypost' (Default: 'monthly' )
	 *                - 'sort_order' : 'date'|'title' (Default: date - used only if the mode is 'postbypost')
	 *                - 'link_type' : 'canonic'|'context' (default: canonic)
	 *                - 'context_isolation' : what params need override when changing date/range (Default: 'm,w,p,title,unit,dstart' )
	 *                - 'form' : true|false (default: false)
	 *                - 'limit' : # of archive entries to display or '' (Default: 12)
	 *                - 'more_link' : more link text (Default: 'More...')
	 *                - 'list_start' : (Default '<ul>')
	 *                - 'list_end' : (Default '</ul>')
	 *                - 'line_start' : (Default '<li>')
	 *                - 'line_end' : (Default '</li>')
	 *                - 'day_date_format' : (Default: conf.)
	 * @return boolean did we display?
	 */
	function SkinTag( & $params )
	{
		global $month;

		/**
		 * @todo get rid of this global:
		 */
		global $m;

		/**
		 * @var Blog
		 */
		global $Collection, $Blog;

		if( empty($Blog) )
		{
			return false;
		}

		// Prefix of the ItemList object
		$itemlist_prefix = isset( $params['itemlist_prefix'] ) ? $params['itemlist_prefix'] : '';

		/**
		 * Default params:
		 */
		$params = array_merge( array(
				// This is what will enclose the block in the skin:
				'block_start'       => '<div class="bSideItem">',
				'block_end'         => "</div>\n",
				// Title:
				'block_title_start' => '<h3>',
				'block_title_end'   => '</h3>',
				// This is what will enclose the body:
				'block_body_start'  => '',
				'block_body_end'    => '',
				// This is what will enclose the list:
				'list_start'        => '<ul>',
				'list_end'          => "</ul>\n",
				// This is what will separate the archive links:
				'line_start'        => '<li>',
				'line_end'          => "</li>\n",
				// Archive mode:
				'mode'              => $Blog->get_setting( 'archive_mode' ),
				// Link type:
				'link_type'         => 'canonic',
				'context_isolation' => $itemlist_prefix.'m,'.$itemlist_prefix.'w,'.$itemlist_prefix.'p,'.$itemlist_prefix.'title,'.$itemlist_prefix.'unit,'.$itemlist_prefix.'dstart',
				// Add form fields?:
				'form'              => false,
				// Number of archive entries to display:
				'limit'             => 12,
				// More link text:
				'more_link'         => T_('More').'...',
			), $params );

		// Sort order (used only in postbypost mode):
		if( $params['mode'] != 'postbypost' )
		{
			$params['sort_order'] = 'date';
		}
		if( ! isset( $params['sort_order'] ) )
		{	// Set default sort order:
			$params['sort_order'] = $Blog->get_setting( 'archives_sort_order' );
		}

		// Daily archive date format?
		if( ! isset( $params['day_date_format'] ) || $params['day_date_format'] == '' )
		{
			$dateformat = locale_datefmt();
			$params['day_date_format'] = $dateformat;
		}

		$ArchiveList = new ArchiveList( $params['mode'], $params['limit'], $params['sort_order'], ($params['link_type'] == 'context'),
																			$this->dbtable, $this->dbprefix, $this->dbIDname );

		echo $params['block_start'];

		if( !empty($params['title']) )
		{	// We want to display a title for the widget block:
			echo $params['block_title_start'];
			echo $params['title'];
			echo $params['block_title_end'];
		}

		echo $params['block_body_start'];

		echo $params['list_start'];
		while( $ArchiveList->get_item( $arc_year, $arc_month, $arc_dayofmonth, $arc_w, $arc_count, $post_ID, $post_title, $permalink) )
		{
			echo $params['line_start'];
			switch( $params['mode'] )
			{
				case 'monthly':
					// --------------------------------- MONTHLY ARCHIVES -------------------------------------
					$arc_m = $arc_year.zeroise($arc_month,2);

					if( $params['form'] )
					{ // We want a radio button:
						echo '<input type="radio" name="'.$itemlist_prefix.'m" value="'.$arc_m.'" class="checkbox"';
						if( $m == $arc_m ) echo ' checked="checked"' ;
						echo ' /> ';
					}

					$text = T_($month[zeroise($arc_month,2)]).' '.$arc_year;

					if( $params['link_type'] == 'context' )
					{	// We want to preserve current browsing context:
						echo '<a rel="nofollow" href="'.regenerate_url( $params['context_isolation'], $itemlist_prefix.'m='.$arc_m ).'">'.$text.'</a>';
					}
					else
					{	// We want to link to the absolute canonical URL for this archive:
						echo $Blog->gen_archive_link( $text, T_('View monthly archive'), $arc_year, $arc_month );
					}

					echo ' <span class="dimmed">('.$arc_count.')</span>';
					break;

				case 'daily':
					// --------------------------------- DAILY ARCHIVES ---------------------------------------
					$arc_m = $arc_year.zeroise($arc_month,2).zeroise($arc_dayofmonth,2);

					if( $params['form'] )
					{ // We want a radio button:
						echo '<input type="radio" name="'.$itemlist_prefix.'m" value="'. $arc_m. '" class="checkbox"';
						if( $m == $arc_m ) echo ' checked="checked"' ;
						echo ' /> ';
					}

					$text = mysql2date($params['day_date_format'], $arc_year.'-'.zeroise($arc_month,2).'-'.zeroise($arc_dayofmonth,2).' 00:00:00');

					if( $params['link_type'] == 'context' )
					{	// We want to preserve current browsing context:
						echo '<a rel="nofollow" href="'.regenerate_url( $params['context_isolation'], $itemlist_prefix.'m='.$arc_m ).'">'.$text.'</a>';
					}
					else
					{	// We want to link to the absolute canonical URL for this archive:
						echo $Blog->gen_archive_link( $text, T_('View daily archive'), $arc_year, $arc_month, $arc_dayofmonth );
					}

					echo ' <span class="dimmed">('.$arc_count.')</span>';
					break;

				case 'weekly':
					// --------------------------------- WEEKLY ARCHIVES --------------------------------------

					$text = $arc_year.', '.T_('week').' '.$arc_w;

					if( $params['link_type'] == 'context' )
					{	// We want to preserve current browsing context:
						echo '<a rel="nofollow" href="'.regenerate_url( $params['context_isolation'], $itemlist_prefix.'m='.$arc_year.'&amp;'.$itemlist_prefix.'w='.$arc_w ).'">'.$text.'</a>';
					}
					else
					{	// We want to link to the absolute canonical URL for this archive:
						echo $Blog->gen_archive_link( $text, T_('View weekly archive'), $arc_year, NULL, NULL, $arc_w );
					}
					echo ' <span class="dimmed">('.$arc_count.')</span>';
					break;

				case 'postbypost':
				default:
					// -------------------------------- POST BY POST ARCHIVES ---------------------------------

					if( $post_title)
					{
						$text = strip_tags($post_title);
					}
					else
					{
						$text = $post_ID;
					}

					if( $params['link_type'] == 'context' )
					{	// We want to preserve current browsing context:
						echo '<a rel="nofollow" href="'.regenerate_url( $params['context_isolation'], 'p='.$post_ID ).'">'.$text.'</a>';
					}
					else
					{
						// fp> THIS IS ALL OBSOLETE. There is a better way to have a post list with a specific widget.
						// TO BE DELETED (waiting for photoblog cleanup)

						// until the cleanup, a fix. I hope.

						echo '<a href="'. $permalink .'">'.$text.'</a>';
					}
			}

			echo $params['line_end'];
		}

		// Display more link:
		if( !empty($params['more_link']) )
		{
			echo $params['line_start'];
			echo '<a href="';
			$Blog->disp( 'arcdirurl', 'raw' );
			echo '">'.format_to_output($params['more_link']).'</a>';
			echo $params['line_end'];
		}

		echo $params['list_end'];

		echo $params['block_body_end'];

		echo $params['block_end'];

		return true;
	}


  /**
   * Get definitions for widget specific editable params
   *
	 * @see Plugin::GetDefaultSettings()
	 * @param local params like 'for_editing' => true
	 */
	function get_widget_param_definitions( $params )
	{
		$r = array(
			'title' => array(
					'label' => T_('Block title'),
					'note' => T_('Title to display in your skin.'),
					'size' => 60,
					'defaultvalue' => T_('Archives'),
			),
			'limit' => array(
				'label' => T_( 'Max items' ),
				'note' => T_( 'Maximum number of items to display.' ),
				'size' => 4,
				'defaultvalue' => 12,
			),
			'mode' => array(
				'label' => T_('Archive grouping'),
				'note' => T_('How do you want to browse the post archives? May also apply to permalinks.'),
				'type' => 'radio',
				'options' => array(
						array( 'monthly', T_('monthly') ),
						array( 'weekly', T_('weekly') ),
						array( 'daily', T_('daily') ),
						array( 'postbypost', T_('post by post') ),
					),
				'defaultvalue' => 'monthly',
			),
			'sort_order' => array(
				'label' => T_('Archive sorting'),
				'note' => T_('How to sort your archives? (only in post by post mode)'),
				'type' => 'radio',
				'options' => array(
						array( 'date', T_('date') ),
						array( 'title', T_('title') ),
					),
				'defaultvalue' => 'date',
			),
		);
		return $r;
	}

}


/**
 * Archive List Class
 *
 * @package evocore
 */
class ArchiveList extends Results
{
	var $archive_mode;
	var $arc_w_last;

	/**
	 * Constructor
	 *
	 * Note: Weekly archives use MySQL's week numbering and MySQL default if applicable.
	 * In MySQL < 4.0.14, WEEK() always uses mode 0: Week starts on Sunday;
	 * Value range is 0 to 53; week 1 is the first week that starts in this year.
	 *
	 * @link http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html
	 *
	 * @todo categories combined with 'ALL' are not supported (will output too many archives,
	 * some of which will resolve to no results). We need subqueries to support this efficiently.
	 *
	 * @param string
	 * @param integer
	 * @param boolean
	 */
	function __construct(
		$archive_mode = 'monthly',
		$limit = 100,
		$sort_order = 'date',
		$preserve_context = false,
		$dbtable = 'T_items__item',
		$dbprefix = 'post_',
		$dbIDname = 'ID' )
	{
		global $DB;
		global $blog, $cat, $catsel;
		global $Collection, $Blog;
		global $show_statuses;
		global $author, $assgn, $status, $types;
		global $s, $sentence, $exact;

		$this->dbtable = $dbtable;
		$this->dbprefix = $dbprefix;
		$this->dbIDname = $dbIDname;
		$this->archive_mode = $archive_mode;


		/*
		 * WE ARE GOING TO CONSTRUCT THE WHERE CLOSE...
		 */
		$this->ItemQuery = new ItemQuery( $this->dbtable, $this->dbprefix, $this->dbIDname ); // TEMPORARY OBJ

		// - - Select a specific Item:
		// $this->ItemQuery->where_ID( $p, $title );

		if( is_admin_page() )
		{	// Don't restrict by date in the Back-office
			$timestamp_min = NULL;
			$timestamp_max = NULL;
		}
		else
		{	// Restrict posts by date started
			$timestamp_min = $Blog->get_timestamp_min();
			$timestamp_max = $Blog->get_timestamp_max();
		}

		if( $preserve_context )
		{	// We want to preserve the current context:
			// * - - Restrict to selected blog/categories:
			$this->ItemQuery->where_chapter( $blog, $cat, $catsel );

			// * Restrict to the statuses we want to show:
			$this->ItemQuery->where_visibility( $show_statuses );

			// Restrict to selected authors:
			$this->ItemQuery->where_author( $author );

			// Restrict to selected assignees:
			$this->ItemQuery->where_assignees( $assgn );

			// Restrict to selected satuses:
			$this->ItemQuery->where_statuses( $status );

			// - - - + * * timestamp restrictions:
			$this->ItemQuery->where_datestart( '', '', '', '', $timestamp_min, $timestamp_max );

			// Keyword search stuff:
			$this->ItemQuery->where_keywords( $s, $sentence, $exact );

			$this->ItemQuery->where_types( $types );
		}
		else
		{	// We want to preserve only the minimal context:
			// * - - Restrict to selected blog/categories:
			$this->ItemQuery->where_chapter( $blog, '', array() );

			// * Restrict to the statuses we want to show:
			$this->ItemQuery->where_visibility( $show_statuses );

			// - - - + * * timestamp restrictions:
			$this->ItemQuery->where_datestart( '', '', '', '', $timestamp_min, $timestamp_max );

			// Include all types except pages, intros and sidebar links:
			$this->ItemQuery->where_itemtype_usage( 'post' );
		}

		// Restrict with locale visibility by current navigation locale:
		$this->ItemQuery->where_locale_visibility();

		$this->from = $this->ItemQuery->get_from();
		$this->where = $this->ItemQuery->get_where();
		$this->group_by = $this->ItemQuery->get_group_by();

		switch( $this->archive_mode )
		{
			case 'monthly':
				// ------------------------------ MONTHLY ARCHIVES ------------------------------------
				$sql = 'SELECT EXTRACT(YEAR FROM '.$this->dbprefix.'datestart) AS year, EXTRACT(MONTH FROM '.$this->dbprefix.'datestart) AS month,
																	COUNT(DISTINCT postcat_post_ID) AS count '
													.$this->from
													.$this->where.'
													GROUP BY year, month
													ORDER BY year DESC, month DESC';
				break;

			case 'daily':
				// ------------------------------- DAILY ARCHIVES -------------------------------------
				$sql = 'SELECT EXTRACT(YEAR FROM '.$this->dbprefix.'datestart) AS year, MONTH('.$this->dbprefix.'datestart) AS month,
																	DAYOFMONTH('.$this->dbprefix.'datestart) AS day,
																	COUNT(DISTINCT postcat_post_ID) AS count '
													.$this->from
													.$this->where.'
													GROUP BY year, month, day
													ORDER BY year DESC, month DESC, day DESC';
				break;

			case 'weekly':
				// ------------------------------- WEEKLY ARCHIVES -------------------------------------
				$sql = 'SELECT EXTRACT(YEAR FROM '.$this->dbprefix.'datestart) AS year, '.
															$DB->week( $this->dbprefix.'datestart', locale_startofweek() ).' AS week,
															COUNT(DISTINCT postcat_post_ID) AS count '
													.$this->from
													.$this->where.'
													GROUP BY year, week
													ORDER BY year DESC, week DESC';
				break;

			case 'postbypost':
			default:
				// ----------------------------- POSY BY POST ARCHIVES --------------------------------
				$this->count_total_rows();
				$archives_list = new ItemListLight( $Blog , $Blog->get_timestamp_min(), $Blog->get_timestamp_max(), $this->total_rows );
				$archives_list->set_filters( array(
						'visibility_array' => array( 'published' ),  // We only want to advertised published items
						'itemtype_usage' => 'post', // Include all types with usage "post"
					) );

				if($sort_order == 'title')
				{
					$archives_list->set_filters( array(
					'orderby' => 'title',
					'order' => 'ASC') );
				}

				$archives_list->query();
				$this->rows = array();
				while ($Item = $archives_list->get_item())
				{
					$this->rows[] = $Item;
				}
				$this->result_num_rows = $archives_list->result_num_rows;
				$this->current_idx = 0;
				$this->arc_w_last = '';
				return;
		}


		// dh> Temp fix for MySQL bug - apparently in/around 4.1.21/5.0.24.
		// See http://forums.b2evolution.net/viewtopic.php?p=42529#42529
		if( in_array($this->archive_mode, array('monthly', 'daily', 'weekly')) )
		{
			$sql_version = $DB->version;
			if( version_compare($sql_version, '4', '>') )
			{
				$sql = 'SELECT SQL_CALC_FOUND_ROWS '.substr( $sql, 7 ); // "SQL_CALC_FOUND_ROWS" available since MySQL 4
			}
		}

		parent::__construct( $sql, 'archivelist_', '', $limit );

		$this->restart();
	}


	/**
	 * Count the number of rows of the SQL result
	 *
	 * These queries are complex enough for us not to have to rewrite them:
	 * dh> ???
	 */
	function count_total_rows( $sql_count = NULL )
	{
		global $DB;

		switch( $this->archive_mode )
		{
			case 'monthly':
				// ------------------------------ MONTHLY ARCHIVES ------------------------------------
				$sql_count = 'SELECT COUNT( DISTINCT EXTRACT(YEAR FROM '.$this->dbprefix.'datestart), EXTRACT(MONTH FROM '.$this->dbprefix.'datestart) ) '
													.$this->from
													.$this->where;
				break;

			case 'daily':
				// ------------------------------- DAILY ARCHIVES -------------------------------------
				$sql_count = 'SELECT COUNT( DISTINCT EXTRACT(YEAR FROM '.$this->dbprefix.'datestart), EXTRACT(MONTH FROM '.$this->dbprefix.'datestart),
																	EXTRACT(DAY FROM '.$this->dbprefix.'datestart) ) '
													.$this->from
													.$this->where;
				break;

			case 'weekly':
				// ------------------------------- WEEKLY ARCHIVES -------------------------------------
				$sql_count = 'SELECT COUNT( DISTINCT EXTRACT(YEAR FROM '.$this->dbprefix.'datestart), '
													.$DB->week( $this->dbprefix.'datestart', locale_startofweek() ).' ) '
													.$this->from
													.$this->where;
				break;

			case 'postbypost':
			default:
				// ----------------------------- POSY BY POST ARCHIVES --------------------------------
				$sql_count = 'SELECT COUNT( DISTINCT '.$this->dbIDname.' ) '
													.$this->from
													.$this->where
													.$this->group_by;
		}

		// echo $sql_count;

		$this->total_rows = $DB->get_var( $sql_count ); //count total rows

		// echo 'total rows='.$this->total_rows;
	}


	/**
	 * Rewind resultset
	 */
	function restart()
	{
		// Make sure query has executed at least once:
		$this->run_query();

		$this->current_idx = 0;
		$this->arc_w_last = '';
	}

	/**
	 * Getting next item in archive list
	 *
	 * WARNING: these are *NOT* Item objects!
	 */
	function get_item( & $arc_year, & $arc_month, & $arc_dayofmonth, & $arc_w, & $arc_count, & $post_ID, & $post_title, & $permalink )
	{
		// echo 'getting next item<br />';

		if( $this->current_idx >= $this->result_num_rows )
		{	// No more entry
			return false;
		}

		$arc_row = $this->rows[ $this->current_idx++ ];

		switch( $this->archive_mode )
		{
			case 'monthly':
				$arc_year  = $arc_row->year;
				$arc_month = $arc_row->month;
				$arc_count = $arc_row->count;
				return true;

			case 'daily':
				$arc_year  = $arc_row->year;
				$arc_month = $arc_row->month;
				$arc_dayofmonth = $arc_row->day;
				$arc_count = $arc_row->count;
				return true;

			case 'weekly':
				$arc_year  = $arc_row->year;
				$arc_w = $arc_row->week;
				$arc_count = $arc_row->count;
				return true;

			case 'postbypost':
			default:
				$post_ID = $arc_row->ID;
				$post_title = $arc_row->title;
				$permalink = $arc_row->get_permanent_url();
				return true;
		}
	}
}

?>