<?php
/**
 * This file implements the abstract Link Owner class, which is a wrapper class for objects which can have linked files.
 * Important: This class is abstract must never be instantiated.
 *
 * 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.' );

/**
 * LinkOwner Abstract Class
 *
 * @package evocore
 */
class LinkOwner
{
	/**
	 * Type of the Link Owner object
	 *
	 * @var string
	 */
	var $type;

	/**
	 * The link owner object
	 *
	 * @var DataObject
	 */
	var $link_Object;

	/**
	 * Array of Links attached to this object.
	 *
	 * NULL when not initialized.
	 *
	 * @var array
	 * @access public
	 */
	var $Links = NULL;

	/**
	 * The link owner Blog
	 *
	 * @var Blog
	 */
	var $Blog = NULL;

	/**
	 * The translation map, and it must be initialized in every subclass constructor
	 *
	 * @var array
	 */
	var $_trans = NULL;

	/**
	 * Name of ID field in DB without prefix 'link_'
	 *
	 * @var string
	 */
	var $ID_field_name;

	/**
	 * @var integer Last order
	 */
	var $last_order = NULL;


	/**
	 * Abstract methods that needs to be overriden in every subclass
	 *
	 * function check_perm( $perm_name, $assert = false ); // check link owner object ( item, comment, ... ) edit/view permission
	 * function get_positions( $file_ID = NULL ); // get all positions where link can be displayed ( 'teaser', 'aftermore' )
	 * function get_edit_url( $glue = '&amp;', $url_type = NULL ); // get link owner edit url
	 * function get_view_url( $glue = '&amp;', $url_type = NULL ); // get link owner view url
	 * function load_Links(); // load link owner all links
	 * function add_link( $file_ID, $position, $order, $update_owner = true ); // add a new link to link owner
	 * function load_Blog(); // set Link Owner Blog
	 */

	/**
	 * Constructor
	 *
	 * @protected It is allowed to be called only from subclasses
	 *
	 * @param object the link owner object
	 * @param string the link type ( item, comment, ... )
	 * @param string Name of ID field ( itm_ID, cmt_ID, usr_ID, ecmp_ID, msg_ID )
	 * @param integer ID of temporary object
	 */
	function __construct( $link_Object, $type, $ID_field_name, $tmp_ID = NULL )
	{
		$this->ID_field_name = $ID_field_name;
		$this->type = $type;
		$this->set_object( $link_Object, $tmp_ID );
	}


	/**
	 * Set object of this link owner
	 * If object is creating currently then new temporary object will be used instead
	 *
	 * @param object Object
	 */
	function set_object( $link_Object, $tmp_ID = NULL )
	{
		$this->link_Object = $link_Object;

		if( empty( $link_Object ) || empty( $link_Object->ID ) )
		{	// The object is creating currently, we should use a temporary object:
			if( $tmp_ID )
			{	// Get already created temporary object:
				$TemporaryIDCache = & get_TemporaryIDCache();
				$tmp_link_Object = & $TemporaryIDCache->get_by_ID( $tmp_ID, false, false );
			}
			else
			{	// Create new temporary object:
				$tmp_link_Object = new TemporaryID();
				if( $this->type == 'comment' && $link_Object->type == 'meta' )
				{	// Special case for internal (meta) comment so we can determine between internal and regular comments:
					$tmp_link_Object_type = 'metacomment';
				}
				else
				{
					$tmp_link_Object_type = $this->type;
				}
				$tmp_link_Object->set( 'type', $tmp_link_Object_type );
				if( ! empty( $this->link_Object->blog_ID ) )
				{	// Set parent collection ID of Item:
					$tmp_link_Object->set( 'coll_ID', $this->link_Object->blog_ID );
				}
				if( ! empty( $this->link_Object->item_ID ) )
				{	// Set parent item ID of Comment:
					$tmp_link_Object->set( 'item_ID', $this->link_Object->item_ID );
				}
				$tmp_link_Object->dbinsert();
				// Update global param in order to don't create temp object twice:
				set_param( 'temp_link_owner_ID', $tmp_link_Object->ID );
			}

			if( ! is_object( $this->link_Object ) || empty( $this->link_Object ) )
			{	// Try to create object if it is empty by some unknown reason:
				switch( $this->type )
				{
					case 'user':
						load_class( 'users/model/_user.class.php', 'User' );
						$this->link_Object = new User();
						break;
					case 'item':
						load_class( 'items/model/_item.class.php', 'Item' );
						$this->link_Object = new Item();
						break;
					case 'comment':
						load_class( 'comments/model/_comment.class.php', 'Comment' );
						$this->link_Object = new Comment();
						break;
					case 'message':
						load_class( 'messaging/model/_message.class.php', 'Message' );
						$this->link_Object = new Message();
						break;
					case 'emailcampaign':
						load_class( 'email_campaigns/model/_emailcampaign.class.php', 'EmailCampaign' );
						$this->link_Object = new EmailCampaign();
						break;
					default:
						debug_die( 'Unknow LinkOwner type "'.$this->type.'"' );
				}
			}

			if( $tmp_link_Object->ID > 0 )
			{	// Mark this link owner is using a temporary object:
				$this->link_Object->tmp_ID = $tmp_link_Object->ID;
				$this->link_Object->tmp_coll_ID = $tmp_link_Object->get( 'coll_ID' );
				$this->link_Object->tmp_item_ID = $tmp_link_Object->get( 'item_ID' );
				$this->link_Object->tmp_type = $tmp_link_Object->get( 'type' );
			}
		}
	}


