<?php
/**
 * Attachment list model definition
 *
 * @package Attachments
 * @subpackage Attachments_Component
 *
 * @copyright Copyright (C) 2007-2018 Jonathan M. Cameron, All Rights Reserved
 * @license http://www.gnu.org/licenses/gpl-3.0.html GNU/GPL
 * @link http://joomlacode.org/gf/project/attachments/frs/
 * @author Jonathan M. Cameron
 */

defined('_JEXEC') or die('Restricted access');

jimport('joomla.application.component.helper');

/** Define the legacy classes, if necessary */
require_once(JPATH_SITE.'/components/com_attachments/legacy/model.php');


/**
 * Attachment List Model for all attachments belonging to one
 * content article (or other content-related entity)
 *
 * @package Attachments
 */
class AttachmentsModelAttachments extends JModelLegacy
{
	/**
	 * ID of parent of the list of attachments
	 */
	var $_parent_id = null;

	/**
	 * type of parent
	 */
	var $_parent_type = null;

	/**
	 * type of parent entity (each parent_type can support several)
	 */
	var $_parent_entity = null;

	/**
	 * Parent class object (an Attachments extension plugin object)
	 */
	var $_parent = null;

	/**
	 * Parent title
	 */
	var $_parent_title = null;

	/**
	 * Parent entity name
	 */
	var $_parent_entity_name = null;

	/**
	 * Whether some of the attachments should be visible to the user
	 */
	var $_some_visible = null;

	/**
	 * Whether some of the attachments should be modifiable to the user
	 */
	var $_some_modifiable = null;

	/**
	 * The desired sort order
	 */
	var $_sort_order;


	/**
	 * The list of attachments for the specified article/content entity
	 */
	var $_list = null;

	/**
	 * Number of attachments
	 *
	 * NOTE: After the list of attachments has been retrieved, if it is empty, this is set to zero.
	 *		 But _list remains null.   You can use this to check to see if the list has been loaded.
	 */
	var $_num_attachments = null;


	/**
	 * Constructor
	 */
	public function __construct()
	{
		parent::__construct();
	}


	/**
	 * Set the parent id (and optionally the parent type)
	 *
	 * NOTE: If the $id is null, it will get both $id and $parent_id from JRequest
	 *
	 * @param int $id the id of the parent
	 * @param string $parent_type the parent type (defaults to 'com_content')
	 * @param string $parent_entity the parent entity (defaults to 'default')
	 */
	public function setParentId($id=null, $parent_type='com_content', $parent_entity='default')
	{
		// Get the parent id and type
		if ( is_numeric($id) ) {
			$parent_id = (int)$id;
			}
		else {
			// It was not an argument, so get parent id and type from the JRequest
			$parent_id	 = JRequest::getInt('article_id', null);

			// Deal with special case of editing from the front end
			if ( $parent_id == null ) {
				if ( (JRequest::getCmd('view') == 'article') &&
					 (JRequest::getCmd('task') == 'edit' )) {
					$parent_id = JRequest::getInt('id', null);
					}
				}

			// If article_id is not specified, get the general parent id/type
			if ( $parent_id == null ) {
				$parent_id = JRequest::getInt('parent_id', null);
				if ( $parent_id == null ) {
					$errmsg = JText::_('ATTACH_ERROR_NO_PARENT_ID_SPECIFIED') . ' (ERR 50)';
					JError::raiseError(500, $errmsg);
					}
				}
			}

		// Reset instance variables
		$this->_parent_id = $parent_id;
		$this->_parent_type = $parent_type;
		$this->_parent_entity = $parent_entity;

		$this->_parent = null;
		$this->_parent_class = null;
		$this->_parent_title = null;
		$this->_parent_entity_name = null;

		$this->_list = null;
		$this->_sort_order = null;
		$this->_some_visible = null;
		$this->_some_modifiable = null;
		$this->_num_attachments = null;
	}



