<?php
/**
 * Joom!Fish - Multi Lingual extention and translation manager for Joomla!
 * Copyright (C) 2003-2009 Think Network GmbH, Munich
 *
 * All rights reserved.  The Joom!Fish project is a set of extentions for
 * the content management system Joomla!. It enables Joomla!
 * to manage multi lingual sites especially in all dynamic information
 * which are stored in the database.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,USA.
 *
 * The "GNU General Public License" (GPL) is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * -----------------------------------------------------------------------------
 * $Id: jfdatabase_inherit.php 1344 2009-06-18 11:50:09Z akede $
 * @package joomfish
 * @subpackage jfdatabase
 * @version 2.0
 *
*/

// Don't allow direct linking
defined( '_JEXEC' ) or die( 'Direct Access to this location is not allowed.' );

//require_once( JPATH_SITE.'/components/com_joomfish/helpers/joomfish.class.php' );
//require_once( JPATH_SITE."/administrator/components/com_joomfish/JoomfishManager.class.php");


/**
 * Multi lingual Database connector class
 *
 * This extention of the standard database class converts the output of the query automatically
 * with the actual selected language in the web site.
 *
 * @package joomfish
 * @subpackage database
 * @copyright 2003-2009 Think Network GmbH
 * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * @version 1.0, 2009-01-07 $Revision: 1251 $
 * @author Geraint Edwards
*/

include_once(dirname(__FILE__)."/intercept.".strtolower(get_class(JFactory::getDBO())).".php");
class JFDatabase extends interceptDB {

	/** @var array list of multi lingual tables */
	var $_mlTableList=null;
	/** @var Internal variable to hold array of unique tablenames and mapping data*/
	var $_refTables=null;

	/** @var Internal variable to hold flag about whether setRefTables is needed - JF queries don't need it */
	var $_skipSetRefTables = false;

	var $orig_limit	= 0;
	var $orig_offset	= 0;

	/** Constructor
	*/
	function JFDatabase( $options) {
		parent::__construct($options );

		$pfunc = $this->_profile();

		$query = "select distinct reference_table from #__jf_content";
		$this->setQuery( $query );
		$this->_mlTableList = $this->loadResultArray(0,false);
		if( !$this->_mlTableList ){
			echo $this->getErrorMsg();
		}

		$pfunc = $this->_profile($pfunc);
	}

	var $profileData = array();

	function _profile($func = "", $forcestart=false){
		if (!$this->_debug) return "";
		// start of function
		if ($func==="" || $forcestart){
			if (!$forcestart){
				$backtrace = debug_backtrace();
				if (count($backtrace)>1){
					if (array_key_exists("class",$backtrace[1])){
						$func = $backtrace[1]["class"]."::".$backtrace[1]["function"];
					}
					else {
						$func = $backtrace[1]["function"];
					}
				}
			}
			if (!array_key_exists($func,$this->profileData)){
				$this->profileData[$func]=array("total"=>0, "count"=>0);
			}
			if (!array_key_exists("start",$this->profileData[$func])) {
				$this->profileData[$func]["start"]=array();
			}
			list ($usec,$sec) = explode(" ", microtime());
			$this->profileData[$func]["start"][] = floatval($usec)+floatval($sec);
			$this->profileData[$func]["count"]++;
			return $func;
		}
		else {
			if (!array_key_exists($func,$this->profileData)){
				exit("JFProfile start not found for function $func");
			}
			list ($usec,$sec) = explode(" ", microtime());
			$laststart = array_pop($this->profileData[$func]["start"]);
			$this->profileData[$func]["total"] += (floatval($usec)+floatval($sec)) - $laststart;
		}
	}

	/**
	 * Public function to test if table has translated content available
	 *
	 * @param string $table : tablename to test
	 */
	function translatedContentAvailable($table){
		return in_array( $table, $this->_mlTableList) || $table=="content";
	}

