<?php
/**
 * Attachments plugins for content
 *
 * @package		Attachments
 * @subpackage	Attachments_Plugin_For_Content
 *
 * @author		Jonathan M. Cameron <jmcameron@jmcameron.net>
 * @copyright	Copyright (C) 2009-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/
 */

// No direct access
defined('_JEXEC') or die('Restricted access');

/** Load the attachments plugin class */
if (!JPluginHelper::importPlugin('attachments', 'attachments_plugin_framework'))
{
	// Fail gracefully if the Attachments plugin framework plugin is disabled
	return;
}


/**
 * The class for the Attachments plugin for regular Joomla! content (articles, categories)
 *
 * @package	 Attachments
 * @since	 3.0
 */
class AttachmentsPlugin_Com_Content extends AttachmentsPlugin
{
	/**
	 * Constructor
	 *
	 * @param	object	&$subject  The object to observe
	 * @param	array	$config	   An optional associative array of configuration settings.
	 *
	 * Recognized key values include 'name', 'group', 'params', 'language'
	 * (this list is not meant to be comprehensive).
	 */
	public function __construct(&$subject, $config = array())
	{
		parent::__construct($subject, $config);

		// Configure the plugin
		$this->_name		  = 'attachments_for_content';

		// Set basic attachments defaults
		$this->parent_type	  = 'com_content';
		$this->default_entity = 'article';

		// Add the information about the default entity (article)
		$this->entities[]					 = 'article';
		$this->entity_name['article']		 = 'article';
		$this->entity_name['default']		 = 'article';
		$this->entity_table['article']		 = 'content';
		$this->entity_id_field['article']	 = 'id';
		$this->entity_title_field['article'] = 'title';

		// Add information about the category description entity
		$this->entities[]					  = 'category';
		$this->entity_name['category']		  = 'category';
		$this->entity_table['category']		  = 'categories';
		$this->entity_id_field['category']	  = 'id';
		$this->entity_title_field['category'] = 'title';

		// Always load the language
		$this->loadLanguage();
	}

	/**
	 * Determine the parent entity
	 *
	 * From the view and the class of the parent (row of onPrepareContent plugin),
	 * determine what the entity type is for this entity.
	 *
	 * @param	&object	 &$parent  The object for the parent (row) that onPrepareContent gets
	 *
	 * @return the correct parent entity (eg, 'article', 'category')
	 */
	public function determineParentEntity(&$parent)
	{
		$view = JRequest::getCmd('view');

		// Handle category calls
		if (($view == 'category') && (get_class($parent) == 'JTableContent'))
		{
			return 'category';
		}

		// Handle everything else (articles)
		// (apparently this is called before parents are displayed so ignore those calls)
		if (isset($parent->id))
		{
			return 'default';
		}

		return false;
	}

	/**
	 * Return the name of the field with the content item text
	 *
	 * During the display of content items (eg, articles, categories), the
	 * onContentPrepare (etc) callbacks are used to insert attachments lists.
	 * The second argument of the onContentPrepare() function is an object
	 * (usually $row) for the content item (eg, article).  This function will
	 * return the appropriate field for the text of the content item.  In some
	 * cases it is 'text', in others, 'introtext'.	Attachments plugins can
	 * override this function to provide the field name more intelligently.
	 *
	 * Note: returns null if the text field is unknown/not present.
	 *
	 * @param	&object	 &$row			 the content object (eg, article) being displayed
	 * @param	string	 $parent_entity	 the type of entity for this content item.
	 *
	 * @return string name of the text field of this content item object.
	 */
	protected function getTextFieldName(&$row, $parent_entity)
	{
		$view = JRequest::getCmd('view');
		$layout = JRequest::getCmd('layout');

		$text_field_name = parent::getTextFieldName($row, $parent_entity);

		// In the case of a blog, we know what text_field_name should be
		if (isset($row->introtext) AND ($layout == 'blog'))
		{
			$text_field_name = 'introtext';
		}

		// Featured also uses 'introtext'
		if (isset($row->introtext) AND ($view == 'featured'))
		{
			$text_field_name = 'introtext';
		}

		// Check for non-menu category view
		if (isset($row->introtext) AND	($view == 'category') AND
			(($parent_entity == 'default') OR ($parent_entity == 'article')))
		{
			$text_field_name = 'introtext';
		}

		if (version_compare(JVERSION, '3.4.0', 'ge') AND
			($view == 'category') AND ($layout == 'blog') AND ($parent_entity == 'article'))
		{
			$text_field_name = 'text';
		}

		return $text_field_name;
	}

	/**
	 * Return the URL that can be called to select a specific content item.
	 *
	 * @param	string	$parent_entity	the type of entity to select from
	 *
	 * @return the URL that can be called to select a specific content item
	 */
	public function getSelectEntityURL($parent_entity = 'default')
	{
		$parent_entity = $this->getCanonicalEntityId($parent_entity);

		switch ($parent_entity)
		{

			case 'category':
				return parent::getSelectEntityURL($parent_entity);
				break;

			default:
				return "index.php?option=com_content&amp;view=articles&amp;layout=modal&amp;tmpl=component&amp;function=jSelectParentArticle";
		}
	}