	/**
	 * Get the parent id
	 *
	 * @return the parent id
	 */
	public function getParentId()
	{
		if ( $this->_parent_id === null ) {
			$errmsg = JText::_('ATTACH_ERROR_NO_PARENT_ID_SPECIFIED') . ' (ERR 51)';
			JError::raiseError(500, $errmsg);
			}
		return $this->_parent_id;
	}


	/**
	 * Get the parent type
	 *
	 * @return the parent type
	 */
	public function getParentType()
	{
		if ( $this->_parent_type == null ) {
			$errmsg = JText::_('ATTACH_ERROR_NO_PARENT_TYPE_SPECIFIED') . ' (ERR 52)';
			throw Exception($errmsg);
			// JError::raiseError(500, $errmsg);
			}
		return $this->_parent_type;
	}


	/**
	 * Get the parent entity
	 *
	 * @return the parent entity
	 */
	public function getParentEntity()
	{
		if ( $this->_parent_entity == null ) {
			$errmsg = JText::_('ATTACH_ERROR_NO_PARENT_ENTITY_SPECIFIED') . ' (ERR 53)';
			JError::raiseError(500, $errmsg);
			}

		// Make sure we have a good parent_entity value
		if ( $this->_parent_entity == 'default' ) {
			$parent = $this->getParentClass();
			$this->_parent_entity = $parent->getDefaultEntity();
			}

		return $this->_parent_entity;
	}


	/**
	 * Get the parent class object
	 *
	 * @return the parent class object
	 */
	public function &getParentClass()
	{
		if ( $this->_parent_type == null ) {
			$errmsg = JText::_('ATTACH_ERROR_NO_PARENT_TYPE_SPECIFIED') . ' (ERR 54)';
			JError::raiseError(500, $errmsg);
			}

		if ( $this->_parent_class == null ) {

			// Get the parent handler
			JPluginHelper::importPlugin('attachments');
			$apm = getAttachmentsPluginManager();
			if ( !$apm->attachmentsPluginInstalled($this->_parent_type) ) {
				$errmsg = JText::sprintf('ATTACH_ERROR_INVALID_PARENT_TYPE_S', $parent_type) . ' (ERR 55)';
				JError::raiseError(500, $errmsg);
				}
			$this->_parent_class = $apm->getAttachmentsPlugin($this->_parent_type);
			}

		return $this->_parent_class;
	}


	/**
	 * Get the title for the parent
	 *
	 * @return the title for the parent
	 */
	public function getParentTitle()
	{
		// Get the title if we have not done it before
		if ( $this->_parent_title == null ) {

			$parent = $this->getParentClass();

			// Make sure we have an article ID
			if ( $this->_parent_id === null ) {
				$errmsg = JText::_('ATTACH_ERROR_UNKNOWN_PARENT_ID') . ' (ERR 56)';
				JError::raiseError(500, $errmsg);
				}

			$this->_parent_title = $parent->getTitle( $this->_parent_id, $this->_parent_entity );
			}

		return $this->_parent_title;
	}


	/**
	 * Get the EntityName for the parent
	 *
	 * @return the entity name for the parent
	 */
	public function getParentEntityName()
	{
		// Get the parent entity name if we have not done it before
		if ( $this->_parent_entity_name == null ) {

			// Make sure we have an article ID
			if ( $this->_parent_id === null ) {
				$errmsg = JText::_('ATTACH_ERROR_NO_PARENT_ID_SPECIFIED') . ' (ERR 57)';
				JError::raiseError(500, $errmsg);
				}

			$this->_parent_entity_name = JText::_('ATTACH_' . $this->getParentEntity());
			}

		return $this->_parent_entity_name;
	}


