<?php
/**
* @version	$Id: cat_dbitem.php 14241 2011-03-16 20:24:35Z alex $
* @package	In-Portal
* @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license      GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/

defined('FULL_PATH') or die('restricted access!');

class kCatDBItem extends kDBItem {

	/**
	 * Category path, needed for import
	 *
	 * @var Array
	 */
	var $CategoryPath = Array();

	/**
	 * Use automatic filename generation
	 *
	 * @var bool
	 */
	var $useFilenames = true;

	/**
	 * Use pending editing abilities during item (delegated by permissions)
	 *
	 * @var bool
	 */
	var $usePendingEditing = false;

	function Clear($new_id = null)
	{
		parent::Clear($new_id);
		$this->CategoryPath = Array();
	}

	/**
	 * Set's prefix and special
	 *
	 * @param string $prefix
	 * @param string $special
	 * @access public
	 */
	function Init($prefix, $special, $event_params = null)
	{
		parent::Init($prefix, $special, $event_params);
		$this->usePendingEditing = $this->Application->getUnitOption($this->Prefix, 'UsePendingEditing');
	}

	function Create($force_id = false, $system_create = false)
	{
		// TODO: move to OnBeforeItemCreate method
		if ($this->useFilenames) {
			$this->checkFilename();
			$this->generateFilename();
		}

		$ret = parent::Create($force_id, $system_create);

		if ($ret) {
			// TODO: move to OnAfterItemCreate method
			$this->assignPrimaryCategory();
		}
		return $ret;
	}

	/**
	 * Assigns primary category for the item
	 *
	 * @access public
	 */
	function assignPrimaryCategory()
	{
		$primary_category = $this->GetDBField('CategoryId') > 0 ? $this->GetDBField('CategoryId') : $this->Application->GetVar('m_cat_id');
		$this->assignToCategory($primary_category, true);
		$this->SetDBField('CategoryId', $primary_category); // set primary category in item object
	}

	function Update($id=null, $system_update=false)
	{
		$this->VirtualFields['ResourceId'] = Array();

		if ($this->GetChangedFields()) {
			$now = adodb_mktime();
			$this->SetDBField('Modified_date', $now);
			$this->SetDBField('Modified_time', $now);
			$this->SetDBField('ModifiedById', $this->Application->RecallVar('user_id'));
		}

		if ($this->useFilenames) {
			$this->checkFilename();
			$this->generateFilename();
		}

		$ret = parent::Update($id, $system_update);

		if ($ret) {
			$filename = $this->useFilenames ? $this->GetDBField('Filename') : '';
			$sql = 'UPDATE '.$this->CategoryItemsTable().'
					SET Filename = '.$this->Conn->qstr($filename).'
					WHERE ItemResourceId = '.$this->GetDBField('ResourceId');
			$this->Conn->Query($sql);
		}

		unset($this->VirtualFields['ResourceId']);
		return $ret;
	}

	/**
	 * Returns CategoryItems table based on current item mode (temp/live)
	 *
	 * @return string
	 */
	function CategoryItemsTable()
	{
		$table = TABLE_PREFIX.'CategoryItems';
		if ($this->Application->IsTempTable($this->TableName)) {
			$table = $this->Application->GetTempName($table, 'prefix:'.$this->Prefix);
		}

		return $table;
	}


	function checkFilename()
	{
		if( !$this->GetDBField('AutomaticFilename') )
		{
			$filename = $this->GetDBField('Filename');
			$this->SetDBField('Filename', $this->stripDisallowed($filename) );
		}
	}

	function Copy($cat_id=null)
	{
		if (!isset($cat_id)) $cat_id = $this->Application->GetVar('m_cat_id');
		$this->NameCopy($cat_id);
		return $this->Create($cat_id);
	}

	function NameCopy($master=null, $foreign_key=null)
	{
		$title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField');
		if (!$title_field) return;

		$new_name = $this->GetDBField($title_field);
		$cat_id = (int)$this->Application->GetVar('m_cat_id');
		$original_checked = false;
		do {
			if ( preg_match('/Copy ([0-9]*) *of (.*)/', $new_name, $regs) ) {
				$new_name = 'Copy '.( (int)$regs[1] + 1 ).' of '.$regs[2];
			}
			elseif ($original_checked) {
				$new_name = 'Copy of '.$new_name;
			}
			$query = 'SELECT '.$title_field.' FROM '.$this->TableName.'
								LEFT JOIN '.TABLE_PREFIX.'CategoryItems ON
								('.TABLE_PREFIX.'CategoryItems.ItemResourceId = '.$this->TableName.'.ResourceId)
								WHERE ('.TABLE_PREFIX.'CategoryItems.CategoryId = '.$cat_id.') AND '.
								$title_field.' = '.$this->Conn->qstr($new_name);
			$res = $this->Conn->GetOne($query);
			$original_checked = true;
		} while ($res !== false);
		$this->SetDBField($title_field, $new_name);

		// this is needed, because Create will create items in its own CategoryId (if it's set),
		// but we need to create it in target Paste category @see{kCatDBItem::Create} and its primary_category detection
		$this->SetDBField('CategoryId', $cat_id);
	}

	/**
	 * Changes item primary category to given/current category
	 *
	 * @param int $category_id
	 */
	function MoveToCat($category_id = null)
	{
//		$this->NameCopy();
		if (!isset($category_id)) {
			$category_id = $this->Application->GetVar('m_cat_id');
		}

		$table_name = TABLE_PREFIX . 'CategoryItems';
		if ($this->IsTempTable()) {
			$table_name = $this->Application->GetTempName($table_name, 'prefix:' . $this->Prefix);
		}

		// check if the item already exists in destination category
		$sql = 'SELECT PrimaryCat
				FROM ' . $table_name . '
				WHERE (CategoryId = ' . (int)$category_id . ') AND (ItemResourceId = ' . $this->GetDBField('ResourceId') . ')';
		$is_primary = $this->Conn->GetOne($sql);

		// if it's not found is_primary will be FALSE, if it's found but not primary it will be int 0
		$exists = $is_primary !== false;

		if ($exists) {
			// if the item already exists in destination category
			if ($is_primary) {
				// do nothing when we paste to primary
				return ;
			}

			// if it's not primary - delete it from destination category, as we will move it from current primary below
			$sql = 'DELETE FROM ' . $table_name . '
					WHERE (CategoryId = ' . (int)$category_id . ') AND (ItemResourceId = ' . $this->GetDBField('ResourceId') . ')';
			$this->Conn->Query($sql);
		}

		// change category id in existing primary category record
		$sql = 'UPDATE ' . $table_name . '
				SET CategoryId = ' . (int)$category_id . '
				WHERE (ItemResourceId = ' . $this->GetDBField('ResourceId') . ') AND (PrimaryCat = 1)';
		$this->Conn->Query($sql);

		$this->Update();
	}

	/**
	 * When item is deleted, then also delete it from all categories
	 *
	 * @param int $id
	 * @return bool
	 */
	function Delete($id = null)
	{
		if( isset($id) ) {
			$this->setID($id);
		}

		$this->Load($this->GetID());

		$ret = parent::Delete();

		if ($ret) {
			// TODO: move to OnAfterItemDelete method
			$query = '	DELETE FROM ' . $this->CategoryItemsTable() . '
						WHERE ItemResourceId = ' . $this->GetDBField('ResourceId');
			$this->Conn->Query($query);
		}

		return $ret;
	}

	/**
	 * Deletes item from categories
	 *
	 * @param Array $delete_category_ids
	 * @author Alex
	 */
	function DeleteFromCategories($delete_category_ids)
	{
		$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField'); // because item was loaded before by ResourceId

		$ci_table = $this->Application->getUnitOption($this->Prefix.'-ci', 'TableName');
		$resource_id = $this->GetDBField('ResourceId');

		$item_cats_sql = 'SELECT CategoryId FROM %s WHERE ItemResourceId = %s';
		$delete_category_items_sql = 'DELETE FROM %s WHERE ItemResourceId = %s AND CategoryId IN (%s)';

		$category_ids = $this->Conn->GetCol( sprintf($item_cats_sql, $ci_table, $resource_id) );
		$cats_left = array_diff($category_ids, $delete_category_ids);
		if(!$cats_left)
		{
			$sql = 'SELECT %s FROM %s WHERE ResourceId = %s';
			$ids = $this->Conn->GetCol( sprintf($sql, $id_field, $this->TableName, $resource_id) );

			$temp =& $this->Application->recallObject($this->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
			$temp->DeleteItems($this->Prefix, $this->Special, $ids);
		}
		else
		{
			$this->Conn->Query( sprintf($delete_category_items_sql, $ci_table, $resource_id, implode(',', $delete_category_ids) ) );

			$sql = 'SELECT CategoryId FROM %s WHERE PrimaryCat = 1 AND ItemResourceId = %s';
			$primary_cat_id = $this->Conn->GetCol( sprintf($sql, $ci_table, $resource_id) );
			if( count($primary_cat_id) == 0 )
			{
				$sql = 'UPDATE %s SET PrimaryCat = 1 WHERE (CategoryId = %s) AND (ItemResourceId = %s)';
				$this->Conn->Query( sprintf($sql, $ci_table, reset($cats_left), $resource_id ) );
			}
		}
	}

	/**
	 * replace not allowed symbols with "_" chars + remove duplicate "_" chars in result
	 *
	 * @param string $string
	 * @return string
	 */
	function stripDisallowed($filename)
	{
		$filenames_helper =& $this->Application->recallObject('FilenamesHelper');
		$table = $this->mode == 't' ? $this->Application->GetTempName(TABLE_PREFIX.'CategoryItems', 'prefix:'.$this->Prefix) : TABLE_PREFIX.'CategoryItems';

		return $filenames_helper->stripDisallowed($table, 'ItemResourceId', $this->GetDBField('ResourceId'), $filename);
	}

	/* commented out because it's called only from stripDisallowed body, which is moved to helper

	function checkAutoFilename($filename)
	{
		$filenames_helper =& $this->Application->recallObject('FilenamesHelper');
		return $filenames_helper->checkAutoFilename($this->TableName, $this->IDField, $this->GetID(), $filename);
	}*/

	/**
	 * Generate item's filename based on it's title field value
	 *
	 * @return string
	 */
	function generateFilename()
	{
		if ( !$this->GetDBField('AutomaticFilename') && $this->GetDBField('Filename') ) return false;

		$title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField');
		if (preg_match('/l([\d]+)_(.*)/', $title_field, $regs)) {
			// if title field is multilingual, then use it's name from primary language
			$title_field = 'l'.$this->Application->GetDefaultLanguageId().'_'.$regs[2];
		}
		$name = $this->stripDisallowed( $this->GetDBField($title_field) );

		if ( $name != $this->GetDBField('Filename') ) $this->SetDBField('Filename', $name);
	}

	/**
	 * Check if value is set for required field
	 *
	 * @param string $field field name
	 * @param Array $params field options from config
	 * @return bool
	 * @access private
	 */
	function ValidateRequired($field, $params)
	{
		$res = true;
		if (getArrayValue($params, 'required')) {
			$res = ( (string) $this->FieldValues[$field] != '');
		}
		if (!$res) {
			$this->SetError($field, 'required');
		}
		return $res;
	}

	/**
	 * Adds item to other category
	 *
	 * @param int $category_id
	 * @param bool $is_primary
	 */
	function assignToCategory($category_id, $is_primary = false)
	{
		$table = $this->CategoryItemsTable();
		$key_clause = '(ItemResourceId = '.$this->GetDBField('ResourceId').')';

		// get all cateories, where item is in
		$sql = 'SELECT PrimaryCat, CategoryId FROM '.$table.' WHERE '.$key_clause;
		$item_categories = $this->Conn->GetCol($sql, 'CategoryId');
		if (!$item_categories) {
			$item_categories = Array();
			$primary_found = false;
		}

		// find primary category
		foreach ($item_categories as $item_category_id => $primary_found) {
			if ($primary_found) {
				break;
			}
		}

		if ($primary_found && ($item_category_id == $category_id) && !$is_primary) {
			// want to make primary category as non-primary :(
			return true;
		}
		else if (!$primary_found) {
			$is_primary = true;
		}

		if ($is_primary && $item_categories) {
			// reset primary mark from all other categories
			$sql = 'UPDATE '.$table.' SET PrimaryCat = 0 WHERE '.$key_clause;
			$this->Conn->Query($sql);
		}

		// UPDATE & INSERT instead of REPLACE because CategoryItems table has no primary key defined in database
		if (isset($item_categories[$category_id])) {
			$sql = 'UPDATE '.$table.' SET PrimaryCat = '.($is_primary ? 1 : 0).' WHERE '.$key_clause.' AND (CategoryId = '.$category_id.')';
			$this->Conn->Query($sql);
		}
		else {
			$fields_hash = 	Array(
				'CategoryId'		=>	$category_id,
				'ItemResourceId'	=>	$this->GetField('ResourceId'),
				'PrimaryCat'		=>	$is_primary ? 1 : 0,
				'ItemPrefix'		=>	$this->Prefix,
				'Filename'			=>	$this->useFilenames ? $this->GetDBField('Filename') : '', // because some prefixes does not use filenames,
			);
			$this->Conn->doInsert($fields_hash, $table);
		}
		// to ensure filename update after adding to another category
		// this is critical since there may be an item with same filename in newly added category!
		$this->Update();
	}

	/**
	 * Removes item from category specified
	 *
	 * @param int $category_id
	 */
	function removeFromCategory($category_id)
	{
		$sql = 'DELETE FROM '.TABLE_PREFIX.'CategoryItems WHERE (CategoryId = %s) AND (ItemResourceId = %s)';
		$this->Conn->Query( sprintf($sql, $category_id, $this->GetDBField('ResourceId')) );
	}

	/**
	 * Returns list of columns, that could exist in imported file
	 *
	 * @return Array
	 */
	function getPossibleExportColumns()
	{
		static $columns = null;
		if (!is_array($columns)) {
			$columns = array_merge($this->Fields['AvailableColumns']['options'], $this->Fields['ExportColumns']['options']);
		}
		return $columns;
	}

	/**
	 * Returns item's primary image data
	 *
	 * @return Array
	 */
	function getPrimaryImageData()
	{
		$sql = 'SELECT *
				FROM '.TABLE_PREFIX.'Images
				WHERE (ResourceId = '.$this->GetDBField('ResourceId').') AND (DefaultImg = 1)';
		$image_data = $this->Conn->GetRow($sql);
		if (!$image_data) {
			// 2. no primary image, then get image with name "main"
			$sql = 'SELECT *
				FROM '.TABLE_PREFIX.'Images
				WHERE (ResourceId = '.$this->GetDBField('ResourceId').') AND (Name = "main")';
			$image_data = $this->Conn->GetRow($sql);
		}
		return $image_data;
	}

	function ChangeStatus($new_status, $pending_editing = false)
	{
		$status_field = array_shift( $this->Application->getUnitOption($this->Prefix,'StatusField') );
		if ($new_status != $this->GetDBField($status_field)) {
			// status was changed
			$this->sendEmailEvents($new_status, $pending_editing);
		}
		$this->SetDBField($status_field, $new_status);

		return $this->Update();
	}

	function sendEmailEvents($new_status, $pending_editing = false)
	{
		$owner_field = $this->Application->getUnitOption($this->Prefix, 'OwnerField');
		if (!$owner_field) {
			$owner_field = 'CreatedById';
		}

		$event_name = $this->Application->getUnitOption($this->Prefix, 'PermItemPrefix');
		if ($pending_editing) {
			$event_name .= '.MODIFY';
		}

		$event_name .= $new_status == STATUS_ACTIVE ? '.APPROVE' : '.DENY';
		$this->Application->EmailEventUser($event_name, $this->GetDBField($owner_field));
	}

	/**
	 * Approves changes made to category item
	 *
	 * @return bool
	 */
	function ApproveChanges()
	{
		$original_id = $this->GetDBField('OrgId');

		if (!($this->usePendingEditing && $original_id)) {
			// non-pending copy of original link
			return $this->ChangeStatus(STATUS_ACTIVE);
		}

		if ($this->raiseEvent('OnBeforeDeleteOriginal', null, Array('original_id' => $original_id))) {
			// delete original item, because changes made in pending copy (this item) got to be approved in this method
			$temp_handler =& $this->Application->recallObject($this->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
			$temp_handler->DeleteItems($this->Prefix, $this->Special, Array($original_id));

			$this->SetDBField('OrgId', 0);
			return $this->ChangeStatus(STATUS_ACTIVE, true);
		}

		return false;
	}

	/**
	 * Decline changes made to category item
	 *
	 * @return bool
	 */
	function DeclineChanges()
	{
		$original_id = $this->GetDBField('OrgId');

		if (!($this->usePendingEditing && $original_id)) {
			// non-pending copy of original link
			return $this->ChangeStatus(STATUS_DISABLED);
		}

		// delete this item, because changes made in pending copy (this item) will be declined in this method
		$temp_handler =& $this->Application->recallObject($this->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
		$temp_handler->DeleteItems($this->Prefix, $this->Special, Array($this->GetID()));

		$this->sendEmailEvents(STATUS_DISABLED, true);
		// original item is not changed here, because it is already enabled (thrus pending copy is visible to item's owner or admin with permission)
		return true;
	}

	function RegisterHit()
	{
		$already_viewed = $this->Application->RecallVar($this->getPrefixSpecial().'_already_viewed');
		$already_viewed = $already_viewed ? unserialize($already_viewed) : Array ();

		$id = $this->GetID();
		if (!in_array($id, $already_viewed)) {
			$property_map = $this->Application->getUnitOption($this->Prefix, 'ItemPropertyMappings');
			if (!$property_map) {
				return ;
			}
			$hits_field = $property_map['ClickField'];
			$new_hits = $this->GetDBField($hits_field) + 1;

			$sql = 'SELECT MAX('.$hits_field.')
					FROM '.$this->TableName.'
					WHERE FLOOR('.$hits_field.') = '.$new_hits;
			$max_hits = $this->Conn->GetOne($sql);
			if ($max_hits) {
				$new_hits = $max_hits + 0.000001;
			}

			$fields_hash = Array (
				$hits_field => $new_hits,
			);
			$this->Conn->doUpdate($fields_hash, $this->TableName, $this->IDField.' = '.$id);

			array_push($already_viewed, $id);
			$this->Application->StoreVar($this->getPrefixSpecial().'_already_viewed', serialize($already_viewed));
		}
	}

	/**
	* Returns part of SQL WHERE clause identifing the record, ex. id = 25
	*
	* @access public
	* @param string $method Child class may want to know who called GetKeyClause, Load(), Update(), Delete() send its names as method
	* @param Array $keys_hash alternative, then item id, keys hash to load item by
	* @return void
	* @see kDBItem::Load()
	* @see kDBItem::Update()
	* @see kDBItem::Delete()
	*/
	function GetKeyClause($method = null, $keys_hash = null)
	{
		if ($method == 'load') {
			// for item with many categories makes primary to load
			$ci_table = TABLE_PREFIX . 'CategoryItems';

			if ($this->IsTempTable()) {
				$ci_table = $this->Application->GetTempName($ci_table, 'prefix:' . $this->Prefix);
			}

			$primary_category_clause = Array ('`' . $ci_table . '`.`PrimaryCat`' => 1);

			if (!isset($keys_hash)) {
				$keys_hash = Array ($this->IDField => $this->ID);
			}

			// merge primary category clause in any case to be sure, that
			// CategoryId field will always contain primary category of item
			$keys_hash = array_merge($keys_hash, $primary_category_clause);
		}

		return parent::GetKeyClause($method, $keys_hash);
	}

}