	/**
	 * Return an array of entity items (with id,title pairs for each item)
	 *
	 * @param	string	$parent_entity	the type of entity to search for
	 * @param	string	$filter			filter the results for matches for this filter string
	 *
	 * @return the array of entity id,title pairs
	 */
	public function getEntityItems($parent_entity = 'default', $filter = '')
	{
		$db = JFactory::getDBO();

		$parent_entity		= $this->getCanonicalEntityId($parent_entity);
		$parent_entity_name = JText::_('ATTACH_' . $parent_entity);

		// Note that article is handled separately
		if (JString::strtolower($parent_entity) != 'category')
		{
			$errmsg = JText::sprintf('ATTACH_ERROR_GETTING_LIST_OF_ENTITY_S_ITEMS', $parent_entity_name) . ' (ERR 400)';
			JError::raiseError(500, $errmsg);
		}

		$entity_table		= $this->entity_table[$parent_entity];
		$entity_title_field = $this->entity_title_field[$parent_entity];
		$entity_id_field	= $this->entity_id_field[$parent_entity];

		// Get the ordering information
		$app	   = JFactory::getApplication();
		$order	   = $app->getUserStateFromRequest('com_attachments.selectEntity.filter_order', 'filter_order', '', 'cmd');
		$order_Dir = $app->getUserStateFromRequest('com_attachments.selectEntity.filter_order_Dir', 'filter_order_Dir', '', 'word');

		// Get all the items
		$query = $db->getQuery(true);
		$query->select('*')->from('#__categories');

		// Filter
		if ($filter)
		{
			$filter = $db->quote('%' . $db->escape($filter, true) . '%', false);
			$query->where('title LIKE ' . $filter);
		}
		$query->where('extension=' . $db->quote('com_content'));

		// NOTE: Ignore any requested order since only ordering by lft makes the hierarchy work
		$query->order('lft');

		// Do the query
		$db->setQuery($query);
		$items = $db->loadObjectList();
		if ($db->getErrorNum())
		{
			$errmsg = JText::sprintf('ATTACH_ERROR_GETTING_LIST_OF_ENTITY_S_ITEMS', $parent_entity_name) . ' (ERR 401) <br/>' . $db->stderr();
			JError::raiseError(500, $errmsg);
		}

		if ($items == null)
		{
			return null;
		}

		// Set up the hierarchy indenting
		foreach ($items as &$item)
		{
			$repeat		 = ($item->level - 1 >= 0) ? $item->level - 1 : 0;
			$item->title = str_repeat('- ', $repeat) . $item->title;
		}

		return $items;
	}

	/**
	 * Return the ID of the creator/owner of the parent entity
	 *
	 * @param	int		$parent_id		the ID for the parent object
	 * @param	string	$parent_entity	the type of entity for this parent type
	 *
	 * @return creators id if found, 0 otherwise
	 */
	public function getParentCreatorId($parent_id, $parent_entity = 'default')
	{
		$parent_entity = $this->getCanonicalEntityId($parent_entity);

		$db	   = JFactory::getDBO();
		$query = $db->getQuery(true);

		$result = 0;

		// Return the right thing for each entity
		switch ($parent_entity)
		{

			case 'category':
				$query->select('created_user_id')->from('#__categories')->where('id = ' . (int) $parent_id);
				$db->setQuery($query, 0, 1);
				$result = $db->loadResult();
				if ($db->getErrorNum())
				{
					$errmsg = JText::_('ATTACH_ERROR_CHECKING_CATEGORY_PERMISSIONS') . ' (ERR 402)';
					JError::raiseError(500, $errmsg);
				}
				break;

			default: // Article
				$query->select('created_by')->from('#__content')->where('id = ' . (int) $parent_id);
				$db->setQuery($query, 0, 1);
				$result = $db->loadResult();
				if ($db->getErrorNum())
				{
					$errmsg = JText::_('ATTACH_ERROR_CHECKING_ARTICLE_PERMISSIONS') . ' (ERR 403)';
					JError::raiseError(500, $errmsg);
				}
		}

		if (is_numeric($result))
		{
			return (int) $result;
		}

		return 0;
	}

	/**
	 * Get a URL to view the content article
	 *
	 * @param	int		$parent_id		the ID for this parent object
	 * @param	string	$parent_entity	the type of parent element/entity
	 *
	 * @return a URL to view the entity (non-SEF form)
	 */
	public function getEntityViewURL($parent_id, $parent_entity = 'default')
	{
		$uri = JFactory::getURI();

		$base_url = $uri->root(true) . '/';

		// Return the right thing for each entity
		switch ($parent_entity)
		{

			case 'category':
				return $base_url . 'index.php?option=com_content&view=category&id=' . $parent_id;
				break;

			default:
				return $base_url . 'index.php?option=com_content&view=article&id=' . $parent_id;
		}
	}