	/**
	 * Description
	 *
	 * @access public
	 * @return int The number of rows returned from the most recent query.
	 */
	function getNumRows( $cur=null, $translate=true, $language=null )
	{
		$count = parent::getNumRows($cur);
		if (!$translate) return $count;

		// setup Joomfish plugins
		$dispatcher	   =& JDispatcher::getInstance();
		JPluginHelper::importPlugin('joomfish');

		// must allow fall back for contnent table localisation to work
		$allowfallback = true;
		$refTablePrimaryKey = "";
		$reference_table = "";
		$ids="";
		$jfm =& JoomFishManager::getInstance();
		$this->_setLanguage($language);
		$registry =& JFactory::getConfig();
		$defaultLang = $registry->getValue("config.defaultlang");
		if ($defaultLang == $language){
			$rows = array($count);	
			$dispatcher->trigger('onBeforeTranslation', array (&$rows, $ids, $reference_table, $language, $refTablePrimaryKey, $this->_getRefTables(), $this->_sql, $allowfallback));
			$count = $rows[0];
			return $count;
		}

		$rows = array($count);
		
		$dispatcher->trigger('onBeforeTranslation', array (&$rows, $ids, $reference_table, $language, $refTablePrimaryKey, $this->_getRefTables(), $this->_sql, $allowfallback));
		
		$dispatcher->trigger('onAfterTranslation', array (&$rows, $ids, $reference_table, $language, $refTablePrimaryKey, $this->_getRefTables(), $this->_sql, $allowfallback));
		$count = $rows[0];
		return $count;
	}

	/**
	* Overwritten method to loads the first field of the first row returned by the query.
	*
	* @return The value returned in the query or null if the query failed.
	*/
	function loadResult( $translate=true, $language=null ) {
		if (!$translate){
			$this->_skipSetRefTables=true;
			$result = parent::loadResult();
			$this->_skipSetRefTables=false;
			return $result;
		}
		$result=null;
		$ret=null;

		$result = $this->_loadObject( $translate, $language );

		$pfunc = $this->_profile();

		if( $result != null ) {
			$fields = get_object_vars( $result );
			$field = each($fields);
			$ret = $field[1];
		}

		$pfunc = $this->_profile($pfunc);

		return $ret;
	}

	/**
	 * Overwritten Load an array of single field results into an array
	 *
	 * @access	public
	 */
	function loadResultArray($numinarray = 0,  $translate=true, $language=null){
		if (!$translate){
			return parent::loadResultArray($numinarray);
		}
		$results=array();
		$ret=array();
		$results = $this->loadObjectList( '', $translate, $language );

		$pfunc = $this->_profile();

		if( $results != null && count($results)>0) {
			foreach ($results as $row) {
				$fields = get_object_vars( $row );
				$keycount = 0;
				foreach ($fields as $k=>$v) {
					if ($keycount==$numinarray){
						$key = $k;
						break;
					}
				}
				$ret[] = $fields[$key];
			}
		}

		$pfunc = $this->_profile($pfunc);

		return $ret;
	}

	/**
	* Overwritten Fetch a result row as an associative array
	*
	* @access	public
	* @return array
	*/
	function loadAssoc( $translate=true, $language=null) {
		if (!$translate){
			return parent::loadResult();
		}
		$result=null;
		$result = $this->_loadObject( $translate, $language );

		$pfunc = $this->_profile();

		if( $result != null ) {
			$fields = get_object_vars( $result );
			$pfunc = $this->_profile($pfunc);
			return $fields;
		}
		return $result;
	}

	/**
	* Overwritten Load a assoc list of database rows
	*
	* @access	public
	* @param string The field name of a primary key
	* @return array If <var>key</var> is empty as sequential list of returned records.
	*/
	function loadAssocList( $key='', $translate=true, $language=null )
	{
		if (!$translate){
			return parent::loadAssocList($key);
		}
		$result=null;
		$rows = $this->loadObjectList($key, $translate, $language );

		$pfunc = $this->_profile();
		$results = array();
		if( $rows != null ) {
			foreach ($rows as $row) {
				if ($key!=""){
					$results[$row->$key] = get_object_vars( $row );
				}
				else {
					$results[] = get_object_vars( $row );
				}
			}
			$pfunc = $this->_profile($pfunc);
		}
		return $results;
	}

	/**
	* This global function loads the first row of a query into an object
	*/
	function loadObject( $translate=true, $language=null ) {
		$objects = $this->loadObjectList("",$translate,$language);
		if (!is_null($objects) && count($objects)>0){
			return $objects[0];
		}
		return null;
	}

	/**
	 * private function to handle the requirement to call different loadObject version based on class
	 *
	 * @param boolran $translate
	 * @param string $language
	 */
	function _loadObject( $translate=true, $language=null ) {
		return $this->loadObject();
	}