	/**
	 * Set the sort order (do this before doing getAttachmentsList)
	 *
	 * @param string $new_sort_order name of the new sort order
	 */
	public function setSortOrder($new_sort_order)
	{
		if ( $new_sort_order == 'filename' )
			$order_by = 'filename';
		else if ( $new_sort_order == 'filename_desc' )
			$order_by = 'filename DESC';
		else if ( $new_sort_order == 'file_size' )
			$order_by = 'file_size';
		else if ( $new_sort_order == 'file_size_desc' )
			$order_by = 'file_size DESC';
		else if ( $new_sort_order == 'description' )
			$order_by = 'description';
		else if ( $new_sort_order == 'description_desc' )
			$order_by = 'description DESC';
		else if ( $new_sort_order == 'display_name' )
			$order_by = 'display_name, filename';
		else if ( $new_sort_order == 'display_name_desc' )
			$order_by = 'display_name DESC, filename';
		else if ( $new_sort_order == 'created' )
			$order_by = 'created';
		else if ( $new_sort_order == 'created_desc' )
			$order_by = 'created DESC';
		else if ( $new_sort_order == 'modified' )
			$order_by = 'modified';
		else if ( $new_sort_order == 'modified_desc' )
			$order_by = 'modified DESC';
		else if ( $new_sort_order == 'user_field_1' )
			$order_by = 'user_field_1';
		else if ( $new_sort_order == 'user_field_1_desc' )
			$order_by = 'user_field_1 DESC';
		else if ( $new_sort_order == 'user_field_2' )
			$order_by = 'user_field_2';
		else if ( $new_sort_order == 'user_field_2_desc' )
			$order_by = 'user_field_2 DESC';
		else if ( $new_sort_order == 'user_field_3' )
			$order_by = 'user_field_3';
		else if ( $new_sort_order == 'user_field_3_desc' )
			$order_by = 'user_field_3 DESC';
		else if ( $new_sort_order == 'id' )
			$order_by = 'id';
		else
			$order_by = 'filename';

		$this->_sort_order = $order_by;
	}



	/**
	 * Get or build the list of attachments
	 *
	 * @return the list of attachments for this parent
	 */
	public function &getAttachmentsList()
	{
		// Just return it if it has already been created
		if ( $this->_list != null ) {
			return $this->_list;
			}

		// Get the component parameters
		$params = JComponentHelper::getParams('com_attachments');

		// Create the list

		// Get the parent id and type
		$parent_id	   = $this->getParentId();
		$parent_type   = $this->getParentType();
		$parent_entity = $this->getParentEntity();

		// Use parent entity corresponding to values saved in the attachments table
		$parent = $this->getParentClass();

		// Define the list order
		if ( ! $this->_sort_order ) {
			$this->_sort_order = 'filename';
			}

		// Determine allowed access levels
		$db = JFactory::getDBO();
		$user = JFactory::getUser();
		$user_levels = $user->getAuthorisedViewLevels();

		// If the user is not logged in, add extra view levels (if configured)
		$secure = $params->get('secure', false);
		$logged_in = $user->get('username') <> '';
		if ( !$logged_in AND $secure ) {
			$user_levels = Array('1'); // Override the authorised levels
			// NOTE: Public attachments are ALWAYS visible
			$guest_levels = $params->get('show_guest_access_levels', Array());
			if (is_array($guest_levels)) {
				foreach ($guest_levels as $glevel) {
					$user_levels[] = $glevel;
					}
				}
			else {
				$user_levels[] = $glevel;
				}
			}
		$user_levels = implode(',', array_unique($user_levels));

		// Create the query
		$query = $db->getQuery(true);
		$query->select('a.*, u.name as creator_name')->from('#__attachments AS a');
		$query->leftJoin('#__users AS u ON u.id = a.created_by');

		if ( $parent_id == 0 ) {
			// If the parent ID is zero, the parent is being created so we have
			// do the query differently
			$user_id = $user->get('id');
			$query->where('a.parent_id IS NULL AND u.id=' . (int)$user_id);
			}
		else {
			$query->where('a.parent_id='.(int)$parent_id);

			// Handle the state part of the query
			if ( $user->authorise('core.edit.state', 'com_attachments') ) {
				// Do not filter on state since this user can change the state of any attachment
				}
			elseif ( $user->authorise('attachments.edit.state.own', 'com_attachments') ) {
				$query->where('((a.created_by = '.(int)$user->id.') OR (a.state = 1))');
				}
			elseif ( $user->authorise('attachments.edit.state.ownparent', 'com_attachments') ) {
				// The user can edit the state of any attachment if they created the article/parent
				$parent_creator_id = $parent->getParentCreatorId($parent_id, $parent_entity);
				if ( (int)$parent_creator_id == (int)$user->get('id') ) {
					// Do not filter on state since this user can change the state of any attachment on this article/parent
					}
				else {
					// Since the user is not the creator, they should only see published attachments
					$query->where('a.state = 1');
					}
				}
			else {
				// For everyone else only show published attachments
				$query->where('a.state = 1');
				}
			}

		$query->where('a.parent_type=' . $db->quote($parent_type) . ' AND a.parent_entity=' . $db->quote($parent_entity));
		if ( !$user->authorise('core.admin') ) {
			$query->where('a.access IN ('.$user_levels.')');
			}
		$query->order($this->_sort_order);

		// Do the query
		$db->setQuery($query);
		$attachments = $db->loadObjectList();
		if ( $db->getErrorNum() ) {
			$errmsg = $db->stderr() . ' (ERR 58)';
			JError::raiseError(500, $errmsg);
			}

		$this->_some_visible = false;
		$this->_some_modifiable = false;

		// Install the list of attachments in this object
		$this->_num_attachments = count($attachments);

		// The query only returns items that are visible/accessible for
		// the user, so if it contains anything, they will be visible
		$this->_some_visible = $this->_num_attachments > 0;

		// Add permissions for each attachment in the list
		if ( $this->_num_attachments > 0 ) {

			$this->_list = $attachments;

			// Add the permissions to each row
			$parent = $this->getParentClass();

			// Add permissions
			foreach ( $attachments as $attachment ) {
				$attachment->user_may_delete = $parent->userMayDeleteAttachment($attachment);
				$attachment->user_may_edit = $parent->userMayEditAttachment($attachment);
				if ( $attachment->user_may_edit ) {
					$this->_some_modifiable = true;
					}
				}

			// Fix relative URLs
			foreach ( $attachments as $attachment ) {
				if ( $attachment->uri_type == 'url' ) {
					$url = $attachment->url;
					if ( strpos($url, '://') === false ) {
						$uri = JFactory::getURI();
						$attachment->url = $uri->base(true) . '/' . $url;
						}
					}
				}

			}

		// Finally, return the list!
		return $this->_list;
	}