	/**
	 * Get a URL to add an attachment to a specific entity
	 *
	 * @param	int		$parent_id		the ID for the parent entity object (null if the parent does not exist)
	 * @param	string	$parent_entity	the type of entity for this parent type
	 * @param	string	$from			where the call should return to
	 *
	 * @return the url to add a new attachments to the specified entity
	 */
	public function getEntityAddUrl($parent_id, $parent_entity = 'default', $from = 'closeme')
	{
		$app = JFactory::getApplication();

		$parent_entity = $this->getCanonicalEntityId($parent_entity);

		// Determine the task
		if ($app->isAdmin())
		{
			$task = 'attachment.add';
		}
		else
		{
			$task = 'upload';
		}

		// Handle article creation
		$url = "index.php?option=com_attachments&task=$task";
		if ($parent_id == null)
		{
			$url .= "&parent_id=$parent_id,new";
		}
		else
		{
			$url .= "&parent_id=$parent_id";
		}

		// Build the right URL for each entity
		switch ($parent_entity)
		{

			case 'category':
				$url .= "&parent_type=com_content.$parent_entity&from=$from";
				break;

			default:
				$url .= "&parent_type=com_content.article&from=$from";
		}

		return $url;
	}

	/**
	 * Check to see if a custom title applies to this parent
	 *
	 * Note: this function assumes that the parent_id's match
	 *
	 * @param	string	$parent_entity		   parent entity for the parent of the list
	 * @param	string	$rtitle_parent_entity  the entity of the candidate attachment list title (from params)
	 *
	 * @return true if the custom title should be used
	 */
	public function checkAttachmentsListTitle($parent_entity, $rtitle_parent_entity)
	{
		if ((($parent_entity == 'default') || ($parent_entity == 'article'))
			&& (($rtitle_parent_entity == 'default') || ($rtitle_parent_entity == 'article')))
		{
			return true;
		}

		if (($parent_entity == 'category') && ($parent_entity == $rtitle_parent_entity))
		{
			return true;
		}

		return false;
	}

	/**
	 * Is the parent new (based on the parent_id)
	 *
	 * @param	object	&$attachment  the attachment
	 *
	 * @return true if the parent is new (being created)
	 */
	public function newParent(&$attachment)
	{
		if ($attachment->parent_id != 0) {
			return false;
			}

		if ($attachment->parent_entity == 'article') {
			// Seems to be true for articles
			return true;  
			}

		if ($attachment->parent_entity == 'category') {
			// Assume this is true for category (but not sure)
			return true;  
			}

		return false;
	}

	/**
	 * Check to see if the parent is published
	 *
	 * @param	int		$parent_id		is the ID for this parent object
	 * @param	string	$parent_entity	the type of entity for this parent type
	 *
	 * @return true if the parent is published
	 */
	public function isParentPublished($parent_id, $parent_entity = 'default')
	{
		$db = JFactory::getDBO();

		$published = false;

		$parent_entity		= $this->getCanonicalEntityId($parent_entity);
		$parent_entity_name = JText::_('ATTACH_' . $parent_entity);

		// Return the right thing for each entity
		switch ($parent_entity)
		{

			case 'category':
				$entity_table = $this->entity_table[$parent_entity];
				$query		  = $db->getQuery(true);
				$query->select('published')->from("#__$entity_table")->where('id = ' . (int) $parent_id);
				$db->setQuery($query, 0, 1);
				$obj = $db->loadObject();
				if ($db->getErrorNum())
				{
					$errmsg = JText::sprintf('ATTACH_ERROR_INVALID_PARENT_S_ID_N', $parent_entity_name, $parent_id) . ' (ERR 404)';
					JError::raiseError(500, $errmsg);
				}
				if (is_object($obj))
				{
					$published = $obj->published == 1;
				}
				else
				{
					$published = false;
				}
				break;

			default:

				// Check for articles
				$query = $db->getQuery(true);
				$query->select('state, publish_up, publish_down')->from('#__content');
				$query->where('id = ' . (int) $parent_id);
				$db->setQuery($query, 0, 1);
				$article = $db->loadObject();
				if ($db->getErrorNum())
				{
					$errmsg = JText::sprintf('ATTACH_ERROR_INVALID_PARENT_S_ID_N', $parent_entity_name, $parent_id) . ' (ERR 405)';
					JError::raiseError(500, $errmsg);
				}
				else
				{
					$now	  = JFactory::getDate()->toUnix();
					$nullDate = JFactory::getDate($db->getNullDate())->toUnix();

					if ($article)
					{
						$publish_up	  = JFactory::getDate($article->publish_up)->toUnix();
						$publish_down = JFactory::getDate($article->publish_down)->toUnix();

						$published = (($article->state == 1) && ($now >= $publish_up) && (($publish_down == $nullDate) || ($now <= $publish_down)));
					}
					else
					{
						$published = false;
					}
				}
		}

		return $published;
	}