	/**
	 * Get all links
	 */
	function & get_Links()
	{
		$this->load_Links();

		return $this->Links;
	}

	/**
	 * Remove link from the owner
	 *
	 * @param object Link
	 * @return boolean true on success
	 */
	function remove_link( & $Link )
	{
		$this->load_Links();

		$index = array_search( $Link, $this->Links );
		if( $index !== false )
		{
			unset( $this->Links[ $index ] );
		}
		$LinkCacche = & get_LinkCache();
		$LinkCacche->remove( $Link );
		return $Link->dbdelete();
	}

	/**
	 * Get Link by File ID
	 */
	function & get_link_by_file_ID( $file_ID )
	{
		$this->load_Links();

		$r = NULL;
		foreach( $this->Links as $Link )
		{
			if( $Link->file_ID == $file_ID )
			{
				$r = $Link;
				break;
			}
		}

		return $r;
	}

	/**
	 * Get Link by link ID
	 *
	 * @param integer link ID
	 * @return object The Link with the requested ID if it exisits between this owners links, NULL otherwise
	 */
	function get_link_by_link_ID( $link_ID )
	{
		$this->load_Links();

		if( isset( $this->Links[ $link_ID ] ) )
		{
			return $this->Links[ $link_ID ];
		}

		return NULL;
	}

	/**
	 * Count how many files are attached to this link owner owner object
	 *
	 * @return int the number of attachments
	 */
	function count_links()
	{
		$this->load_Links();

		return count( $this->Links );
	}


	/**
	 * Get collection object
	 *
	 * @return object
	 */
	function & get_Blog()
	{
		$this->load_Blog();

		return $this->Blog;
	}


	/**
	 * Get collection ID
	 *
	 * @return integer
	 */
	function get_blog_ID()
	{
		$Blog = & $this->get_Blog();

		return $Blog ? $Blog->ID : 0;
	}


	/**
	 * Get where condition for select query to get owner links
	 *
	 * @return string
	 */
	function get_where_condition()
	{
		return 'link_'.$this->get_ID_field_name().' = '.$this->get_ID();
	}


