<?php
/**
* @version	$Id: phrases_cache.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 PhrasesCache extends kBase {

	/**
	* Connection to database
	*
	* @var kDBConnection
	* @access public
	*/
	var $Conn;

	var $Phrases = Array();
	var $Ids = Array();
	var $OriginalIds = Array(); //for comparing cache

	var $LanguageId = null;

	/**
	 * Administrator's language, when visiting site (from frame)
	 *
	 * @var int
	 */
	var $AdminLanguageId = null;

	var $fromTag = false;

	/**
	 * Allows to edit existing phrases
	 *
	 * @var bool
	 */
	var $_editExisting = false;

	/**
	 * Allows to edit missing phrases
	 *
	 * @var bool
	 */
	var $_editMissing = false;

	/**
	 * Template, used for phrase adding/editing
	 *
	 * @var string
	 */
	var $_phraseEditTemplate = 'languages/phrase_edit';

	/**
	 * Use simplified form for phrase editing
	 *
	 * @var bool
	 */
	var $_simpleEditingMode = false;

	/**
	 * HTML tag used to translate phrases
	 *
	 * @var string
	 */
	var $_translateHtmlTag = 'a';

	/**
	 * Phrases, that are in cache, but are not in database
	 *
	 * @var Array
	 */
	var $_missingPhrases = Array ();

	/**
	 * Mask for editing link
	 *
	 * @var string
	 */
	var $_editLinkMask = '';

	/**
	 * Escape phrase name, before placing it in javascript translation link
	 * @var bool
	 */
	var $_escapePhraseName = true;

	function PhrasesCache()
	{
		parent::kBase();
		$this->Conn =& $this->Application->GetADODBConnection();
	}

	/**
	 * Sets phrase editing mode, that corresponds current editing mode
	 *
	 */
	function setPhraseEditing()
	{
		if (!$this->Application->isAdmin && (EDITING_MODE == EDITING_MODE_CONTENT)) {
			// front-end viewed in content mode
			$this->_editExisting = true;
			$this->_editMissing = true;
			$this->_simpleEditingMode = true;
			$this->_translateHtmlTag = 'span';
		}

		$this->_editLinkMask = 'javascript:translate_phrase(\'#LABEL#\', \'' . $this->_phraseEditTemplate . '\', {event: \'OnPreparePhrase\', simple_mode: ' . ($this->_simpleEditingMode ? 'true' : 'false') . '});';

		if (defined('DEBUG_MODE') && DEBUG_MODE && !$this->Application->GetVar('admin')) {
			// admin and front-end while not viewed using content mode (via admin)
			$this->_editMissing = defined('DBG_PHRASES') && DBG_PHRASES;

			if (!$this->Application->isAdmin) {
				$this->_phraseEditTemplate = 'phrases_edit';

				$url_params = Array (
					'm_opener' => 'd',
					'phrases_label' => '#LABEL#',
					'phrases_event' => 'OnPreparePhrase',
					'pass' => 'm,phrases'
				);

				$this->_escapePhraseName = false;
				$this->_editLinkMask = $this->Application->HREF($this->_phraseEditTemplate, '', $url_params);
			}
		}
	}

	function Init($prefix, $special = '')
	{
		if (constOn('IS_INSTALL')) {
			$this->LanguageId = 1;
		}
		else {
			if ($this->Application->isAdmin) {
				$this->LanguageId = $this->Application->Session->GetField('Language');
			}
			else {
				$this->LanguageId = $this->Application->GetVar('m_lang');

				if ($this->Application->GetVar('admin')) {
					$admin_session =& $this->Application->recallObject('Session.admin');
					/* @var $admin_session Session */

					$this->AdminLanguageId = $admin_session->GetField('Language');
				}
			}
		}

		if (isset($this->Application->Caches['PhraseList'])) {
			$this->LoadPhrases( $this->Application->Caches['PhraseList'] );
		}
	}

	function GetCachedIds()
	{
		$cache_key = md5($this->Application->GetVar('t') . $this->Application->GetVar('m_theme') . $this->Application->GetVar('m_lang'));

		$sql = 'SELECT PhraseList, ConfigVariables
				FROM ' . TABLE_PREFIX . 'PhraseCache
				WHERE Template = ' . $this->Conn->qstr($cache_key);
		$res = $this->Conn->GetRow($sql);

		if ($res && $res['ConfigVariables']) {
			$this->Application->OriginalConfigCacheIds =  explode(',', $res['ConfigVariables']);
			$this->Application->ConfigCacheIds = $this->Application->OriginalConfigCacheIds;
		}

		return ($res === false) ? Array() : explode(',', $res['PhraseList']);
	}

	function LoadPhrases($ids)
	{
		if ( !is_array($ids) || !implode('', $ids) ) {
			return;
		}

		$sql = 'SELECT l' . $this->LanguageId . '_Translation, PhraseKey
				FROM ' . TABLE_PREFIX . 'Phrase
				WHERE PhraseId IN (' . implode(',', $ids) . ') AND l' . $this->LanguageId . '_Translation IS NOT NULL';
		$this->Phrases = $this->Conn->GetCol($sql, 'PhraseKey');

		/*foreach($phrases as $phrase => $tanslation)
		{
			$this->AddCachedPhrase(mb_strtoupper($phrase), $tanslation);
		}*/

		$this->Ids = $ids;
		$this->OriginalIds = $ids;
	}

	function AddCachedPhrase($label, $value, $allow_editing = true)
	{
		// uppercase phrase name for cases, when this method is called outside this class
		$cache_key = ($allow_editing ? '' : 'NE:') . mb_strtoupper($label);

		$this->Phrases[$cache_key] = $value;
	}

	function NeedsCacheUpdate()
	{
		return is_array($this->Ids) && count($this->Ids) > 0 && $this->Ids != $this->OriginalIds;
	}

	/**
	 * Copy from Application->UpdateCache method
	 *
	 * @deprecated
	 */
	function UpdateCache()
	{
		$update = false;
		//something changed
		$update = $update || (is_array($this->Ids) && count($this->Ids) > 0 && $this->Ids != $this->OriginalIds);
		$update = $update || (count($this->Application->ConfigCacheIds) && $this->Application->ConfigCacheIds != $this->Application->OriginalConfigCacheIds);
		if ($update) {
			$query = sprintf("REPLACE %s (PhraseList, CacheDate, Template, ConfigVariables)
												VALUES (%s, %s, %s, %s)",
												TABLE_PREFIX.'PhraseCache',
												$this->Conn->Qstr(join(',', $this->Ids)),
												adodb_mktime(),
												$this->Conn->Qstr(md5($this->Application->GetVar('t').$this->Application->GetVar('m_theme').$this->Application->GetVar('m_lang'))),
												$this->Conn->qstr(implode(',', array_unique($this->Application->ConfigCacheIds))));
			$this->Conn->Query($query);
		}
	}

	function GetPhrase($label, $allow_editing = true, $use_admin = false)
	{
		if (!isset($this->LanguageId)) {
			//actually possible when custom field contains references to language labels and its being rebuilt in OnAfterConfigRead
			//which is triggered by Sections rebuild, which in turn read all the configs and all of that happens BEFORE seeting the language...
			return 'impossible case';
		}

		// cut exclamation marks - depricated form of passing phrase name from templates
		$label = preg_replace('/^!(.*)!$/', '\\1', $label);

		if (strlen($label) == 0) {
			return '';
		}

		$original_label = $this->_escapePhraseName ? addslashes($label) : $label;
		$label = mb_strtoupper($label);

		$cache_key = ($allow_editing ? '' : 'NE:') . $label;

		if (array_key_exists($cache_key, $this->Phrases)) {
			$translated_label = $this->Phrases[$cache_key];

			if ($this->_editExisting && $allow_editing && !array_key_exists($label, $this->_missingPhrases)) {
				// option to change translation for Labels
				$edit_url = 'javascript:translate_phrase(\'' . $original_label . '\', \'' . $this->_phraseEditTemplate . '\', {event: \'OnPreparePhrase\', simple_mode: ' . ($this->_simpleEditingMode ? 'true' : 'false') . '});';
				$translated_label = '<' . $this->_translateHtmlTag . ' href="' . $edit_url . '" name="cms-translate-phrase" title="Edit translation">' . $translated_label . '</' . $this->_translateHtmlTag . '>';

				if ($this->fromTag) {
					$translated_label = $this->escapeTagReserved($translated_label);
				}
			}

			return $translated_label;
		}

		$this->LoadPhraseByLabel($label, $original_label, $allow_editing, $use_admin);

		return $this->GetPhrase($label, $allow_editing);
	}

	function LoadPhraseByLabel($label, $original_label, $allow_editing = true, $use_admin = false)
	{
		if (!$allow_editing && !$use_admin && !array_key_exists($label, $this->_missingPhrases) && array_key_exists($label, $this->Phrases)) {
			// label is aready translated, but it's version without on the fly translation code is requested
			$this->Phrases['NE:' . $label] = $this->Phrases[$label];

			return true;
		}

		$language_id = $use_admin ? $this->AdminLanguageId : $this->LanguageId;

		$sql = 'SELECT PhraseId, l' . $language_id . '_Translation
				FROM ' . TABLE_PREFIX . 'Phrase
				WHERE (PhraseKey = ' . $this->Conn->qstr($label) . ') AND (l' . $language_id . '_Translation IS NOT NULL)';
		$res = $this->Conn->GetRow($sql);

		if ($res === false || count($res) == 0) {
			$translation = '!' . $label . '!';

			if ($this->_editMissing && $allow_editing) {
				$edit_url = str_replace('#LABEL#', $original_label, $this->_editLinkMask);
				$translation = '<' . $this->_translateHtmlTag . ' href="' . $edit_url . '" name="cms-translate-phrase" title="Translate">!' . $label . '!</' . $this->_translateHtmlTag . '>';

				if ($this->fromTag) {
					$translation = $this->escapeTagReserved($translation);
				}

				$this->_missingPhrases[$label] = true; // add as key for faster accessing
			}

			// add it as already cached, as long as we dont need to cache not found phrase
			$this->AddCachedPhrase($label, $translation, $allow_editing);

			return false;
		}

		$cache_key = ($allow_editing ? '' : 'NE:') . $label;
		$this->Phrases[$cache_key] = $res['l' . $language_id . '_Translation'];

		array_push($this->Ids, $res['PhraseId']);
		$this->Ids = array_unique($this->Ids); // just to make sure

		return true;
	}

	/**
	 * Sort params by name and then by length
	 *
	 * @param string $a
	 * @param string $b
	 * @return int
	 * @access private
	 */
	function CmpParams($a, $b)
	{
		$a_len = mb_strlen($a);
		$b_len = mb_strlen($b);
		if ($a_len == $b_len) return 0;
		return $a_len > $b_len ? -1 : 1;
	}

	/**
	 * Replace language tags in exclamation marks found in text
	 *
	 * @param string $text
	 * @param bool $force_escape force escaping, not escaping of resulting string
	 * @return string
	 * @access public
	 */
	function ReplaceLanguageTags($text, $forse_escaping = null)
	{
		$this->fromTag = true;
		if( isset($forse_escaping) ) {
			$this->fromTag = $forse_escaping;
		}

		preg_match_all("(!(la|lu)[^!]+!)", $text, $res, PREG_PATTERN_ORDER);
		$language_tags = $res[0];
		uasort($language_tags, Array(&$this, 'CmpParams'));

		$i = 0;
		$values = Array();

		foreach ($language_tags as $label) {
			array_push($values, $this->GetPhrase($label) );
			//array_push($values, $this->Application->Phrase($label) );
			$language_tags[$i] = '/' . $language_tags[$i] . '/';
			$i++;
		}

		$this->fromTag = false;

		return preg_replace($language_tags, $values, $text);
	}

	/**
	 * Escape chars in phrase translation, that could harm parser to process tag
	 *
	 * @param string $text
	 * @return string
	 * @access private
	 */
	function escapeTagReserved($text)
	{
		$reserved = Array('"',"'"); // =
		$replacement = Array('\"',"\'"); // \=
		return str_replace($reserved,$replacement,$text);
	}

}