	/**
	 * Check to see if the parent is archived
	 *
	 * @param	int		$parent_id		is the ID for this parent object
	 * @param	string	$parent_entity	the type of entity for this parent type
	 *
	 * @return true if the parent is archived
	 */
	public function isParentArchived($parent_id, $parent_entity = 'default')
	{
		$archived = false;

		$parent_entity = $this->getCanonicalEntityId($parent_entity);

		// Return the right thing for each entity
		switch ($parent_entity)
		{

			case 'category':
				// You apparently cannot archive categories
				break;

			default:
				// Articles
				$db	   = JFactory::getDBO();
				$query = $db->getQuery(true);
				$query->select('state')->from('#__content')->where(' id = ' . (int) $parent_id);
				$db->setQuery($query, 0, 1);
				$article = $db->loadObject();
				if ($db->getErrorNum())
				{
					$parent_entity_name = JText::_('ATTACH_' . $parent_entity);
					$errmsg				= JText::sprintf('ATTACH_ERROR_INVALID_PARENT_S_ID_N', $parent_entity_name, $parent_id) . ' (ERR 406)';
					JError::raiseError(500, $errmsg);
				}
				else
				{
					if ($article)
					{
						$archived = $article->state == -1;
					}
					else
					{
						$archived = false;
					}
				}
		}

		return $archived;
	}

	/**
	 * Return a string of the where clause for filtering the the backend list of attachments
	 *
	 * @param	string	$parent_state	the state ('ALL', 'PUBLISHED', 'UNPUBLISHED', 'ARCHIVED', 'NONE')
	 * @param	string	$filter_entity	the entity filter ('ALL', 'ARTICLE', 'CATEGORY')
	 *
	 * @return an array of where clauses
	 */
	public function getParentPublishedFilter($parent_state, $filter_entity)
	{
		// If we want all attachments, do no filtering
		if ($parent_state == 'ALL')
		{
			return array();
		}

		$db = JFactory::getDBO();

		$where = Array();

		$filter_entity = JString::strtoupper($filter_entity);

		// NOTE: These WHERE clauses will be combined by OR

		if ($parent_state == 'PUBLISHED')
		{

			if (($filter_entity == 'ALL') || ($filter_entity == 'ARTICLE'))
			{
				$now	  = JFactory::getDate()->toSql();
				$nullDate = $db->getNullDate();
				$where[]  = "EXISTS (SELECT * FROM #__content AS c1 " .
					"WHERE (a.parent_entity = 'article' AND c1.id = a.parent_id AND c1.state=1 AND " .
					'(c1.publish_up = ' . $db->quote($nullDate) . ' OR c1.publish_up <= ' . $db->quote($now) . ') AND ' .
					'(c1.publish_down = ' . $db->quote($nullDate) . ' OR c1.publish_down >= ' . $db->quote($now) . ')))';
			}

			if (($filter_entity == 'ALL') || ($filter_entity == 'CATEGORY'))
			{
				$where[] = "EXISTS (SELECT * FROM #__categories AS c2 " .
					"WHERE (a.parent_entity = 'category' AND c2.id = a.parent_id AND c2.published=1))";
			}
		}
		elseif ($parent_state == 'UNPUBLISHED')
		{
			// These WHERE clauses will be combined by OR
			if (($filter_entity == 'ALL') || ($filter_entity == 'ARTICLE'))
			{
				$where[] = "EXISTS (SELECT * FROM #__content AS c1 " .
					"WHERE (a.parent_entity = 'article' AND c1.id = a.parent_id AND c1.state=0))";
				$where[] = "(a.parent_entity = 'article' AND NOT EXISTS (select * from #__content as c1 where c1.id = a.parent_id))";

				// ??? Add clauses here to get articles that are unpublished because of publish_up/publish_down
			}

			if (($filter_entity == 'ALL') || ($filter_entity == 'CATEGORY'))
			{
				$where[] = "EXISTS (SELECT * FROM #__categories AS c2 " .
					"WHERE (a.parent_entity = 'category' AND c2.id = a.parent_id AND c2.published=0))";
				$where[] = "(a.parent_entity = 'category' AND NOT EXISTS (select * from #__categories as c1 where c1.id = a.parent_id))";
			}
		}
		elseif ($parent_state == 'ARCHIVED')
		{
			// These WHERE clauses will be combined by OR
			if (($filter_entity == 'ALL') || ($filter_entity == 'ARTICLE'))
			{
				$where[] = "EXISTS (SELECT * FROM #__content AS c1 " .
					"WHERE (a.parent_entity = 'article' AND c1.id = a.parent_id AND c1.state=2))";
			}

			if (($filter_entity == 'ALL') || ($filter_entity == 'CATEGORY'))
			{
				$where[] = "EXISTS (SELECT * FROM #__categories AS c2 " .
					"WHERE (a.parent_entity = 'category' AND c2.id = a.parent_id AND c2.published=2))";
			}
		}
		elseif ($parent_state == 'TRASHED')
		{
			// These WHERE clauses will be combined by OR
			if (($filter_entity == 'ALL') || ($filter_entity == 'ARTICLE'))
			{
				$where[] = "EXISTS (SELECT * FROM #__content AS c1 " .
					"WHERE (a.parent_entity = 'article' AND c1.id = a.parent_id AND c1.state=-2))";
			}

			if (($filter_entity == 'ALL') || ($filter_entity == 'CATEGORY'))
			{
				$where[] = "EXISTS (SELECT * FROM #__categories AS c2 " .
					"WHERE (a.parent_entity = 'category' AND c2.id = a.parent_id AND c2.published=-2))";
			}
		}
		elseif ($parent_state == 'NONE')
		{
			// NOTE: The 'NONE' clauses will be combined with AND (with other tests for a.parent_id)
			$where[] = "(NOT EXISTS( SELECT * FROM #__content as c1 " .
				"WHERE a.parent_entity = 'article' AND c1.id = a.parent_id ))";

			$where[] = "(NOT EXISTS( SELECT * FROM #__categories as c2 " .
				"WHERE a.parent_entity = 'category' AND c2.id = a.parent_id ))";
		}
		else
		{
			$errmsg = JText::sprintf('ATTACH_ERROR_UNRECOGNIZED_PARENT_STATE_S', $parent_state) . ' (ERR 407)';
			JError::raiseError(500, $errmsg);
		}

		return $where;
	}