	/**
	 * Get SQL query to select all links attached to this owner
	 *
	 * @param string links order by
	 * @return object SQL
	 */
	function get_SQL( $order_by = NULL )
	{
		$SQL = new SQL();

		/**
		 * asimo> Replace in select query the link_cmt_ID, link_itm_ID with the following ( it can be used only after MySQL 5.0)
		 * (CASE
		 *	WHEN (link_cmt_ID IS NOT NULL) THEN 'comment'
		 *	WHEN (link_itm_ID IS NOT NULL) THEN 'item'
		 *	END) as owner_type
		 */
		if( $order_by == NULL )
		{
			$order_by = 'link_order, link_ID';
		}

		// Set links query. Note: Use inner join to make sure that result contains only existing files!
		$SQL->SELECT( 'link_ID, link_position, link_order, link_cmt_ID, link_itm_ID, file_ID, file_creator_user_ID, file_type, file_title, file_root_type, file_root_ID, file_path, file_alt, file_desc, file_path_hash' );
		$SQL->FROM( 'T_links INNER JOIN T_files ON link_file_ID = file_ID' );
		$SQL->WHERE( $this->get_where_condition() );
		$SQL->ORDER_BY( $order_by );

		return $SQL;
	}


	/**
	 * Check if current link owner is used as temporary for new creating object
	 *
	 * @return boolean TRUE if link owner is temporary
	 */
	function is_temp()
	{
		return ! empty( $this->link_Object->tmp_ID );
	}

	/**
	 * Get link owner object ID
	 */
	function get_ID()
	{
		return $this->is_temp() ? $this->link_Object->tmp_ID : $this->link_Object->ID;
	}


	/**
	 * Get link owner object ID field name
	 *
	 * @return string
	 */
	function get_ID_field_name()
	{
		return $this->is_temp() ? 'tmp_ID' : $this->ID_field_name;
	}


	/**
	 * Get link owner object parameter
	 *
	 * @param string parameter name to get
	 */
	function get( $parname )
	{
		return $this->link_Object->dget( $parname );
	}

	/**
	 * Get a ready-to-display position name by key value
	 *
	 * @param string link position
	 */
	function dget_position( $position )
	{
		$positions = $this->get_positions();
		if( isset( $positions[ $position ] ) )
		{
			return $positions[ $position ];
		}
		return NULL;
	}

	/**
	 * Get default position for a new link
	 *
	 * @param integer File ID
	 * @return string Position
	 */
	function get_default_position( $file_ID )
	{
		// Use by default this position for all simple link owner such as "comment" and "user"
		// For "item" we set default position depending on file type and order
		return 'aftermore';
	}


	/**
	 * Get list of attached files
	 *
	 * INNER JOIN on files ensures we only get back file links
	 *
	 * @param integer Limit max result
	 * @param string Restrict to files/images linked to a specific position.
	 *               Position can be 'teaser'|'aftermore'|'inline'
	 *               Use comma as separator
	 * @param string File type: 'image', 'audio', 'other'; NULL - to select all
	 * @return DataObjectList2 on success or NULL if no linked files found
	 */
	function get_attachment_FileList( $limit = 1000, $position = NULL, $file_type = NULL )
	{
		if( ! isset($GLOBALS['files_Module']) )
		{
			return NULL;
		}

		global $DB;

		load_class( '_core/model/dataobjects/_dataobjectlist2.class.php', 'DataObjectList2' );

		$FileCache = & get_FileCache();

		$FileList = new DataObjectList2( $FileCache ); // IN FUNC

		$SQL = new SQL();
		$SQL->SELECT( 'file_ID, file_creator_user_ID, file_type, file_title, file_root_type, file_root_ID, file_path, file_alt, file_desc, file_path_hash, link_ID' );
		$SQL->FROM( 'T_links INNER JOIN T_files ON link_file_ID = file_ID' );
		$SQL->WHERE( $this->get_where_condition() );
		if( !empty($position) )
		{
			$position = explode( ',', $position );
			$SQL->WHERE_and( 'link_position IN ( '.$DB->quote( $position ).' )' );
		}
		$SQL->ORDER_BY( 'link_order' );
		$SQL->LIMIT( $limit );

		if( ! is_null( $file_type ) )
		{ // Restrict the Links by File type
			$SQL->WHERE_and( 'file_type = '.$DB->quote( $file_type ).' OR file_type IS NULL' );
		}

		$FileList->sql = $SQL->get();

		$FileList->run_query( false, false, false, 'get_attachment_FileList' );

		if( $FileList->result_num_rows == 0 )
		{	// Nothing found
			$FileList = NULL;
		}

		return $FileList;
	}