	/**
	* Load a list of database objects
	* @param string The field name of a primary key
	* @return array If <var>key</var> is empty as sequential list of returned TRANSLATED records.
	* If <var>key</var> is not empty then the returned array is indexed by the value
	* the database key.  Returns <var>null</var> if the query fails.
	*/
	function loadObjectList( $key='', $translate=true, $language=null ) {
		global $_JOOMFISH_MANAGER;

		if (!$translate) {
			$this->_skipSetRefTables=true;
			$result = parent::loadObjectList( $key );
			$this->_skipSetRefTables=false;
			return $result;
		}

		$result = parent::loadObjectList( $key );

		if( isset($_JOOMFISH_MANAGER)) {
			$this->_setLanguage($language);
		}

		// TODO check the impact of this on frontend translation
		// It does stop Joomfish plugins from working on missing translations e.g. regional content so disable for now
		// Don't do it for now since translation caching is so effective
		/*
		$registry =& JFactory::getConfig();
		$defaultLang = $registry->getValue("config.defaultlang");
		if ($defaultLang == $language){
		$translate = false;
		}
		*/

		if( isset($_JOOMFISH_MANAGER)) {
			$doTranslate=false;
			$tables =$this->_getRefTables();
			if ($tables == null) return $result; // an unstranslatable query to return result as is
			// if we don't have "fieldTablePairs" then we can't translate
			if (!array_key_exists("fieldTablePairs",$tables)){
				return $result;
			}
			foreach ($tables["fieldTablePairs"] as $i=>$table) {
				if ($this->translatedContentAvailable($table)) {
					$doTranslate=true;
					break;
				}
			}
			if ($doTranslate ) {
				$pfunc = $this->_profile();
				if ($_JOOMFISH_MANAGER->getCfg("transcaching",1)){
					// cache the results
					// TODO call based on config
					//$cache = &JFactory::getCache('jfquery');
					$cache = $_JOOMFISH_MANAGER->getCache($language);
					$this->orig_limit	= $this->_limit;
					$this->orig_offset	= $this->_offset;
					$result = $cache->get( array("JoomFish", 'translateListCached'), array($result, $language, $this->_getRefTables() ));
					$this->orig_limit	= 0;
					$this->orig_offset	= 0;
				}
				else {
					$this->orig_limit	= $this->_limit;
					$this->orig_offset	= $this->_offset;
					JoomFish::translateList( $result, $language, $this->_getRefTables() );
					$this->orig_limit	= 0;
					$this->orig_offset	= 0;
				}
				$pfunc = $this->_profile($pfunc);
			}
		}
		return $result;
	}


	/**
	 * Overwritten
	 *
	 * @access	public
	 * @return The first row of the query.
	 */
	function loadRow( $translate=true, $language=null)
	{
		if (!$translate){
			return parent::loadRow();
		}
		$result=null;
		$result = $this->_loadObject( $translate, $language );

		$pfunc = $this->_profile();

		$row = array();
		if( $result != null ) {
			$fields = get_object_vars( $result );
			foreach ($fields as $val) {
				$row[] = $val;
			}
			return $row;
		}
		return $row;
	}

	/**
	* Overwritten Load a list of database rows (numeric column indexing)
	*
	* @access public
	* @param string The field name of a primary key
	* @return array If <var>key</var> is empty as sequential list of returned records.
	* If <var>key</var> is not empty then the returned array is indexed by the value
	* the database key.  Returns <var>null</var> if the query fails.
	*/
	function loadRowList( $key=null , $translate=true, $language=null)
	{
		if (!$translate){
			return parent::loadRowList($key);
		}
		$results=array();
		if (is_null($key)) $key="";
		$rows = $this->loadObjectList($key, $translate, $language );

		$pfunc = $this->_profile();

		$row = array();
		if( $rows != null ) {
			foreach ($rows as $row) {
				$fields = get_object_vars( $row );
				$result = array();
				foreach ($fields as $val) {
					$result[] = $val;
				}
				if ($key!="") {
					$results[$row->$key] = $result;
				}
				else {
					$results[] = $result;
				}
			}
		}
		$pfunc = $this->_profile($pfunc);
		return $results;
	}