	/**
	 * May the parent be viewed by the user?
	 *
	 * @param	int		$parent_id		the ID for this parent object
	 * @param	string	$parent_entity	the type of entity for this parent type
	 * @param	object	$user_id		the user_id to check (optional, primarily for testing)
	 *
	 * @return true if the parent may be viewed by the user
	 */
	public function userMayViewParent($parent_id, $parent_entity = 'default', $user_id = null)
	{
		$parent_entity = $this->getCanonicalEntityId($parent_entity);

		// Return the right thing for each entity
		$table = null;
		switch ($parent_entity)
		{

			case 'category':
				$table = 'categories';
				break;

			default:
				// Article
				$table = 'content';
				break;
		}

		// Get the user's permitted access levels
		$user		 = JFactory::getUser($user_id);
		$user_levels = array_unique($user->getAuthorisedViewLevels());

		// See if the parent's access level is permitted for the user
		$db	   = JFactory::getDBO();
		$query = $db->getQuery(true);
		$query->select('id')->from("#__$table");
		$query->where('id = ' . (int) $parent_id . ' AND access in (' . implode(',', $user_levels) . ')');
		$db->setQuery($query, 0, 1);
		$obj = $db->loadObject();
		if ($db->getErrorNum())
		{
			$parent_entity_name = JText::_('ATTACH_' . $parent_entity);
			$errmsg				= JText::sprintf('ATTACH_ERROR_INVALID_PARENT_S_ID_N', $parent_entity_name, $parent_id) . ' (ERR 408)';
			JError::raiseError(500, $errmsg);
		}

		return !empty($obj);
	}