	/**
	 * Get list of attached Links
	 *
	 * @param integer Limit max result
	 * @param string Restrict to files/images linked to a specific position.
	 *               Position can be 'teaser'|'aftermore'|'inline'
	 *               Use comma as separator
	 * @param string File type: 'image', 'audio', 'other'; NULL - to select all
	 * @param array Params
	 * @return DataObjectList2 on success or NULL if no linked files found
	 */
	function get_attachment_LinkList( $limit = 1000, $position = NULL, $file_type = NULL, $params = array() )
	{
		if( ! isset($GLOBALS['files_Module']) )
		{
			return NULL;
		}

		$params = array_merge( array(
				'sql_select_add' => '', // Additional fields for SELECT clause
				'sql_order_by'   => 'link_order', // ORDER BY clause
			), $params );

		global $DB;

		load_class( '_core/model/dataobjects/_dataobjectlist2.class.php', 'DataObjectList2' );

		$LinkCache = & get_LinkCache();

		$LinkList = new DataObjectList2( $LinkCache ); // IN FUNC

		$SQL = new SQL();
		$SQL->SELECT( 'l.*' );
		$SQL->SELECT_add( $params['sql_select_add'] );
		$SQL->FROM( 'T_links AS l' );
		$SQL->WHERE( $this->get_where_condition() );
		if( !empty($position) )
		{
			$position = explode( ',', $position );
			$SQL->WHERE_and( 'link_position IN ( '.$DB->quote( $position ).' )' );
		}
		$SQL->ORDER_BY( $params['sql_order_by'] );
		$SQL->LIMIT( $limit );

		if( ! is_null( $file_type ) )
		{ // Restrict the Links by File type
			$SQL->FROM_add( 'INNER JOIN T_files ON link_file_ID = file_ID' );
			$SQL->WHERE_and( 'file_type = '.$DB->quote( $file_type ).' OR file_type IS NULL' );
		}

		$LinkList->sql = $SQL->get();

		$LinkList->run_query( false, false, false, 'get_attachment_LinkList' );

		if( $LinkList->result_num_rows == 0 )
		{ // Nothing found
			$LinkList = NULL;
		}

		return $LinkList;
	}


	/**
	 * Get translated text for the specific link owner class
	 *
	 * @param string text key in the translation map
	 */
	function translate( $text_key, $text_params = NULL )
	{
		if( empty( $this->_trans ) || empty( $text_key ) || ( !array_key_exists( $text_key, $this->_trans ) ) )
		{ // This text was not listed in translation map
			return NULL;
		}

		return sprintf( T_( $this->_trans[ $text_key ] ), $text_params );
	}


	/**
	 * Update owner last_touched_ts if exists
	 * This must be override in the subclasses if the owner object has last_touched_ts field
	 */
	function update_last_touched_date()
	{
		return;
	}


	/**
	 * Update owner contents_last_updated_ts if exists
	 * This must be override in the subclasses if the owner object has contents_last_updated_ts field
	 */
	function update_contents_last_updated_ts()
	{
		return;
	}


	/**
	 * This function is called after when some file was unlinked from owner
	 *
	 * @param integer Link ID
	 */
	function after_unlink_action( $link_ID = 0 )
	{
		// Update last touched date content last updated date of the Owner:
		$this->update_last_touched_date();
		$this->update_contents_last_updated_ts();
	}


	/**
	 * Get last order number of this Link Owner
	 *
	 * @return integer
	 */
	function get_last_order()
	{
		global $DB;

		if( $this->get_ID() == 0 )
		{
			return 0;
		}

		if( $this->last_order === NULL )
		{	// Get last order frin DB:
			$SQL = new SQL( 'Get last order number of the Link Owner ( '.$this->type.', #'.$this->get_ID().' )' );
			$SQL->SELECT( 'MAX( link_order )' );
			$SQL->FROM( 'T_links' );
			$SQL->WHERE( 'link_'.$this->get_ID_field_name().' = '.$this->get_ID() );
			$this->last_order = intval( $DB->get_var( $SQL ) );
		}

		return $this->last_order;
	}
}

?>