	/**
	* Overwritten insert function to enable storage of material created in non-default language.
	* Note that this creates a translation which is identical to the original - when we update
	* the original in the default language we then keep the translation (although it will appread out of date!).
	*
	* @param	string	table name
	* @param	object	instance with information to store
	* @param	string	primary key name of table
	* @param	boolean	debug info printed or not
	* @param	boolean	passthru without storing information in a translation table
	*/
	function insertObject( $table, &$object, $keyName = NULL, $verbose=false , $passthru=false) {
		global $_JOOMFISH_MANAGER;
		if( isset($_JOOMFISH_MANAGER)) {
			$this->_setLanguage($language);
		}
		$conf	=& JFactory::getConfig();
		$default_lang	= $conf->getValue('config.defaultlang');

		// if the currect language is the site default language the translations will not be updated!
		$passthru = $language == $default_lang;

		if( !$passthru && isset($_JOOMFISH_MANAGER)) {
			//Must insert parent first to get reference id !
			$parentInsertReturn = parent::insertObject( $table, $object, $keyName, $verbose);

			$pfunc = $this->_profile();

			$actContentObject=null;
			if( isset($table) && $table!="" ) {
				$tableName = ereg_replace( '^#__', '', $table);
				if ($table != "#__jf_content" ){
					// *** QUESTION ***//
					// IS THIS TEST APPROPRIATE HERE - I THINK IT MEANS YOU CAN'T DO A FIRST TRANSLATION FOR A TABLE VIA THE FRONT END
					// ** TEST BEFORE 1.8 **//
					//if ($this->translatedContentAvailable($table)) {
					$contentElement = $_JOOMFISH_MANAGER->getContentElement( $tableName );
					if( isset( $contentElement ) ) {
						include_once(JPATH_ADMINISTRATOR."/components/com_joomfish/models/ContentObject.php");
						$actContentObject = new ContentObject( $_JOOMFISH_MANAGER->getLanguageID($language), $contentElement );
						if( isset( $object->$keyName ) ) {
							$actContentObject->loadFromContentID( $object->$keyName );
							$actContentObject->updateMLContent( $object );
							if( isset( $object->state ) ) {
								$actContentObject->published = ($object->state == 1) ? true : false;
							} else if ( isset( $object->published ) ) {
								$actContentObject->published = ($object->published == 1) ? true : false;
							}
							if ($actContentObject->published){
								if ( $_JOOMFISH_MANAGER->getCfg("frontEndPublish")){
									$user =& JFactory::getUser();
									$access = new stdClass();
									$access->canPublish =  $user->authorize('com_content', 'publish', 'content', 'all');
									if ($access->canPublish) $actContentObject->setPublished($actContentObject->published);
								}
							}
							$actContentObject->store();

							if ($_JOOMFISH_MANAGER->getCfg("transcaching",1)){
								// clean the cache!
								$cache = $_JOOMFISH_MANAGER->getCache($language);
								$cache->clean();
							}
						}
					}
				}
				//}
			}

			$pfunc = $this->_profile($pfunc);

			return $parentInsertReturn;
		}
		else {
			return parent::insertObject( $table, $object, $keyName, $verbose);
		}
	}

	/**
	* Overwritten update function to enable storage of translated information.
	* Based on the configuration in the content elements the automatic storage of
	* information is activated or not. It is important that this upgraded method will ensure
	* that all information will be written into the translation table. Only in the case that the
	* default language is choosen the information will be updated directly within the original tables.
	* To make sure that all other information will be written into the tables as expected the
	* statements will be manipulated as needed.
	*
	* @param	string	table name
	* @param	object	instance with information to store
	* @param	string	primary key name of table
	* @param	boolean	update fields with null or not
	* @param	boolean	passthru without storing information in a translation table
	*/
	function updateObject( $table, &$object, $keyName, $updateNulls=true, $passthru=false ) {

		$pfunc = $this->_profile();

		global $_JOOMFISH_MANAGER;

		if( isset($_JOOMFISH_MANAGER)) {
			$this->_setLanguage($language);
		}
		$conf	=& JFactory::getConfig();
		$default_lang	= $conf->getValue('config.defaultlang');

		// if the currect language is the site default language the translations will not be updated!
		$passthru = $language == $default_lang;

		if( !$passthru && isset($_JOOMFISH_MANAGER)) {
			$actContentObject=null;
			if( isset($table) && $table!="") {
				$tableName = ereg_replace( '^#__', '', $table);
				if ($table != "#__jf_content" ){
					// *** QUESTION ***//
					// IS THIS TEST APPROPRIATE HERE - I THINK IT MEANS YOU CAN'T DO A FIRST TRANSLATION FOR A TABLE VIA THE FRONT END
					// ** TEST BEFORE 1.8 **//
					//if ($this->translatedContentAvailable($table)) {
					$contentElement = $_JOOMFISH_MANAGER->getContentElement( $tableName );
					if( isset( $contentElement ) ) {
						include_once(JPATH_ADMINISTRATOR."/components/com_joomfish/models/ContentObject.php");
						$actContentObject = new ContentObject( $_JOOMFISH_MANAGER->getLanguageID($language), $contentElement );
						if( isset( $object->$keyName ) ) {
							$actContentObject->loadFromContentID( $object->$keyName );
							$actContentObject->updateMLContent( $object );
							if( isset( $object->state ) ) {
								$actContentObject->published = ($object->state == 1) ? true : false;
							} else if ( isset( $object->published ) ) {
								$actContentObject->published = ($object->published == 1) ? true : false;
							}
							if ( $_JOOMFISH_MANAGER->getCfg("frontEndPublish")){
								$user =& JFactory::getUser();
								$access = new stdClass();
								$access->canPublish =  $user->authorize('com_content', 'publish', 'content', 'all');
								if ($access->canPublish) $actContentObject->setPublished($actContentObject->published);
							}

							$actContentObject->store();

							if ($_JOOMFISH_MANAGER->getCfg("transcaching",1)){
								// clean the cache!
								$cache = $_JOOMFISH_MANAGER->getCache($language);
								$cache->clean();
							}
						}
					}
				}
				//}
			}

			$pfunc = $this->_profile($pfunc);

			return parent::updateObject( $table, $object, $keyName, $updateNulls );

		} else {
			return parent::updateObject( $table, $object, $keyName, $updateNulls );
		}
	}