	/** Return true if the attachments should be hidden for this parent
	 *
	 * @param	&object	 &$parent		 the object for the parent that onPrepareContent gives
	 * @param	int		 $parent_id		 the ID of the parent the attachment is attached to
	 * @param	string	 $parent_entity	 the type of entity for this parent type
	 *
	 * @return true if the attachments should be hidden for this parent
	 */
	public function attachmentsHiddenForParent(&$parent, $parent_id, $parent_entity)
	{
		// Check for generic options
		if (parent::attachmentsHiddenForParent($parent, $parent_id, $parent_entity))
		{
			return true;
		}
		$pclass = get_class($parent);

		$parent_entity		= $this->getCanonicalEntityId($parent_entity);
		$parent_entity_name = JText::_('ATTACH_' . $parent_entity);

		// Make sure we have a valid parent ID
		if (!$parent_id && ($parent_entity == 'category'))
		{
			$parent_id = JRequest::getInt('id');
		}
		if ($parent_id !== 0)
		{
			// Note: parent_id of 0 may be allowed for categories, so don't abort
			if (($parent_id == null) || ($parent_id == '') || !is_numeric($parent_id))
			{
				$errmsg = JText::sprintf('ATTACH_ERROR_BAD_ENTITY_S_ID', $parent_entity_name) . ' (ERR 409)';
				JError::raiseError(500, $errmsg);
			}
		}

		// Check to see if it should be hidden with readmore
		$aparams = $this->attachmentsParams();
		$hide_with_readmore = $aparams->get('hide_with_readmore', false);
		if ($hide_with_readmore && isset($parent->readmore) && $parent->readmore)
		{
			return true;
		}

		// Get the options
		$all_but_article_views = $aparams->get('hide_except_article_views', false);

		// Make sure the parent is valid and get info about it
		$db = JFactory::getDBO();

		if ($parent_entity == 'category')
		{
			// Handle categories
			$always_show_category_attachments = $aparams->get('always_show_category_attachments', false);
			if ($always_show_category_attachments)
			{
				return false;
			}
			if ($all_but_article_views)
			{
				return true;
			}

			// Check to see whether the attachments should be hidden for this category
			$hide_attachments_for_categories = $aparams->get('hide_attachments_for_categories', Array());
			if (in_array($parent_id, $hide_attachments_for_categories))
			{
				return true;
			}

			// Make sure a category with this ID exists
			$query		 = $db->getQuery(true);
			$query->select('id')->from('#__categories');
			$query->where('id = ' . (int) $parent_id);
			$db->setQuery($query, 0, 1);
			$result = (int) $db->loadResult();
			if ((int) $parent_id != $result)
			{
				return true;
			}
		}

		else
		{
			// Handle articles
			if ($parent_id == 0)
			{
				return false;
			}

			// Make sure we have a valid article
			$query = $db->getQuery(true);
			$query->select('created_by, catid')->from('#__content')->where('id = ' . (int) $parent_id);
			$db->setQuery($query);
			$attachments = $db->loadObjectList();
			if ($db->getErrorNum() || (count($attachments) === false))
			{
				$errmsg = JText::sprintf('ATTACH_ERROR_INVALID_PARENT_S_ID_N', $parent_entity_name, $parent_id) . ' (ERR 410)';
				JError::raiseError(500, $errmsg);
			}

			// Honor all_but_article_view option
			$view = JRequest::getCmd('view');
			if ($all_but_article_views)
			{
				if ($view != 'article')
				{
					return true;
				}
			}

			// See if the options apply to this article
			$created_by = (int) $attachments[0]->created_by;
			$catid		= (int) $attachments[0]->catid;

			// First, check to see whether the attachments should be hidden for this parent
			$hide_attachments_for_categories = $aparams->get('hide_attachments_for_categories', Array());
			if (in_array($catid, $hide_attachments_for_categories))
			{
				return true;
			}
		}

		// The default is: attachments are not hidden
		return false;
	}

	/**
	 * Return true if the user may add an attachment to this parent
	 *
	 * (Note that all of the arguments are assumed to be valid; no sanity checking is done.
	 *	It is up to the caller to validate these objects before calling this function.)
	 *
	 * @param	int		$parent_id		the ID of the parent the attachment is attached to
	 * @param	string	$parent_entity	the type of entity for this parent type
	 * @param	bool	$new_parent		if true, the parent is being created and does not exist yet
	 * @param	object	$user_id		the user_id to check (optional, primarily for testing)
	 *
	 * @return true if this user may add attachments to this parent
	 */
	public function userMayAddAttachment($parent_id, $parent_entity, $new_parent = false, $user_id = null)
	{
		require_once JPATH_ADMINISTRATOR . '/components/com_attachments/permissions.php';

		$user = JFactory::getUser($user_id);

		// Handle each entity type
		$parent_entity = $this->getCanonicalEntityId($parent_entity);

		switch ($parent_entity)
		{

			case 'category':

				// First, determine if the user can edit this category
				if (!AttachmentsPermissions::userMayEditCategory($parent_id))
				{
					return false;
				}

				// Finally, see if the user has permissions to create attachments
				return $user->authorise('core.create', 'com_attachments');

				break;

			default:
				// For articles

				// First, determine if the user can edit this article
				if (!AttachmentsPermissions::userMayEditArticle($parent_id))
				{
					return false;
				}

				// Finally, see if the user has permissions to create attachments
				return $user->authorise('core.create', 'com_attachments');
		}

		// No one else is allowed to add attachments
		return false;
	}