	/**
	 * Get the number of attachments
	 *
	 * @return the number of attachments for this parent
	 */
	public function numAttachments()
	{
		return $this->_num_attachments;
	}


	/**
	 * Are some of the attachments be visible?
	 *
	 * @return true if there are attachments and some should be visible
	 */
	public function someVisible()
	{
		// See if the attachments list has been loaded
		if ( $this->_list == null ) {

			// See if we have already loaded the attachements list
			if ( $this->_num_attachments === 0 ) {
				return false;
				}

			// Since the attachments have not been loaded, load them now
			$this->getAttachmentsList();
			}

		return $this->_some_visible;
	}


	/**
	 * Should some of the attachments be modifiable?
	 *
	 * @return true if there are attachments and some should be modifiable
	 */
	public function someModifiable()
	{
		// See if the attachments list has been loaded
		if ( $this->_list == null ) {

			// See if we have already loaded the attachements list
			if ( $this->_num_attachments === 0 ) {
				return false;
				}

			// Since the attachments have not been loaded, load them now
			$this->getAttachmentsList();
			}

		return $this->_some_modifiable;
	}



	/**
	 * Returns the types of attachments
	 *
	 * @return 'file', 'url', 'both', or false (if no attachments)
	 */
	public function types()
	{
		// Make sure the attachments are loaded
		if ( $this->_list == null ) {

			// See if we have already loaded the attachements list
			if ( $this->_num_attachments === 0 ) {
				return false;
				}

			// Since the attachments have not been loaded, load them now
			$this->getAttachmentsList();
			}

		// Scan the attachments
		$types = false;
		foreach ( $this->_list as $attachment ) {
			if ( $types ) {
				if ( $attachment->uri_type != $types ) {
					return 'both';
					}
				}
			else {
				$types = $attachment->uri_type;
				}
			}

		return $types;
	}

}