	/**
	 *  Internal function to determit the table name from an sql query
	 *
	 *  This is now deprecated
	 *
	 * @return	string	table name
	 */
	function _getTableName() {

		$pfunc = $this->_profile();

		$conf	=& JFactory::getConfig();
		$dbprefix 	= $conf->getValue('config.dbprefix');

		$posFrom = strpos( strtoupper($this->_sql), 'FROM ') + 5; // after 'FROM '
		$posWhere = strpos( strtoupper($this->_sql), 'WHERE ');
		$table = substr( $this->_sql, $posFrom, $posWhere - $posFrom);
		if( strpos( $table, ' ' ) !== false ) {
			$table = substr( $table, 0, strpos( $table, ' ' ) );
		}
		if (isset($dbprefix) && strlen($dbprefix)>0) $table = ereg_replace( $dbprefix, '', $table);
		$table = ereg_replace( "\n", "", $table) ;

		$pfunc = $this->_profile($pfunc);

		return $table;
	}

	/**
	 * Override query in order to extract ref tables data
	 *
	 * @return n/a
	 */
	function query() {
		parent::query();
		if (!$this->_skipSetRefTables){
			$this->setRefTables();
		}
		return $this->_cursor;
	}

	/** Internal function to return reference table names from an sql query
	 *
	 * @return	string	table name
	 */
	function _getRefTables(){
		return $this->_refTables;
	}

	/**
	 * Private function to get the language for a specific translation
	 *
	 */
	function _setLanguage( & $language){

		$pfunc = $this->_profile();

		// first priority to passed in language
		if (!is_null($language)){
			return;
		}
		// second priority to language for build route function in other language
		// ie so that module can translate the SEF URL
		$registry =& JFactory::getConfig();
		$sefLang = $registry->getValue("joomfish.sef_lang", false);
		if ($sefLang){
			//$jfLang = TableJFLanguage::createByShortcode($sefLang, false);
			$language = $sefLang;
		}
		else {
			$jlang =& JFactory::getLanguage();
			$language = $jlang->getTag();
		}

		$pfunc = $this->_profile($pfunc);


	}

	/**
	 * Returns a reference to the global Database object, only creating it
	 * if it doesn't already exist. And keeps sure that there is only one
	 * instace for a specific combination of the JDatabase signature
	 *
	 * @param string  Database driver
	 * @param string Database host
	 * @param string Database user name
	 * @param string Database user password
	 * @param string Database name
	 * @param string Common prefix for all tables
	 * @return database A database object
	 * @since 1.5
	*/
	function &getInstance( $driver='mysql', $host='localhost', $user, $pass, $db='', $table_prefix='' )
	{
		$signature = serialize(array($driver, $host, $user, $pass, $db, $table_prefix));
		$database = JDatabase::_getStaticInstance($signature,'JFDatabase',true);

		return $database;
	}


	/**
	 * The following methods are not yet implemented in Joomfish (or may not be possible)
	 */
	// function queryBatch( $abort_on_error=true, $p_transaction_safe = false)
}

class mldatabase extends JFDatabase
{
	function mldatabase($options){
		parent::JFDatabase( $options);
	}

	function loadObject( &$object, $translate=true, $language=null )
	{
		if (!is_null($object) && is_object($object)){
			$res = parent::loadObject($translate, $language);
			foreach (get_object_vars($res) as $key=>$val) {
				$object->$key = $val;
			}
			return true;
		}
		else {
			$object  = parent::loadObject($translate, $language);
			return $object;
		}
	}

	/**
	 * private function to handle the requirement to call different loadObject version based on class
	 *
	 * @param boolran $translate
	 * @param string $language
	 */
	function _loadObject( $translate=true, $language=null ) {
		return parent::loadObject( $translate, $language );
	}


	function __destruct()
	{
		// I must have a specific destructor otherwise session data may not get written
		// One example of this is system messages not being cleared from the stack
	}

}