	/**
	 * Return true if this user may edit (modify/update/delete) this attachment for this parent
	 *
	 * (Note that all of the arguments are assumed to be valid; no sanity checking is done.
	 *	It is up to the caller to validate these objects before calling this function.)
	 *
	 * @param	&record	 &$attachment  database reocrd for the attachment
	 * @param	object	 $user_id	   the user_id to check (optional, primarily for testing)
	 *
	 * @return true if this user may edit this attachment
	 */
	public function userMayEditAttachment(&$attachment, $user_id = null)
	{
		// If the user generally has permissions to edit all content, they
		// may edit this attachment (editor, publisher, admin, etc)
		$user = JFactory::getUser($user_id);
		if ($user->authorise('com_content', 'edit', 'content', 'all'))
		{
			return true;
		}

		require_once JPATH_ADMINISTRATOR . '/components/com_attachments/permissions.php';

		// Handle each entity type

		switch ($attachment->parent_entity)
		{

			case 'category':

				// ?? Deal with parents being created (parent_id == 0)

				// First, determine if the user can edit this category
				if (!AttachmentsPermissions::userMayEditCategory($attachment->parent_id))
				{
					return false;
				}

				// See if the user can edit any attachment
				if ($user->authorise('core.edit', 'com_attachments'))
				{
					return true;
				}

				// See if the user has permissions to edit their own attachments
				if ($user->authorise('core.edit.own', 'com_attachments') && ((int) $user->id == (int) $attachment->created_by))
				{
					return true;
				}

				// See if the user has permission to edit attachments on their own cateogory
				if ($user->authorise('attachments.edit.ownparent', 'com_attachments'))
				{
					$category_creator_id = $this->getParentCreatorId($attachment->parent_id, 'category');
					return (int) $user->id == (int) $category_creator_id;
				}

				break;

			default:
				// Articles

				// ?? Deal with parents being created (parent_id == 0)

				// First, determine if the user can edit this article
				if (!AttachmentsPermissions::userMayEditArticle($attachment->parent_id))
				{
					return false;
				}

				// See if the user can edit any attachment
				if ($user->authorise('core.edit', 'com_attachments'))
				{
					return true;
				}

				// See if the user has permissions to edit their own attachments
				if ($user->authorise('core.edit.own', 'com_attachments') && ((int) $user->id == (int) $attachment->created_by))
				{
					return true;
				}

				// See if the user has permission to edit attachments on their own article
				if ($user->authorise('attachments.edit.ownparent', 'com_attachments'))
				{
					$article_creator_id = $this->getParentCreatorId($attachment->parent_id, 'article');
					return (int) $user->id == (int) $article_creator_id;
				}
		}

		return false;
	}

	/**
	 * Return true if this user may delete this attachment for this parent
	 *
	 * (Note that all of the arguments are assumed to be valid; no sanity checking is done.
	 *	It is up to the caller to validate the arguments before calling this function.)
	 *
	 * @param	&record	 &$attachment  database record for the attachment
	 * @param	object	 $user_id	   the user_id to check (optional, primarily for testing)
	 *
	 * @return true if this user may delete this attachment
	 */
	public function userMayDeleteAttachment(&$attachment, $user_id = null)
	{
		// If the user generally has permissions to edit ALL content, they
		// may edit this attachment (editor, publisher, admin, etc)
		$user = JFactory::getUser($user_id);
		if ($user->authorise('com_content', 'edit', 'content', 'all'))
		{
			return true;
		}

		require_once JPATH_ADMINISTRATOR . '/components/com_attachments/permissions.php';

		// Handle each entity type

		switch ($attachment->parent_entity)
		{

			case 'category':

				// First, determine if the user can edit this category
				if (!AttachmentsPermissions::userMayEditCategory($attachment->parent_id))
				{
					return false;
				}

				// Ok if the user can delete any attachment
				if ($user->authorise('core.delete', 'com_attachments'))
				{
					return true;
				}

				// See if the user has edit.own and created it
				if ($user->authorise('attachments.delete.own', 'com_attachments') && ((int) $user->id == (int) $attachment->created_by))
				{
					return true;
				}

				// See if the user has permission to delete any attachments for categories they created
				if ($user->authorise('attachments.delete.ownparent', 'com_attachments'))
				{
					$category_creator_id = $this->getParentCreatorId($attachment->parent_id, 'category');
					return (int) $user->id == (int) $category_creator_id;
				}

				break;

			default: // Articles

				// ?? Deal with parents being created (parent_id == 0)

				// First, determine if the user can edit this article
				if (!AttachmentsPermissions::userMayEditArticle($attachment->parent_id))
				{
					return false;
				}

				// Ok if the user can delete any attachment
				if ($user->authorise('core.delete', 'com_attachments'))
				{
					return true;
				}

				// See if the user has permissions to delete their own attachments
				if ($user->authorise('attachments.delete.own', 'com_attachments') && ((int) $user->id == (int) $attachment->created_by))
				{
					return true;
				}

				// See if the user has permission to delete any attachments for articles they created
				if ($user->authorise('attachments.delete.ownparent', 'com_attachments'))
				{
					$article_creator_id = $this->getParentCreatorId($attachment->parent_id, 'article');
					return (int) $user->id == (int) $article_creator_id;
				}
		}

		return false;
	}

	/**
	 * Return true if this user may change the state of this attachment
	 *
	 * (Note that all of the arguments are assumed to be valid; no sanity checking is done.
	 *	It is up to the caller to validate the arguments before calling this function.)
	 *
	 * @param	int		$parent_id				the ID for the parent object
	 * @param	string	$parent_entity			the type of entity for this parent type
	 * @param	int		$attachment_creator_id	the ID of the creator of the attachment
	 * @param	object	$user_id				the user_id to check (optional, primarily for testing)
	 *
	 * @return true if this user may change the state of this attachment
	 */
	public function userMayChangeAttachmentState($parent_id, $parent_entity, $attachment_creator_id, $user_id = null)
	{
		// If the user generally has permissions to edit all content, they
		// may change this attachment state (editor, publisher, admin, etc)
		$user = JFactory::getUser($user_id);
		if ($user->authorise('com_content', 'edit', 'content', 'all'))
		{
			return true;
		}

		require_once JPATH_ADMINISTRATOR . '/components/com_attachments/permissions.php';

		// Handle each entity type

		switch ($parent_entity)
		{

			case 'category':

				// ?? Deal with parents being created (parent_id == 0)

				// First, determine if the user can edit this category
				if (!AttachmentsPermissions::userMayEditCategory($parent_id))
				{
					return false;
				}

				// See if the user can change the state of any attachment
				if ($user->authorise('core.edit.state', 'com_attachments'))
				{
					return true;
				}

				// See if the user has permissions to change the state of their own attachments
				if ($user->authorise('attachments.edit.state.own', 'com_attachments') && ((int) $user->id == (int) $attachment_creator_id))
				{
					return true;
				}

				// See if the user has permission to change the state of any attachments for categories they created
				if ($user->authorise('attachments.edit.state.ownparent', 'com_attachments'))
				{
					$category_creator_id = $this->getParentCreatorId($parent_id, 'category');
					return (int) $user->id == (int) $category_creator_id;
				}

				break;

			default:
				// Articles

				// ?? Deal with parents being created (parent_id == 0)

				// First, determine if the user can edit this article
				if (!AttachmentsPermissions::userMayEditArticle($parent_id))
				{
					return false;
				}

				// See if the user can change the state of any attachment
				if ($user->authorise('core.edit.state', 'com_attachments'))
				{
					return true;
				}

				// See if the user has permissions to change the state of their own attachments
				if ($user->authorise('attachments.edit.state.own', 'com_attachments') && ((int) $user->id == (int) $attachment_creator_id))
				{
					return true;
				}

				// See if the user has permission to edit the state of any attachments for articles they created
				if ($user->authorise('attachments.edit.state.ownparent', 'com_attachments'))
				{
					$article_creator_id = $this->getParentCreatorId($parent_id, 'article');
					return (int) $user->id == (int) $article_creator_id;
				}
		}

		return false;
	}

	/** Check to see if the user may access (see/download) the attachments
	 *
	 * @param	&record	 &$attachment  database record for the attachment
	 * @param	object	 $user_id	   the user_id to check (optional, primarily for testing)
	 *
	 * @return true if access is okay (false if not)
	 */
	public function userMayAccessAttachment(&$attachment, $user_id = null)
	{
		$user = JFactory::getUser($user_id);
		return in_array($attachment->access, $user->getAuthorisedViewLevels());
	}


	/** See if the attachments list should be displayed in its content description editor
	 *
	 * @param	string	$parent_entity	the type of entity for this parent type
	 * @param	string	$view			the view
	 * @param	string	$layout			the layout on the view
	 *
	 * @return	true if the attachments list should be added to the editor
	 */
	public function showAttachmentsInEditor($parent_entity, $view, $layout)
	{
		return ($layout =='edit') && (($view == 'form') || ($view == 'article') || ($view == 'category'));
	}

	/** Get the parent_id in the component content item editor
	 *	(the article or category editor)
	 *
	 * @param  string  $parent_entity  the type of entity for this parent type
	 *
	 * @return the parent ID, null if the content item is being created, and false if there is no match
	 *
	 * @since	 Attachments 3.2
	 */
	public function getParentIdInEditor($parent_entity, $view, $layout)
	{
		$app = JFactory::getApplication();
		if ($app->isAdmin()) {
			// The default works fine for the back end
			return parent::getParentIdInEditor($parent_entity, $view, $layout);
			}

		// Note categories cannot be created or edited from the frontend
		if ($parent_entity == 'category') {
			return false;
			}

		// Deal with articles (in frontend)
		$id = null;
		if (($view == 'article') OR ($view == 'form')) {
			$id = JRequest::getInt('a_id', $default=null);
			}
		else {
			$id = false;
			}

		// If we got one, convert it to an int
		if (is_numeric($id)) {
			$id = (int)$id;
			}

		return $id;
	}


	/** Known from keywords
	 *
	 * Attachment pop-dialogs will be closed using javascript if they are called from pages of these 'from' types
	 *
	 * @retrun array  An array of known tokens (strings)
	 */
	public function knownFroms()
	{
		return array_merge(parent::knownFroms(), Array('frontpage', 'featured', 'article', 'category', 'details'));
	}

}


/** Register this attachments type */
$apm = getAttachmentsPluginManager();
$apm->addParentType('com_content');
