<?php
/*
Officity - Web application platform - Version 6.0 - 2011-07-05

François Dispaux, Boris Verdeyen, Thomas Hermant,
Jérémie Roy, Grégory Meurice, Abdelila Harbi, 
Marc Mignonsin, Jonathan Sanchez, Julien Gonzalez, Pierre Fouchez

Sushee and Officity is © Copyright 2011 Nectil SA.

`/var/www/installer/public_html/officity-source/apps/system/tools/element/element.class.php` is part of Officity.

Sushee 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 3 of the License, or
(at your option) any later version.

Officity and Sushee are distributed in the hope that they 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 Sushee. If not, see <http://www.gnu.org/licenses/>.
*/
    error_reporting(E_ALL  & ~E_NOTICE  & ~E_WARNING);

	define('DEFAULT_SORT_FIELD', 'INFO/DENOMINATION');
	define('DEFAULT_SORT_ORDER', 'ascending');
	define('DEFAULT_PAGINATION', 50);

	/* *** */

	class Element extends NectilObject
	{
		static $version = '2010-12-06';

		protected $module_denomination;
		protected $command_name, $results_name, $refresh, $ID;
		protected $nql_command;
		protected $nql;
        protected $last_error;
        protected $files_2_upload;

		// search-related variables
		protected $search_config_string, $search_config_xml;
		
		/* CONTRUCTION/DESTRUCTION, GETTERS, MISC */

        /**
         * Element constructor
         * @param name string <p>
         * Name of the module.
         * </p>
         * @param ID integer[optional]
         * @return nothing
         */
		function __construct($name, $ID=0)
		{
			//parent::__construct();
			$this->setDenomination($name);
			$this->setCommandName('');
			$this->setResultsName('');
			$this->setRefresh('');

			$this->nql = new Sushee_Shell(false);
			$this->nql_command = null;

			$this->createRootNode('<'.$this->getDenomination().'/>');
			$this->setID($ID);

			$this->search_config_string = '';
			$this->search_config_xml = null;

            $this->last_error = '';
            $this->files_2_upload = array();
		}

		function __destruct()
		{
			unset($this->nql_command);
			unset($this->nql);
			unset($this->search_config_xml);

			//parent::__destruct();
		}

		static function getVersion()
		{
			return self::$version;
		}

		protected function setDenomination($denomination)
		{
			$this->module_denomination = strtoupper($denomination);
		}

		public function getDenomination()
		{
			return $this->module_denomination;
		}

		protected function setCommandName($command_name)
		{
			$this->command_name = $command_name;
		}

		protected function getCommandName()
		{
			return $this->command_name;
		}

		protected function setResultsName($results_name)
		{
			$this->results_name = $results_name;

			if (!empty($results_name))
			{
				$this->getCommandNode()->setAttribute('name', $this->results_name);
			}
		}

		protected function getResultsName()
		{
			return $this->results_name;
		}

		public function setRefresh($refresh)
		{
			$this->refresh = $refresh;

			if (!empty($refresh))
			{
				$this->getCommandNode()->setAttribute('refresh', $this->refresh);
			}
		}

		protected function getRefresh()
		{
			return $this->refresh;
		}
		
		public function setElementAttr($attr_name, $attr_value)
		{
			$this->getElementNode()->setAttribute($attr_name, $attr_value);
		}

		public function setID($ID)
		{
			$this->ID = $ID;

			if ($ID > 0)
			{
				$this->getElementNode()->setAttribute('ID', $this->ID);
			}
            else
            {
                if ($this->getElementNode() !== false)
                {
                    $this->getElementNode()->removeAttribute('ID');
                }
            }

            $this->removeInfo('ID'); // just in case...
		}

		public function removeID()
		{
			$this->ID = 0;
			$this->getElementNode()->removeAttribute('ID');
			$this->removeInfo('ID');
		}

		public function getID()
		{
			return $this->ID;
		}

		public function getNQLObject()
		{
			return  $this->nql_command;
		}
		
		public function toString()
		{
			return $this->nql_command->toString('/','');
		}

		public function logCommand($screen=true, $die=true, $html_conversion=false)
		{
			$command = $this->toString();
			debug_log($command);

			if ($screen == true)
			{
                if ($html_conversion == true)
                {
                    echo htmlentities($command);
                }
                else
                {
                    var_dump($command);
                }
			}
			if ($die == true)
			{
				die();
			}
		}

		protected function setLastError($error, $msg_element=false)
		{
			debug_log('ERROR in Element class : '.$error);
			if ($msg_element !== false)
			{
                $msg = (is_object($msg_element)) ? 
                			$msg_element->valueOf('.').' ('.$msg_element->valueOf('@errorCode').')' : 
                			$msg_element;
                			
				debug_log($msg);
                $this->last_error = $msg;
			}
            else
            {
                $this->last_error = $error;
            }
		}

		public function getLastError()
		{
            return empty($this->last_error) ? 'undefined error.' : $this->last_error;
		}
		
		/* SHARED RESOURCES */
				
		public function addSharedResourcesCommands(&$nql)
		{
			/*$nql->addCommand('
			');*/
		}
		
		public function setDisplayProfile()
		{			
			$this->setReturnService('INFO');
			$this->setDefaultSort('INFO/MODIFICATIONDATE', 'descending');
		}

		/* MODULE INFORMATION */

		public function getModuleID()
		{
			$nql = new Sushee_Shell(false);

			$nql->addCommand(
				'<SEARCH name="module">
					<MODULE>
						<INFO>
							<DENOMINATION operator="=">'.$this->getDenomination().'</DENOMINATION>
						</INFO>
					</MODULE>
					<RETURN>
						<NOTHING />
					</RETURN>
				</SEARCH>');

			$nql->execute();

			$module_ID = $nql->getElement('/RESPONSE/RESULTS[@name="module"]/MODULE')->valueOf('./@ID');

			return $module_ID;
		}
		
		/* SEARCH CONFIG */
		
		public function loadSearchConfig($ID=0)
		{
			$search_config = new Officity_SearchconfigElement();
			
			if ($ID > 0) 
			{
				if ($search_config->find() == false)
				{
					throw new OfficityAppException('Cannot find the custom search configuration ($ID='.$ID.')! Please check the module configuration ('.$this->getDenomination().').');
				}
				
			}
			else
			{
				if ($search_config->getDefault($this->getDenomination()) == false)
				{
					throw new OfficityAppException('Cannot find the default search configuration! Please check the module configuration ('.$this->getDenomination().').');
				}
			}
						
			$this->search_config_string = $search_config->getXMLInfo('CONFIGXML');
			
			// create the XML tree for use in addSearchCommands and @ the end of advancedSearchParsing()
			$this->search_config_xml = new XML($this->search_config_string);
		}

		public function initSearchConfigCommand(&$nql)
		{ 
			$this->loadSearchConfig();
			
			if (exists($nql))
			{
				$nql->addCommand('<RESULTS name="search-config" static="true">'.$this->search_config_string.'</RESULTS>');
			}
			//die($nql->getQuery());
		}
		
		public function initNewSearch(&$nql, $filter_ID)
		{
			$module_denomination = $this->getDenomination();
		    if (empty($filter_ID))
		    {
		        $filter = FilterElement::restore_last_filter($module_denomination);        
				$this->addNewSearchCommands($nql, $filter, false);
		    }
		    else
		    {
		        $filter = new FilterElement($filter_ID);
		        $filter->get();
		
		        if ($filter->getInfo("ISDISPLAY") == '0')
		        {
		            $default_filter = FilterElement::restore_last_filter($module_denomination);
		            $filter->cloneDisplay($default_filter);		
		            $filter->setInfo('ISDISPLAY', 0); // reset this flag to ensure that the update dialog is correct
		       }
		
		        $this->addNewSearchCommands($nql, $filter, true);
		    }
		}
		
		public function addNewCritTemplateCommands(&$nql, $dependency_type, $category)
		{
			$this->initSearchConfigCommand($nql);	

			$cmds_nql = new Sushee_Shell(false);

			if (empty($category))
			{
				if (empty($dependency_type))
				{
					$target_module = $this->getDenomination();
					$target_field = $this->search_config_xml->getElement('DEFAULTFIELD')->valueOf('.');
				}
				else
				{
					$dep_in_config = $this->search_config_xml->getElement('DEPENDENCIES//DEPENDENCY[TYPE="'.$dependency_type.'"]');
					$target_module = $dep_in_config->valueOf('MODULE');
					$target_field = $dep_in_config->valueOf('DEFAULTFIELD');
				}				
				//var_dump($target_module);
				//var_dump($target_field);
								
				$cmds_nql->addCommand('<RESULTS name="search-config" static="true">'.$this->search_config_string.'</RESULTS>');
				$cmds_nql->addCommand('
					<SEARCH refresh="daily" name="field">
						<FIELD searchable="1" module="'.$target_module.'" denomination="'.$target_field.'" />
						<RETURN>
							<INFO>
								<DENOMINATION />		
								<TYPE />												
	                            <TARGETMODULE />
	                            <TARGETFIELD />						
								<LISTNAME />
							</INFO>
						</RETURN>
					</SEARCH>
				');
			}
			else
			{
				$cmds_nql->addCommand('<RESULTS name="search-config" static="true">'.$this->search_config_string.'</RESULTS>');
			}

			$res_cmds = $cmds_nql->transform(SYSTEM_TOOLS_PATH.'search/newsearch-resources-cmds-templates.xsl');
            //die(var_dump($res_cmds));
            $nql->addCommand($res_cmds);
            
            $nql->addCommand('
				<SEARCH refresh="daily" name="fields-'.$target_module.'">
					<FIELD searchable="1" module="'.$target_module.'"/>
					<RETURN>
						<INFO>
							<MODULE/>
							<DENOMINATION/>
							<TYPE />												
                            <TARGETMODULE />
                            <TARGETFIELD />						
							<LISTNAME />
						</INFO>
						<DESCRIPTION>
							<TITLE/>
							<SUMMARY/>
						</DESCRIPTION>
					</RETURN>
				</SEARCH>
			');
		}

		public function addNewSearchCommands(&$nql, $filter, $loaded_filter=false)
		{			
			// -- add static config command to the NQL passed in parameter --
			$this->initSearchConfigCommand($nql);

            // -- add static filter command (loaded or not) --
            $filter_command = $filter->getStaticCommand('search-filter');
            $nql->addCommand($filter_command);

			$fields_nql = new Sushee_Shell(false);
			$fields_nql->addCommand('<RESULTS name="search-config" static="true">'.$this->search_config_string.'</RESULTS>');
			$fields_nql->addCommand('
				<SEARCH refresh="daily" name="fields">
					<FIELD searchable="1">
						<INFO>
							<TYPE>list</TYPE>
							<TYPE>systemList</TYPE>
							<TYPE>elementsList</TYPE>
						</INFO>
					</FIELD>
					<RETURN>
						<INFO>
							<MODULE/>
							<DENOMINATION />
							<TYPE />
							<LISTNAME />
                            <TARGETMODULE />
                            <TARGETFIELD />
						</INFO>
					</RETURN>
				</SEARCH>
			');

			if ($loaded_filter == true)
			{
				$fields_nql->addCommand($filter_command); // use loaded filter as well
			}
			
			$res_cmds = $fields_nql->transform(SYSTEM_TOOLS_PATH.'search/newsearch-resources-cmds.xsl');
            //die(var_dump($res_cmds));
            $res_cmds = '<RESULTS name="commands" static="true">'.$res_cmds.'</RESULTS>';
            
            $lists_nql = new Sushee_Shell(false);
			$lists_nql->addCommand('<RESULTS name="search-config" static="true">'.$this->search_config_string.'</RESULTS>');
			$lists_nql->addCommand($res_cmds);
            $distinct_lists_and_fields_commands = $lists_nql->transform(SYSTEM_TOOLS_PATH.'search/newsearch-resources-distinct-cmds.xsl');
            //die($distinct_lists_and_fields_commands);

			$nql->addCommand($distinct_lists_and_fields_commands);
		}

		public function addSearchCommands(&$nql, &$filter, $add_filter=true)
		{
            $filter_command = $filter->getStaticCommand('search-filter');
			if ($add_filter == true)
            {                
                $nql->addCommand($filter_command);
            }

			$fields_nql = new Sushee_Shell(false);
			$fields_nql->addCommand('<RESULTS name="search-config" static="true">'.$this->search_config_string.'</RESULTS>');
			$fields_nql->addCommand($filter_command);
			$fields_nql->addCommand('
				<SEARCH refresh="daily" name="fields">
					<FIELD searchable="1">
						<INFO>
							<TYPE>list</TYPE>
							<TYPE>systemList</TYPE>
							<TYPE>elementsList</TYPE>
						</INFO>
					</FIELD>
					<RETURN>
						<INFO>
							<MODULE/>
							<DENOMINATION />
							<TYPE />
							<LISTNAME />
                            <TARGETMODULE />
                            <TARGETFIELD />	
						</INFO>
					</RETURN>
				</SEARCH>
			');

			$res_cmds = $fields_nql->transform(SYSTEM_TOOLS_PATH.'search/search-resources-cmds.xsl');
            //die(var_dump($res_cmds));
            $res_cmds = '<RESULTS name="commands" static="true">'.$res_cmds.'</RESULTS>';

            $lists_nql = new Sushee_Shell(false);
			$lists_nql->addCommand('<RESULTS name="search-config" static="true">'.$this->search_config_string.'</RESULTS>');
            $lists_nql->addCommand($filter_command);
			$lists_nql->addCommand($res_cmds);
            $distinct_lists_and_fields_commands = $lists_nql->transform(SYSTEM_TOOLS_PATH.'search/search-resources-distinct-cmds.xsl');
            //die($distinct_lists_and_fields_commands);

			$nql->addCommand($distinct_lists_and_fields_commands);
			
			// -- PAGE BROWSER --
			
			$nql->addCommand('
				<GET refresh="monthly">
					<ENUM>
						<START>1</START>
						<END>20</END>
					</ENUM>
				</GET>
			');
		}
		
		public function initAdvancedSearch(&$nql)
		{
			$_GET += $_POST; // for XSLT
			
		    $DISPLAY_SELECTION = $_POST['displaySelection'];
		    $SELECTED_IDs = explode(',', $_POST['selectedItems']);
		    array_pop($SELECTED_IDs); // remove the last empty ID (the list ends with an extra comma)
		    $LAST_HITS = $_POST['lastHits'];
		    
		    $selectionsCommand = '<RESULTS name="selections" count="'.count($SELECTED_IDs).'" lasthits="'.$LAST_HITS.'" display="'.$DISPLAY_SELECTION.'" static="true">';
		    foreach ($SELECTED_IDs as $selID)
		    {
		        $selectionsCommand .= '<SELECTION ID="'.$selID.'"/>';
		    }
		    $selectionsCommand .= '</RESULTS>';
		
		    $nql->addCommand($selectionsCommand);
		
		    /* *** */
		
		    $this->initSearchConfigCommand($nql);
		        
		    if (($DISPLAY_SELECTION == 'yes') && (!empty($SELECTED_IDs)))
		    {
		        $this->setElementsByID($SELECTED_IDs);
		    }
		    else
		    {
		        $this->advancedSearchParsing($_POST);
		    }
		
		    $COLUMNS_LIST = $_POST['columns'];
		    $columns = explode(',', $COLUMNS_LIST);    		    
		    $this->setSearchReturn($columns);
		    $this->setSort($_POST['sort'], $_POST['order']);
		    $this->setPaginate($_POST['display'], $_POST['page']);
		    $this->search('list', false);	    
		}
		
		public function advancedSearch(&$nql)
		{			
		    $search_command = $this->getNQLCommand();
		    $nql->addCommand($search_command);
		    $nql->addStatic($search_command, 'search-nql'); // firebug console logging		    
		    //die(var_dump($search_command));
		    		    
		    /* *** */
		
		    $search_filter = $this->buildSearchFilter();
		    $search_filter->save_as_last($this->getDenomination());		
		    $this->addSearchCommands($nql, $search_filter);
		    		    
		    //xml_out($nql->getQuery());		
		}

		public function addDisplayConfigCommands(&$nql)
		{
            // -- add static config command to the NQL passed in parameter --
			$this->initSearchConfigCommand($nql);

			$fields_nql = new Sushee_Shell(false);
			$fields_nql->addCommand('<RESULTS name="search-config" static="true">'.$this->search_config_string.'</RESULTS>');
            $fields_commands = $fields_nql->transform(SYSTEM_TOOLS_PATH.'search/displayconfig-resources-cmds.xsl');

			$nql->addCommand($fields_commands);
		}

		/* NODES MANIPULATION */

		public function createFromXML($xml_string)
		{
			$this->createRootNode($xml_string);
		}

		/* ROOT NODE */

		protected function createRootNode($xml_string)
		{
			if ($this->nql_command != null)
			{
				unset($this->nql_command);
				$this->nql_command = null;
			}

			$this->nql_command = new XML($xml_string);
		}

		/* COMMAND NODE */

		protected function createCommandNode($command_name, $results_name='')
		{			
			if ($this->getCommandNode($command_name) === false) // discard if command already exists
			{				
				// backup
				$element_node_string = $this->getElementNode()->toString();	
				
                if (($command_name == 'GET') || ($command_name == 'SEARCH'))
                {
                    $return_node = $this->getReturnNode();
                    $return_node_string = ($return_node !== false) ? $return_node->toString() : '';

                    $sort_node = $this->getSortNode();
                    $sort_node_string = ($sort_node !== false) ? $sort_node->toString() : '';

                    $paginate_node = $this->getPaginateNode();
                    $paginate_node_string = ($paginate_node !== false) ? $paginate_node->toString() : '';
                }

				// create new command node
				$this->command_name = $command_name;
				$this->createRootNode('<'.$this->command_name.'/>');

				// restore
				$command_node = $this->getCommandNode();
				$command_node->appendChild($element_node_string);
								
                if (($command_name == 'GET') || ($command_name == 'SEARCH'))
                {
                    if (!empty($return_node_string)) $command_node->appendChild($return_node_string);
                    if (!empty($sort_node_string)) $command_node->appendChild($sort_node_string);
                    if (!empty($paginate_node_string)) $command_node->appendChild($paginate_node_string);
                }
			}

			// restore/update command node attribute : name & refresh
			$results_name = (!empty($results_name)) ? $results_name : $this->getResultsName();
			$this->setResultsName($results_name);

            $this->setRefresh($this->getRefresh());
		}

		protected function getCommandNode($command_name='')
		{
			$xpath = (!empty($command_name)) ? '/'.$command_name : '/'.$this->command_name;
			return $this->nql_command->getElement($xpath);
		}

		/* ELEMENT (MODULE) NODE */

		protected function getElementNode()
		{
			$xpath = '//'.$this->getDenomination();
			/*var_dump(htmlentities($this->nql_command->toString()));
			echo '<br />';
			var_dump($xpath);
			echo '<br />';
			var_dump($this->nql_command->getElement($xpath)===false);
			echo '<br />';*/
			return $this->nql_command->getElement($xpath);
		}
		
		/* PUBLISHER NODE */
		
		protected function getPublisherNode($node_name='')
		{
			$xpath = '//'.$this->getDenomination().'/PUBLISHER';
			if (!empty($node_name)) $xpath .= '/'.$node_name;
			return $this->nql_command->getElement($xpath);
		}
		
		protected function createPublisherNode($node_name, $node_value='')
		{
			$node_name = strtoupper($node_name);
			$node_value = stripcslashes($node_value);
			$node_string = '<'.$node_name.'>'.$node_value.'</'.$node_name.'>';
			$publisher_node = $this->getPublisherNode();

			if ($this->getPublisherNode($node_name) === false) // node doesn't exist
			{
				$publisher_node->appendChild($node_string); // create it
			}
			else // node already exists
			{
				$publisher_node->removeChild('./'.$node_name);
				$publisher_node->appendChild($node_string);
			}
		}

		/* INFO NODES */

		protected function getInfoNode($node_name='')
		{
			$xpath = '//'.$this->getDenomination().'/INFO';
			if (!empty($node_name)) $xpath .= '/'.$node_name;
			return $this->nql_command->getElement($xpath);
		}

		protected function createInfoNode($node_name, $node_value='', $action_if_exists='discard', $xml_encode=true)
		{
			$node_name = strtoupper($node_name);
			if (is_string($node_value))
			{
				$node_value = stripcslashes($node_value);
			}
			if (in_array($node_name, array('SQLTYPE'))) // discard
			{
				return;
			}
			if(substr($node_name,-3)=='XML'){
				$xml_encode = false;
			}
			if ($xml_encode == true)
            {
                $node_value = encode_to_xml($node_value);
            }
			$node_string = '<'.$node_name.'>'.$node_value.'</'.$node_name.'>';
			
			$info_node = $this->getInfoNode();

			if ($this->getInfoNode($node_name) === false) // node doesn't exist
			{
				$info_node->appendChild($node_string); // create it

				// just in case
				if ($node_name == 'ID')
				{
					$this->setID($node_value);
				}
			}
			else // node already exists
			{
				switch ($action_if_exists)
				{
					case 'replace':
						$info_node->removeChild('./'.$node_name);
						$info_node->appendChild($node_string);

						// just in case
						if ($node_name == 'ID')
						{
							$this->setID($node_value);
						}
						break;

					case 'append':
						$info_node->appendChild($node_string);

						// just in case
						if ($node_name == 'ID')
						{
							$this->setID($node_value);
						}
						break;

					case 'discard':
					default:
						break;
				}
			}
		}

		/* DEPINFO NODE */

		protected function getDepInfoNode()
		{
			$xpath = '//'.$this->getDenomination().'/DEPINFO';
			return $this->nql_command->getElement($xpath);
		}

		/* GET/SEARCH NODES */

		protected function verify_MATCH_operand($operand)
		{
			$words = explode(' ', $operand);

			foreach ($words as $w)
			{
				$l = strlen($w);
				
				if (($l > 0) && ($l < 4))
				{
					return false;
				}
			}

			return true;
		}

		protected function createSearchInfoNode($node_name, $operator, $operand1, $operand2='', $action_if_exists='discard')
		{
			if (strtoupper($operator) == 'MATCH')
			{
				if ($this->verify_MATCH_operand($operand1) == false)
				{
					$operator = 'contains';
				}
			}

			$node_name = strtoupper($node_name);

			/*if (is_string($operand1))
			{
				$operand1 = stripcslashes($operand1);
			}*/
			$operand1 = encode_to_xml($operand1);

			$node_value = $operand1;

			if (!empty($operand2))
			{
				/*if (is_string($operand2))
				{
					$operand2 = stripcslashes($operand2);
				}*/
				$operand2 = encode_to_xml($operand2);

				$node_value .= '/'.$operand2;
			}

			$node_string = '<'.$node_name.' operator="'.$operator.'">'.$node_value.'</'.$node_name.'>';

			$info_node = $this->getInfoNode();

			if ($this->getInfoNode($node_name) === false) // node doesn't exist
			{
				$info_node->appendChild($node_string); // create it
			}
			else // node already exists
			{
				switch ($action_if_exists)
				{
					case 'replace':
						$info_node->removeChild('./'.$node_name);
						$info_node->appendChild($node_string);
						break;

					case 'append':
						$info_node->appendChild($node_string);
						break;

					case 'discard':
					default:
						break;
				}
			}
		}

		protected function createSortNode()
		{
			// we need a root node now becuz the SORT is the sibling of
			// the element node and we can only use appendChild()
			$this->createCommandNode('SEARCH');

			if ($this->getSortNode() === false)
			{
				$this->getCommandNode()->appendChild('<SORT/>');
			}
		}

		protected function getSortNode()
		{
			$xpath = '//SORT';
			return $this->nql_command->getElement($xpath);
		}

		protected function createPaginateNode()
		{
			// we need a root node now becuz the PAGINATE is the sibling of
			// the element node and we can only use appendChild()
			$this->createCommandNode('SEARCH');

			if ($this->getPaginateNode() === false)
			{
				$this->getCommandNode()->appendChild('<PAGINATE/>');
			}
		}

		protected function getPaginateNode()
		{
			$xpath = '//PAGINATE';
			return $this->nql_command->getElement($xpath);
		}

		protected function createMainReturnNode($depth='')
		{
			// we need a root node becuz the RETURN node is the sibling of
			// the element node and we can only use appendChild()
			$this->createCommandNode('SEARCH');
			
			if ($this->getReturnNode() === false)
			{
				$this->getCommandNode()->appendChild('<RETURN/>');
			}

			if (!empty($depth))
			{
				$this->getReturnNode()->setAttribute('depth', $depth);
			}
		}

		protected function getReturnNode($node_name='')
		{
			$xpath = '//RETURN';
			if (!empty($node_name)) $xpath .= '/'.$node_name;
			return $this->nql_command->getElement($xpath);
		}

		protected function createReturnServiceNode($node_name, $node_attribute_name='', $node_attribute_value='')
		{
			$node_name = strtoupper($node_name);

			if ($this->getReturnNode($node_name) === false) // lock : no replacement
			{
				$this->getReturnNode()->appendChild('<'.$node_name.'/>');
			}

			if (!empty($node_attribute_name))
			{
				$this->getReturnNode($node_name)->setAttribute($node_attribute_name, $node_attribute_value);
			}
		}

		protected function createReturnInfoNode($node_name, $order=0)
		{
			$node_name = strtoupper($node_name);

			if ($this->getReturnNode('INFO/'.$node_name) === false) // lock : no replacement
			{
				$order_attribute = ($order > 0) ? 'order="'.$order.'"' : '';
				$this->getReturnNode('INFO')->appendChild('<'.$node_name.' '.$order_attribute.'/>');
			}
		}

		protected function getReturnDependencyNode($dependency_type, $node_name='')
		{
			$xpath = '//RETURN/DEPENDENCIES/DEPENDENCY[@type="'.$dependency_type.'"]';
			if (!empty($node_name)) $xpath .= '/'.$node_name;
			return $this->nql_command->getElement($xpath);
		}

		protected function createReturnDependencyNode($dependency_type)
		{
			if ($this->getReturnDependencyNode($dependency_type) === false) // lock : no replacement
			{
				$this->getReturnNode('DEPENDENCIES')->appendChild('<DEPENDENCY type="'.$dependency_type.'" />');
			}
		}
		
		protected function createReturnDependencyServiceNode($dependency_type, $node_name)
		{
			$node_name = strtoupper($node_name);

			if ($this->getReturnDependencyNode($dependency_type, $node_name) === false) // lock : no replacement
			{
				$this->getReturnDependencyNode($dependency_type)->appendChild('<'.$node_name.'/>');
			}
		}

		protected function createReturnDependencyInfoNode($dependency_type, $node_name, $order=0)
		{
			$node_name = strtoupper($node_name);

			if ($this->getReturnDependencyNode($dependency_type, 'INFO/'.$node_name) === false) // lock : no replacement
			{
				$order_attribute = ($order > 0) ? 'order="'.$order.'"' : '';
				$this->getReturnDependencyNode($dependency_type, 'INFO')->appendChild('<'.$node_name.' '.$order_attribute.'/>');
			}
		}

        /* CATEGORY NODES */

		protected function getCategoriesNode()
		{
			$xpath = '//'.$this->getDenomination().'/'.'CATEGORIES';
			return $this->nql_command->getElement($xpath);
		}

		protected function createCategoriesNode($operation='')
		{
			if ($this->getCategoriesNode() === false) // lock : only one <CATEGORIES> node
			{
				$this->getElementNode()->appendChild('<CATEGORIES />');
			}
            if (!empty($operation))
            {
                $this->getCategoriesNode()->setAttribute('operation', $operation);
            }
            else
            {
                $this->getCategoriesNode()->removeAttribute('operation');
            }
		}

		protected function getCategoryNode($attr_name, $attr_value)
		{
			$xpath = '//'.$this->getDenomination().'/'.'CATEGORIES/CATEGORY[@'.$attr_name.'="'.$attr_value.'"]';
			return $this->nql_command->getElement($xpath);
		}

		/* DEPENDENCY NODES */

		protected function getDependenciesNode()
		{
			$xpath = '//'.$this->getDenomination().'/'.'DEPENDENCIES';
			return $this->nql_command->getElement($xpath);
		}

		protected function createDependenciesNode()
		{
			if ($this->getDependenciesNode() === false) // lock : only one <DEPENDENCIES> node
			{
				$this->getElementNode()->appendChild('<DEPENDENCIES/>');
			}
		}

		protected function getDependencyNode($type_name)
		{
			$xpath = '//'.$this->getDenomination().'/'.'DEPENDENCIES/DEPENDENCY[@type="'.$type_name.'"]';
			return $this->nql_command->getElement($xpath);
		}

		/* DESCRIPTION NODES */

		protected function getDescriptionsNode()
		{
			$xpath = '//'.$this->getDenomination().'/'.'DESCRIPTIONS';
			return $this->nql_command->getElement($xpath);
		}

		protected function createDescriptionsNode()
		{
			if ($this->getDescriptionsNode() === false) // lock : only one <DESCRIPTIONS> node
			{
				$this->getElementNode()->appendChild('<DESCRIPTIONS/>');
			}
		}

		protected function getLanguageDescriptionNode($languageID)
		{
			$xpath = '//'.$this->getDenomination().'/'.'DESCRIPTIONS/DESCRIPTION[@languageID="'.$languageID.'"]';
			return $this->nql_command->getElement($xpath);
		}

		protected function getDescriptionNode($languageID, $node_name='')
		{
			$xpath = '//'.$this->getDenomination().'/DESCRIPTIONS/DESCRIPTION[@languageID="'.$languageID.'"]';
			if (!empty($node_name)) $xpath .= '/'.$node_name;
			return $this->nql_command->getElement($xpath);
		}

		protected function createDescriptionNode($languageID, $node_name, $node_value='', $action_if_exists='discard')
		{
			$node_name = strtoupper($node_name);
			if (is_string($node_value))
			{
				$node_value = stripcslashes($node_value);
			}			
            if ($node_name == 'BODY')
            {
                $node_value = '<CSS>'.$node_value.'</CSS>';
            }
            else
            {
                $node_value = encode_to_xml($node_value);
            }

			$node_string = '<'.$node_name.'>'.$node_value.'</'.$node_name.'>';
			
			$language_description_node = $this->getLanguageDescriptionNode($languageID);

			if ($this->getDescriptionNode($languageID, $node_name) === false) // node doesn't exist
			{
				$language_description_node->appendChild($node_string); // create it
			}
			else // node already exists
			{
				switch ($action_if_exists)
				{
					case 'replace':
						$language_description_node->removeChild('./'.$node_name);
						$language_description_node->appendChild($node_string);
						break;

					case 'append':
						$language_description_node->appendChild($node_string);
						break;

					case 'discard':
					default:
						break;
				}
			}
		}

		protected function getCustomDescriptionNode($languageID, $child_node_name='')
		{
			$xpath = '//'.$this->getDenomination().'/DESCRIPTIONS/DESCRIPTION[@languageID="'.$languageID.'"]/CUSTOM';
			if (!empty($child_node_name)) $xpath .= '/'.$child_node_name;
			return $this->nql_command->getElement($xpath);
		}

		protected function createCustomDescriptionNode($languageID, $custom_node_name, $custom_node_value, $action_if_exists='discard')
		{
			$custom_node_value = encode_to_xml($custom_node_value);
			$child_node_string = '<'.$custom_node_name.'>'.$custom_node_value.'</'.$custom_node_name.'>';

			$this->createDescriptionNode($languageID, 'CUSTOM', '', 'discard'); // do not replace the CUSTOM node if it already exists
			$custom_node = $this->getDescriptionNode($languageID, 'CUSTOM');

			if ($this->getCustomDescriptionNode($languageID, 'CUSTOM', $custom_node_name) === false) // child node doesn't exists
			{
				$custom_node->appendChild($child_node_string); // create it
			}
			else // node already exists
			{
				switch ($action_if_exists)
				{
					case 'replace':
						$custom_node->removeChild('./'.$custom_node_name);
						$custom_node->appendChild($child_node_string);
						break;

					case 'append':
						$custom_node->appendChild($child_node_string);
						break;

					case 'discard':
					default:
						break;
				}
			}
		}
		
		/* DESCRIPTION : return */
		protected function getReturnDescriptionNode($node_name='')
		{
			$xpath = '//RETURN/DESCRIPTION';
			if (!empty($node_name)) $xpath .= '/'.$node_name;
			return $this->nql_command->getElement($xpath);
		}
		
		protected function createReturnDescriptionNode($node_name, $order=0)
		{
			$node_name = strtoupper($node_name);
			if ($this->getReturnDescriptionNode($node_name) === false) // lock : no replacement
			{
				$order_attribute = ($order > 0) ? 'order="'.$order.'"' : '';
				$this->getReturnDescriptionNode()->appendChild('<'.$node_name.' '.$order_attribute.'/>');
			}
		}
		
		/* ANCESTOR NODE */
		
		protected function getAncestorNode()
		{
			$xpath = '//'.$this->getDenomination().'/ANCESTOR';
			return $this->nql_command->getElement($xpath);
		}

		/* NQL COMMAND */

		protected function executeNQLCommand($execute)
		{
			$command = $this->getNQLCommand();

			if ($execute == false) return true;

			$this->nql->addCommand($command);

			//var_dump($command); die();

			return $this->nql->execute();
		}

		public function getNQLCommand()
		{
			$command = $this->nql_command->toString('/', '');
			return $command;
		}

		/* HIGH LEVEL NQL COMMANDS */

		public function get($results_name='', $execute=true)
		{
			if ($this->getID() == 0) return false;
			
			$this->createCommandNode('GET', $results_name);
			$response = $this->executeNQLCommand($execute);
			
			if ($execute == true)
			{
				$xpath_cond = !empty($results_name) ? "[@name='$results_name']" : '';		
				$result = $this->nql->getElement("/RESPONSE/RESULTS$xpath_cond/".$this->getDenomination());
				
				if ($result !== false)
				{
					$this->createRootNode($result->toString());
					//$this->logCommand();
					return true;
				}
				else
				{
					$this->setLastError('get failed!');
					return false;
				}
			}

			return $response;
		}

		public function search($results_name, $execute=true)
		{
			$this->createCommandNode('SEARCH', $results_name);
			$response = $this->executeNQLCommand($execute);

			if ($execute == true)
			{
				$xpath_cond = !empty($results_name) ? "[@name='$results_name']" : '';				
				$result = $this->nql->getElement("/RESPONSE/RESULTS$xpath_cond/".$this->getDenomination());

				if ($results_list !== false)
				{
					return $results_list;
				}
				else
				{
					$this->setLastError('search failed!');
					return false;
				}
			}

			return $response;
		}

		public function find($execute=true)
		{
			$this->createCommandNode('SEARCH', 'find-result');
			$display = $page = 1;
			$this->setPaginate($display, $page);
			$response = $this->executeNQLCommand($execute);

			//$this->logCommand();

			if ($execute == true)
			{
				$results_list = $this->nql->getElement("/RESPONSE/RESULTS[@name='find-result']");

				if ($results_list !== false)
				{
					if ($results_list->valueOf('./@hits') == 1)
					{
						$result = $results_list->getElement('./'.$this->getDenomination());

						if ($result !== false)
						{
							//var_dump($result->valueOf('./@ID'));
							$this->createRootNode($result->toString());
							$this->setID($result->valueOf('./@ID')); // ...
							return true;
						}
					}
				}

				return false;
			}

			return $response;
		}

		function findMatch($field, $value, $execute=true)
		{
			$this->setSearchInfo($field, '=', $value);
			$this->setNoReturn();
			return $this->find($execute);
		}

		public function create($execute=true)
		{
			$this->createCommandNode('CREATE');			
			$response = $this->executeNQLCommand($execute);

			if ($execute == true)
			{
				$message = $this->nql->getElement("/RESPONSE/MESSAGE");
				
				if (($message !== false) && ($message->valueOf("@msgType") == '0'))
				{
					$ID = $message->valueOf("./@elementID");
					$this->setID($ID);
					return true;
				}
				else
				{
					$this->setLastError('create failed!', $message);
					return false;
				}
			}

			return $response;
		}

		// TODO : take search infos into account when creating the UPDATE command node => automatically create the WHERE node
		public function update($execute=true)
		{
			if ($this->getID() == 0) return false;

			$this->createCommandNode('UPDATE');
			$response = $this->executeNQLCommand($execute);

			if ($execute == true)
			{
				$message = $this->nql->getElement("/RESPONSE/MESSAGE");

				if (($message !== false) &&
					($message->valueOf("@msgType") == '0') && 
					($message->valueOf("@elementID") == $this->getID()))
				{
					return true;
				}
				else
				{
					$this->setLastError('update failed!', $message);
					return false;
				}
			}

			return $response;
		}
		public function updateFromSearchFilter(&$target_filter, $execute=true)
		{
			$this->createCommandNode('UPDATE');

			// the filter <QUERYXML> node should have a single child : the element node
			$query_nodes = $target_filter->getInfoNode('QUERYXML')->getElements('./*');			
			$query_element_node = $query_nodes[0];

			// check if it matches the name of this module
			if (strtoupper($query_element_node->nodeName()) != $this->getDenomination())
			{
				return false;
			}

			$element_node = $this->getElementNode();
			$element_node->appendChild('<WHERE/>');
			$where_node = $element_node->getElement('./WHERE');

			$target_query_nodes = $query_element_node->getElements('./*');
			foreach ($target_query_nodes as $query_node)
			{
				$where_node->appendChild($query_node->toString());
			}

			/* *** */

			$response = $this->executeNQLCommand($execute);

			if ($execute == true)
			{
				$message = $this->nql->getElement("/RESPONSE/MESSAGE");

				if (($message !== false) && ($message->valueOf("@msgType") == '0'))
				{
					return true;
				}
				else
				{
					$this->setLastError('update failed!', $message);
					return false;
				}
			}

			return $response;
		}

		public function save($execute=true)
		{
			if ($this->getID() == 0)
			{
				return $this->create($execute);
			}
			else
			{
				return $this->update($execute);
			}
		}

		public function destroy($execute=true)
		{
			return $this->kill($execute);
		}

		// TODO : take search infos into account when creating the DELETE command node => automatically create the WHERE node
		public function delete($execute=true) // NB : discarded if element has dependencies
		{
			if ($this->getID() == 0) return false;

			$this->createCommandNode('DELETE');
			$response = $this->executeNQLCommand($execute);

			if ($execute == true)
			{
				$message = $this->nql->getElement("/RESPONSE/MESSAGE");

				if (($message !== false) &&
					($message->valueOf("@msgType") == '0') && ($message->valueOf("@elementID") == $this->getID()))
				{
					return true;
				}
				else
				{
					$this->setLastError('delete failed!', $message);
					return false;
				}
			}

			return $response;
		}

		// TODO : take search infos into account when creating the KILL command node => automatically create the WHERE node
		public function kill($execute=true) // NB : delete element & its dependencies (not the elements in dependencies!)
		{
			if ($this->getID() == 0) return false;

			$this->createCommandNode('KILL');
			$response = $this->executeNQLCommand($execute);

			if ($execute == true)
			{
				$message = $this->nql->getElement("/RESPONSE/MESSAGE");

				if (($message !== false) &&
					($message->valueOf("@msgType") == '0') && ($message->valueOf("@elementID") == $this->getID()))
				{
					return true;
				}
				else
				{
					$this->setLastError('kill failed!', $message);
					return false;
				}
			}

			return $response;
		}
		
		public function duplicate($execute=true)
		{
			if ($this->getID() == 0) return false;

			$this->createCommandNode('DUPLICATE');
			$response = $this->executeNQLCommand($execute);

			if ($execute == true)
			{
				$message = $this->nql->getElement("/RESPONSE/MESSAGE");
				
				if (($message !== false) && ($message->valueOf("@msgType") == '0'))
				{
					$ID = $message->valueOf("./@elementID");			
					if ($ID == $this->getID())
					{
						$this->setLastError('duplicate failed!', 'ID returned is the same as the original element!');
						return false;
					}
					
					$this->setID($ID);
					return true;
				}
				else
				{
					$this->setLastError('duplicate failed!', $message);
					return false;
				}
			}

			return $response;
		}

		public function count($execute=true)
		{	
			$this->createCommandNode('COUNT');
            //$this->logCommand();
			$response = $this->executeNQLCommand($execute);

			if ($execute == true)
			{
				$result = $this->nql->getElement("/RESPONSE/RESULTS");

				if ($result !== false)
				{
					return $result->valueOf("./@totalCount");
				}
				else
				{
					$message = $this->nql->getElement("/RESPONSE/MESSAGE");
					$this->setLastError('count failed!', $message);
					return false;
				}
			}

			return $response;
		}
		
		public function emptyCache($generic=true)
		{
			$ID_attr = ($generic == true) ? 
						'' : 
						($this->getID() > 0) ? 
							'ID='.$this->getID() : 
							'';
						
			$this->createFromXML('
				<EMPTY>
					<CACHE>
						<'.$this->getDenomination().' '.$ID_attr.' />
					</CACHE>
				</EMPTY>
			');
			
			$this->executeNQLCommand(true);
			
			$message = $this->nql->getElement("/RESPONSE/MESSAGE");
			if (($message !== false) &&	($message->valueOf("@msgType") == '0'))
			{
				return true;
			}
			else
			{
				$this->setLastError('empty cache failed!', $message);
				return false;
			}
		}

		public function reset()
		{
			$this->createRootNode('<'.$this->getDenomination().'/>');
			$this->setID($this->getID());
		}

		/* INCOMING DATA PARSING */

		public function advancedSearchParsing(&$data_array)
		{
			$search_parms = array();
			$item_types = array(
				'field', 
				'operator', 
				'operand1', 
				'operand2', 
				'operand1_hh', 
				'operand1_mm', 
				'operand1_ss', 
				'operand2_hh', 
				'operand2_mm', 
				'operand2_ss', 
				'categ', 
				'group'
			);

			//var_dump($data_array);

			// 1st pass : collect all search parameters
			foreach ($data_array as $key => $value)
			{
				$key_infos = explode('-', $key);
				//var_dump($key_infos);
				//var_dump($value);
				
				// FORMATS EXPECTED :
					// search-field-[ID]
					// search-operator-[ID]
					// search-operand[1|2]-[ID]
					// search-categ-[ID] or search-operator-[ID] or search-group-[ID]
				$key_infos[0] = strtolower($key_infos[0]);
				if ($key_infos[0] != 'search')
				{
					continue; // discard
				}

				// field | operator | operand1 | operand2 | categ | group
				$item_type = strtolower($key_infos[1]);
				if (!in_array($item_type, $item_types))
				{
					continue; // discard
				}

				$item_id = $key_infos[2]; // 0..n
				if ($item_type == 'field')
				{
					// FORMATS EXPECTED :
						// main element =>
						//  [MODULE NAME]-[SERVICE NAME]-[FIELD NAME]-[TYPE]
						//
						// element in dependency =>
						//  dependency-[DEPENDENCY NAME]-[MODULE NAME]-[SERVICE NAME]-[FIELD NAME]-[TYPE]
						//  dependency-[DEPENDENCY NAME]-[MODULE NAME]-[exist|notexist]

					$value_infos = explode('-', $value);
					$search_parms[$item_id][$item_type] = $value_infos;
				}
				else // operator, operands or categories
				{
					$search_parms[$item_id][$item_type] = $value;
				}
			}

			//var_dump($search_parms);
			if (empty($search_parms)) // done
			{
				return;
			}
			
			//var_dump($search_parms);

			// 2nd pass : create/update all elements involved in the search
			$dependency_elements = array();

			foreach ($search_parms as $parm)
			{
				//var_dump($parm);
				
				/* CATEGORIES */
				if (isset($parm['categ']))
				{
					$this->setSearchCategoryOr($parm['categ'], $parm['operator'], $parm['group']);
					continue;
				}				
				
				/* FIELDS */
				$field_infos = $parm['field'];

				// handle special operators
				switch ($parm['operator'])
				{
					case 'today':
						$operator = '=';
						$operand1 = 'today';
						$operand2 = '';
						break;

					case 'thisweek':
						$operator = '=';
						$operand1 = 'this_week';
						$operand2 = '';
						break;

					case 'thismonth':
						$operator = '=';
						$operand1 = 'this_month';
						$operand2 = '';
						break;

					case 'thisyear':
						$operator = '=';
						$operand1 = 'this_year';
						$operand2 = '';
						break;

					case 'notdefined':
						$operator = 'starts-with';
						$operand1 = ($field_type == 'datetime') ? '0000-00-00 00:00:00' : '0000-00-00';
						$operand2 = '';
						break;

					case 'since':
						$operator = 'between';
						$operand1 = 'today-'.$parm['operand1'].' '.$parm['operand2'];
						$operand2 = 'today';
						break;
						
					case 'forthenext':
						$operator = 'between';
						$operand1 = 'today';
						$operand2 = 'today+'.$parm['operand1'].' '.$parm['operand2'];
						break;
						
					case 'dephits':
						$operator = $parm['operator'];
						$operand1 = abs($parm['operand1']);
						$operand2 = '';
						break;

					default:
						$operator = $parm['operator'];
						$operand1 = $parm['operand1'];
						$operand2 = $parm['operand2'];
						break;
				}

				if ($field_infos[0] != 'dependency') // main element
				{
					// FORMAT EXPECTED : [MODULE NAME]-[SERVICE NAME]-[FIELD NAME]-[TYPE]
					$module_name = strtoupper($field_infos[0]);
					if ($module_name != $this->getDenomination())
					{
						continue; // discard
					}

					$service_name = strtoupper($field_infos[1]);
					if ($service_name != 'INFO')
					{
						// TODO : $this->addSearchService($service_name, $field_name, $operator, $operand1, $operand2);
						continue; // discard
					}

					$field_name = $field_infos[2];

					$field_type = $field_infos[3];
					if ($field_type == 'date')
					{
						$operand1 = nql_date($operand1);
						$operand2 = nql_date($operand2);
					}
					else if ($field_type == 'datetime')
					{
						if (empty($parm['operand1_hh']) && empty($parm['operand1_mm']) && empty($parm['operand1_ss']))
						{
							$operand1 = nql_date($operand1);
						}
						else
						{
							$hhmmss1 .= ' '.sprintf('%02d', $parm['operand1_hh']);
							$hhmmss1 .= ':'.sprintf('%02d', $parm['operand1_mm']);
							$hhmmss1 .= ':'.sprintf('%02d', $parm['operand1_ss']);	
							$operand1 = nql_datetime($operand1.$hhmmss1);						
						}
						if (empty($parm['operand2_hh']) && empty($parm['operand2_mm']) && empty($parm['operand2_ss']))
						{
							$operand2 = nql_date($operand2);
						}
						else
						{
							$hhmmss2 .= ' '.sprintf('%02d', $parm['operand2_hh']);
							$hhmmss2 .= ':'.sprintf('%02d', $parm['operand2_mm']);
							$hhmmss2 .= ':'.sprintf('%02d', $parm['operand2_ss']);	
							$operand2 = nql_datetime($operand2.$hhmmss2);						
						}
					}

					// update this element
					$this->addSearchInfo($field_name, $operator, $operand1, $operand2);
				}
				else // element in dependency
				{
					//var_dump($field_infos);
					// FORMATS EXPECTED :
					//  dependency-[DEPENDENCY NAME]-[MODULE NAME]-[SERVICE NAME]-[FIELD NAME]-[TYPE]
					//  dependency-[DEPENDENCY NAME]-[MODULE NAME]-dephits
					$dependency_name = $field_infos[1];
					$module_name = strtoupper($field_infos[2]);

					$service_name = strtolower($field_infos[3]);
					if (($service_name != 'info') && ($service_name != 'dephits'))
					{
						// TODO : $this->setSearchService($service_name, $field_name, $operator, $operand1, $operand2);
						continue; // discard
					}

					if (!exists($dependency_elements[$dependency_name]))
					{
						// update elements array
						$dependency_elements[$dependency_name]['module'] = new Element($module_name);
					}
					
					if ($service_name == 'dephits')
					{
						$dependency_elements[$dependency_name]['operator'] = $operator;
						$dependency_elements[$dependency_name]['operand'] = $operand1;
					}
					else
					{
						//$dependency_elements[$dependency_name]['operator'] = '';
						
						$field_name = $field_infos[4];
						$field_type = $field_infos[5];
						if (($field_type == 'date') || ($field_type == 'datetime'))
						{
							$operand1 = nql_date($operand1);
							$operand2 = nql_date($operand2);
						}

						$dependency_elements[$dependency_name]['module']->addSearchInfo($field_name, $operator, $operand1, $operand2);
					}
				}
			}

			// 3rd pass : using the search config nql, we create the elements dependencies, by recursion
			if (!empty($dependency_elements))
			{
				$root_node = $this->search_config_xml->getElement('/CONFIGXML');
				$this->buildSearchNQL($dependency_elements, $root_node, '', '');
				
				unset($dependency_elements);
				$dependency_elements = null;
			}

			//$this->logCommand();
		}

		public function buildSearchNQL(&$elements, &$current_node, $parent_dependency, $parent_denomination)
		{
			$dependencies = $current_node->getElements('./DEPENDENCIES/DEPENDENCY');
			if (!empty($dependencies))
			{
				// recursive case
				foreach ($dependencies as $dependency)
				{
					$this->buildSearchNQL($elements, $dependency, $current_node->valueOf('./TYPE'), $current_node->valueOf('./MODULE'));
				}
			}

			// base case
			$current_dependency = $current_node->valueOf('./TYPE');
			//echo 'current dep: '; var_dump($current_dependency);
			if (exists($elements[$current_dependency]))
			{
				$current_element_data = $elements[$current_dependency];

				if (empty($parent_dependency))
				{
					$parent_element = $this;
				}
				else
				{
					// no info crit added for the parent element => no element created. So we do it now, on-the-fly...
					if (!exists($elements[$parent_dependency]['module']))
					{
						$elements[$parent_dependency]['module'] = new Element($parent_denomination);
					}

					$parent_element = $elements[$parent_dependency]['module'];
				}
				
				//echo 'parent dep: '; var_dump($parent_dependency);
				$parent_element->setSearchDependencyByElement($current_dependency, $current_element_data['module'], $current_element_data['operator'], $current_element_data['operand']);
				//echo 'parent element: '; $parent_element->logCommand(true, false);
			}
		}

		// TODO : ajouter préfixe avec un nom d'occurence ('parent', 'abonnement', ...) => un form peut envoyer
		// [nom unique de l'objet]-[nom du module]-[service]-[champ]-([format])
		// => possibilité de parser sur l'objet que l'on désire + automatiser création dépendances
		public function parse(&$data_array)
		{
			foreach ($data_array as $current_data => $current_value)
			{
				$field_infos = explode('-', $current_data);
				//var_dump($field_infos);

				switch (strtolower($field_infos[0]))
				{
					case 'info':
						// CONVENTION :
						// info-[MODULE NAME]-[FIELD NAME](-[FIELD TYPE]-[TYPE OPTIONS])
						// NAMESPACES ARE POSTFIXED WITH '__' instead of ':'
						
						$current_module = $this->format_namespace($field_infos[1]);
						if ($current_module != $this->getDenomination()) continue; // discard

						$current_field = $this->format_namespace($field_infos[2]);
						$current_field_type = strtolower($field_infos[3]);
						
						if ($current_field_type != 'xml')
						{
							$current_field_type_options = strtolower($field_infos[4]);
							$current_value = $this->format_field($current_value, $current_field_type, $current_field_type_options);
							// NB : if called twice, there will be a replacement, only the last one will be used (!= addInfo())
							$this->setInfo($current_field, $current_value);
						}
						else
						{
							$this->setXMLInfo($current_field, $current_value);
						}
						break;

					case 'description':
						// CONVENTION : description-[LANGUAGEID]-[MODULE NAME]-[FIELD NAME](-[FIELD TYPE]-[TYPE OPTIONS])
						// Custom fields : description-[LANGUAGEID]-[MODULE NAME]-CUSTOM-[CUSTOM FIELD NAME](-[FIELD TYPE]-[TYPE OPTIONS])
						$current_module = $this->format_namespace($field_infos[2]);
						if ($current_module != $this->getDenomination()) continue; // discard

						$languageID = strtolower($field_infos[1]);

						$current_field = strtoupper($field_infos[3]);

						if ($current_field == 'CUSTOM')
						{
							$custom_field = $field_infos[4];
							$custom_field_type = $field_infos[5];
							$custom_field_type_options = strtolower($field_infos[6]);

							$current_value = $this->format_field($current_value, $custom_field_type, $custom_field_type_options);

							// NB : if called twice, there will be a replacement, only the last one will be used (!= addInfo())
							$this->setCustomDescription($languageID, $custom_field, $current_value);
						}
						else
						{
							$current_field_type = $field_infos[4];
							$current_field_type_options = strtolower($field_infos[5]);

							$current_value = $this->format_field($current_value, $current_field_type, $current_field_type_options);
							
							// NB : if called twice, there will be a replacement, only the last one will be used (!= addInfo())
							$this->setDescription($languageID, $current_field, $current_value);
						}
						break;
						
					case 'dependency':
						// CONVENTION : dependency-[DEPENDENCY TYPE]-[MODULE NAME]-info-[FIELD NAME](-[FIELD TYPE]-[TYPE OPTIONS])						
						$current_dependency = $this->format_namespace($field_infos[1], false);
						if (empty($current_dependency)) continue;
						
						$target_module = $this->format_namespace($field_infos[2]);
						if (empty($target_module)) continue;
						
						$target_element = new Element($target_module);
												
						$target_field = $this->format_namespace($field_infos[4]);
						$target_field_type = $field_infos[5];
						$target_field_type_options = strtolower($field_infos[6]);
						$current_value = $this->format_field($current_value, $target_field_type, $target_field_type_options);
						$target_element->setInfo($target_field, $current_value);
						
						$this->setDependencyByElement($current_dependency, $target_element, '', '');
						unset($target_element);
						break;
						
					case 'category':
						// CONVENTION : category-[MODULE NAME]-[CATEGORY NAME]
						$current_module = $this->format_namespace($field_infos[1]);
						if ($current_module != $this->getDenomination()) continue; // discard
						
						$current_category = $field_infos[2];
						
						if ($current_value == '1')
						{
							$this->setCategory($current_category);
						}		
						break;
						
					case 'publisher-login':
						$this->setPublisher('login', $current_value);
						break;
						
					case 'publisher-password':
						$this->setPublisher('password', $current_value);
						break;

					default:
						continue; // discard
						break;
				}
			}

			return $this->parseFiles();
		}
		
		public function parseFiles()
		{
			$this->files_2_upload = array();

			foreach ($_FILES as $file_key => $file_data)
			{			
				$field_infos = explode('-', $file_key);
				
				switch (strtolower($field_infos[0]))
				{	
					case 'info':
						// CONVENTION : info-[MODULE NAME]-[FIELD NAME]-file
						// NAMESPACES ARE POSTFIXED WITH '__' instead of ':'
						$current_module = $this->format_namespace($field_infos[1]);
						if ($current_module != $this->getDenomination()) continue; // discard
						
						$current_field = $this->format_namespace($field_infos[2]);
						
						$current_field_type = $field_infos[3];						
						if ($current_field_type != 'file') continue; // discard
						
						$this->files_2_upload[$file_key] = $current_field;
						break;
				
					default:
						continue;
						break;
				}
			}

			return true;
		}
		
		public function getFiles2Upload()
		{
			return $this->files_2_upload;
		}
		public function noFiles2Upload()
		{
			return (count($this->files_2_upload) == 0);
		}
		
		public function uploadFiles($update=false, $execute=true)
		{
			$result = true;
			
			if (empty($this->files_2_upload)) return $result; // nothing to do
			
			if ($update == false)
			{
            	foreach ($this->files_2_upload as $input_name => $field_name)
	            {
	                if ($this->uploadFile($input_name, $field_name) == false)
		            {
		            	$result = false;		                
		            }
	            }
	            
  		        if ($result == true)
	            {
            		$this->files_2_upload = array();
            	}
			}
			else
			{	
            	$this->reset(); // update only file-related INFO fields
            	
            	foreach ($this->files_2_upload as $input_name => $field_name)
	            {
	                if ($this->uploadFile($input_name, $field_name) == false)
		            {
		            	$result = false;		                
		            }
	            }
	            
            	if ($result == true)
	            {
            		$this->files_2_upload = array();
            	}
            	
			    if ($this->update($execute) == false)
            	{
                	$this->setLastError('update after upload failed => '.$this->getLastError());
		        }
			}
		                       
            return $result;
		}
		
        protected function uploadFile($input_name, $field_name, $dest_path='') // TODO : multiple uploads
        {
        	//var_dump($_FILES);
        	if (!isset($_FILES[$input_name])) return true; // discard
        	        	
            $folderpath = (empty($dest_path)) ? '/'.strtolower($this->getDenomination()).'/'.$this->getID() : $dest_path;
            //var_dump($folderpath);
			$folder = new Folder($folderpath);
			$folder->create();
			if (!$folder->exists())
			{
				$this->setLastError('upload failed => cannot create folder '.$folderpath.'!');
                return false;
			}

            $uploader = new FilesUploader();
            $uploader->setTarget($folder);
            $uploader->addFile($_FILES[$input_name]);

            if ($uploader->execute() == false)
            {
                $file_infos = '"'.$folderpath.'/'.$_FILES[$input_name]['name'];
                $file_infos .= '" ('.$_FILES[$input_name]['type'];
                $file_infos .= ' - '.$_FILES[$input_name]['size'].' bytes)';
                $this->setLastError('upload failed => '.PHP_EOL.PHP_EOL.$file_infos);
                return false;
            }

            $files = $uploader->getFiles();           
            if (is_object($files[0]))
            {
            	//var_dump($files[0]->getPath());
                $this->setInfo($field_name, $files[0]->getPath());
            }

            return true;
        }
        
        public function removeFile($field, $execute=true)
        {
        	$field = strtoupper($field);
            if (empty($field))
            {
                return true;
            }
        	
            $this->setReturn(array('INFO/'.$field));
            if ($this->get($execute) == false) return false;

            $file_location = $this->getInfo($field);
            if (empty($file_location))
            {
                return true;
            }

            $this->setInfo($field, '');
            if ($this->update($execute) == false) return false;

            $file = new File($file_location);
            if ($execute == true) $file->unlink();

            return true;
        }

		protected function format_namespace($string, $to_upper=true)
		{
			if ($to_upper == true) $string = strtoupper($string);
			return str_replace('__', ':', $string);
		}

		protected function format_field($current_value, $current_field_type, $current_field_type_options)
		{
			// TODO : all field types & options
			switch ($current_field_type)
			{
				case 'date':
					// DATE TYPE OPTIONS : conversion from european format to NQL format
					// (e.g. 21/07/2009 => 2009-07-21)
					$current_value = nql_date($current_value);
					break;

				case 'datetime':
					// DATE TYPE OPTIONS : ddmmyyyy => conversion from european format to NQL format
					// (e.g. 21/07/2009 12:34:55 => 2009-07-21 12:34:55)
					$current_value = nql_datetime($current_value);
					break;

				default:
					break;
			}

			return $current_value;
		}
		
		/* PUBLISHER NODE */
		
		public function setPublisher($node, $value)
		{
			// just in case
			if ($this->getPublisherNode() === false)
			{
				$this->getElementNode()->appendChild('<PUBLISHER/>');
			}
			// replace if it already exists
			$this->createPublisherNode($node, $value);
		} 

		/* INFO NODES OPERATIONS */

		public function setInfo($node_name, $node_value, $xml_encode=true)
		{
			// just in case
			if ($this->getInfoNode() === false)
			{
				$this->getElementNode()->appendChild('<INFO/>');
			}
			// replace if it already exists
			$this->createInfoNode($node_name, $node_value, 'replace', $xml_encode);
		}

		public function addInfo($node_name, $node_value, $xml_encode=true)
		{
			// just in case
			if ($this->getInfoNode() === false)
			{
				$this->getElementNode()->appendChild('<INFO/>');
			}
			// append regardless if it already exists
			$this->createInfoNode($node_name, $node_value, 'append', $xml_encode);
		}

		public function getInfo($node_name)
		{
			$info_node = $this->getInfoNode();

			if ($info_node === false) return false;

			$node_name = strtoupper($node_name);

			return $info_node->valueOf('./'.$node_name);
		}

		public function removeInfo($node_name)
		{
			$info_node = $this->getInfoNode();

			if ($info_node === false) return false;

			$node_name = strtoupper($node_name);

			return $info_node->removeChild('./'.$node_name);
		}

		/* *** */

		public function setXMLInfo($node_name, $node_value)
		{
			// just in case
			if ($this->getInfoNode() === false)
			{
				$this->getElementNode()->appendChild('<INFO/>');
			}
			// replace if it already exists
			$this->createInfoNode($node_name, $node_value, 'replace', false); // no encoding
		}
		
		public function getXMLInfo($node_name)
		{
			$node_name = strtoupper($node_name);
			$info_node = $this->getInfoNode();

			if ($info_node === false) return false;

			return $info_node->copyOf('./'.$node_name);
		}

	    // CATEGORIES

        public function setSearchCategory($ID_name_or_path, $operator='descendant') 
        {
            $attr_name = (strpos($ID_name_or_path, '/') === false) ? (is_numeric($ID_name_or_path) ? 'ID' : 'name') : 'path';

			$this->createCategoriesNode(); // just in case

			if ($this->getCategoryNode($attr_name, $ID_name_or_path) === false)
			{
				$node_string = '<CATEGORY '.$attr_name.'="'.$ID_name_or_path.'" operator="'.$operator.'"/>';
				$this->getCategoriesNode()->appendChild($node_string);
			}
        }
        
        public function setSearchCategoryOr($ID_name_or_path, $operator='descendant', $group_name='default_group') 
        {
            $attr_name = (strpos($ID_name_or_path, '/') === false) ? (is_numeric($ID_name_or_path) ? 'ID' : 'name') : 'path';

			$this->createCategoriesNode(); // just in case

			if ($this->getCategoryNode($attr_name, $ID_name_or_path) === false)
			{
				$node_string = '<CATEGORY '.$attr_name.'="'.$ID_name_or_path.'" operator="'.$operator.'" or_group="'.$group_name.'"/>';
				$this->getCategoriesNode()->appendChild($node_string);
			}
        }
        
        public function setSearchCategoryAnd($ID_name_or_path, $operator='descendant', $group_name='default_group') 
        {
            $attr_name = (strpos($ID_name_or_path, '/') === false) ? (is_numeric($ID_name_or_path) ? 'ID' : 'name') : 'path';

			$this->createCategoriesNode(); // just in case

			if ($this->getCategoryNode($attr_name, $ID_name_or_path) === false)
			{
				$node_string = '<CATEGORY '.$attr_name.'="'.$ID_name_or_path.'" operator="'.$operator.'" and_group="'.$group_name.'"/>';
				$this->getCategoriesNode()->appendChild($node_string);
			}
        }
        
        public function setCategory($ID_name_or_path, $operation='')
        {
            $attr_name = (strpos($ID_name_or_path, '/') === false) ? (is_numeric($ID_name_or_path) ? 'ID' : 'name') : 'path';

			$this->createCategoriesNode($operation); // just in case

			if ($this->getCategoryNode($attr_name, $ID_name_or_path) === false)
			{
				$node_string = '<CATEGORY '.$attr_name.'="'.$ID_name_or_path.'" />';
				$this->getCategoriesNode()->appendChild($node_string);
			}
        }

        public function removeAllCategories()
        {
            $this->getElementNode()->removeChild('./CATEGORIES');
			$this->createCategoriesNode();
        }

        public function removeSubCategories($fatherID)
        {
			$this->createCategoriesNode('remove'); // just in case

			if ($this->getCategoryNode('fatherID', $fatherID) === false)
			{
				$node_string = '<CATEGORY fatherID="'.$fatherID.'" />';
				$this->getCategoriesNode()->appendChild($node_string);
			}
        }

		/* *** */

		public function setSearchInfo($node_name, $operator, $operand1, $operand2='')
		{
			// just in case
			if ($this->getInfoNode() === false)
			{
				$this->getElementNode()->appendChild('<INFO/>');
			}
			// replace if it already exists
			$this->createSearchInfoNode($node_name, $operator, $operand1, $operand2, 'replace');
		}

		public function addSearchInfo($node_name, $operator, $operand1, $operand2='')
		{
			// just in case
			if ($this->getInfoNode() === false)
			{
				$this->getElementNode()->appendChild('<INFO/>');
			}
			// replace if it already exists
			$this->createSearchInfoNode($node_name, $operator, $operand1, $operand2, 'append');
		}

		public function setSearchDependencyByName($type_name, $operator='exist') // operator = exist | none
		{
			$this->createDependenciesNode(); // just in case

			if ($this->getDependencyNode($type_name) === false)
			{
				$operator_attribute = (!empty($operator)) ? 'operator="'.$operator.'"' : '';
				$node_string = '<DEPENDENCY type="'.$type_name.'" '.$operator_attribute.'/>';
				$this->getDependenciesNode()->appendChild($node_string);
			}
		}

		/*public function setSearchDependencyByElement($type_name, $target_element, $operator='exist')
		{
			$this->createDependenciesNode(); // just in case

			if ($this->getDependencyNode($type_name) === false)
			{
				$operator_attribute = (!empty($operator)) ? 'operator="'.$operator.'"' : '';
				$node_string = '<DEPENDENCY type="'.$type_name.'" '.$operator_attribute.'/>';
				$this->getDependenciesNode()->appendChild($node_string);
			}

			$module_element_node = $target_element->getElementNode();
			$module_element_string = $module_element_node->toString();

			$this->getDependencyNode($type_name)->appendChild($module_element_string);
		}*/
		// operator = 'exist' | 'none' / =, LT, LT=, GT; GT= ; operand = '' / [hits count]
		public function setSearchDependencyByElement($type_name, $target_element, $operator='exist', $operand='')
		{
			$this->createDependenciesNode(); // just in case

			if ($this->getDependencyNode($type_name) === false)
			{
				if (!empty($operator)) // hits count or existence
				{
					if (($operator == 'exist') || ($operator == 'none'))
					{
						$node_string = '<DEPENDENCY type="'.$type_name.'" operator="'.$operator.'" />';					
					}
					else
					{
						$node_string = '<DEPENDENCY type="'.$type_name.'" />';
						$hits_string = '<HITS operator="'.$operator.'">'.$operand.'</HITS>';
						$target_element->getElementNode()->appendChild($hits_string);
					}					
				}
				else // no operator
				{
					$node_string = '<DEPENDENCY type="'.$type_name.'" />';
				}
				
				$this->getDependenciesNode()->appendChild($node_string);
				
				$target_element_string = $target_element->getElementNode()->toString();
				$this->getDependencyNode($type_name)->appendChild($target_element_string);
			}
		}

		/* *** */
		
		public function setAncestorByElement($target_element, $type='')
		{
			// just in case
			if ($this->getAncestorNode() !== false)
			{
				$this->getElementNode()->removeChild('./ANCESTOR');
			}
			
			$ancestor_xml_string = '<ANCESTOR';
			if (!empty($type)) $ancestor_xml_string .= ' type="'.$type.'"';
			$ancestor_xml_string .= ' />';
			$this->getElementNode()->appendChild($ancestor_xml_string);
			
			$module_element_node = $target_element->getElementNode();
			$module_element_string = $module_element_node->toString();

			$this->getAncestorNode()->appendChild($module_element_string);
		}
		
		/* *** */

		public function setElementsByID($IDs)
		{
			// just in case
			if ($this->getInfoNode() !== false)
			{
				$this->getElementNode()->removeChild('./INFO');
			}

			$this->getElementNode()->appendChild('<INFO/>');

			$info_node = $this->getInfoNode();

			foreach ($IDs as $ID)
			{
				$info_node->appendChild('<ID>'.$ID.'</ID>');
			}
		}

		/* DEPINFO */

		public function setDepInfo($node_value)
		{
			if (is_string($node_value))
			{
				$node_value = stripcslashes($node_value);
			}
			$node_value = encode_to_xml($node_value);
			$node_string = '<DEPINFO>'.$node_value.'</DEPINFO>';

			// just in case
			if ($this->getDepInfoNode() !== false)
			{
				$this->getElementNode()->removeChild('./DEPINFO');
			}
			
			$this->getElementNode()->appendChild($node_string);
		}

		/* DEPENDENCY NODES SETTERS & GETTERS */

		public function setDependencyByElement($type_name, $target_element, $mode='normal', $operation='append')
		{			
			$this->createDependenciesNode(); // just in case
			
			if ($this->getDependencyNode($type_name) === false)
			{
				$mode_attribute = (!empty($mode)) ? 'mode="'.$mode.'"' : '';
				$operation_attribute = (!empty($operation)) ? 'operation="'.$operation.'"' : '';

				$node_string = '<DEPENDENCY type="'.$type_name.'" '.$mode_attribute.' '.$operation_attribute.' />';

				$this->getDependenciesNode()->appendChild($node_string);
			}

			// NB : if target element is null, we can use default "operation=replace" to remove the dependency
			if ($target_element != null)
			{
				//$target_ID = $target_element->getID();

				/*if ($target_ID > 0)
				{
					$module_element_string = '<'.$target_element->getDenomination().' ID="'.$target_ID.'"/>';
				}
				else
				{*/
					$module_element_node = $target_element->getElementNode();
					$module_element_string = $module_element_node->toString();
				/*}*/

				$this->getDependencyNode($type_name)->appendChild($module_element_string);
			}
		}

		public function setDependencyByName($type_name, $target_name, $target_ID, $mode='normal', $operation='append') // append|remove|replace
		{
			$this->createDependenciesNode(); // just in case

			if ($this->getDependencyNode($type_name) === false)
			{
				$mode_attribute = (!empty($mode)) ? 'mode="'.$mode.'"' : '';
				$operation_attribute = (!empty($operation)) ? 'operation="'.$operation.'"' : '';

				$node_string = '<DEPENDENCY type="'.$type_name.'" '.$mode_attribute.' '.$operation_attribute.' />';

				$this->getDependenciesNode()->appendChild($node_string);
			}

			if (!empty($target_name) && !empty($target_ID))
			{
				$module_element_string = '<'.$target_name.' ID="'.$target_ID.'"/>';
				$this->getDependencyNode($type_name)->appendChild($module_element_string);
			}
		}

		public function removeDependency($type_name)
		{
			$dependencies_node = $this->getDependenciesNode();
			if ($dependencies_node === false) return false;

			return $dependencies_node->removeChild('./DEPENDENCY[@type="'.$type_name.'"]');
		}

		public function removeDependencyTargets($type_name)
		{
			$dependency_node = $this->getDependencyNode($type_name);
			if ($dependency_node === false) return false;

			return $dependency_node->removeChild('./*');
		}

		/* DESCRIPTIONS */

		public function setDescription($languageID, $node_name, $node_value)
		{
			$this->createDescriptionsNode(); // just in case

			if ($this->getLanguageDescriptionNode($languageID) === false)
			{
				$node_string = '<DESCRIPTION languageID="'.$languageID.'"/>';
				$this->getDescriptionsNode()->appendChild($node_string);
			}

			$this->createDescriptionNode($languageID, $node_name, $node_value, 'replace');
		}

		public function getDescription($languageID, $node_name)
		{
			$language_description_node = $this->getLanguageDescriptionNode($languageID);
			if ($language_description_node === false) return false;

			$node_name = strtoupper($node_name);

			return $language_description_node->valueOf('./'.$node_name);
		}

		public function removeDescription($languageID, $node_name)
		{
			$language_description_node = $this->getLanguageDescriptionNode($languageID);
			if ($language_description_node === false) return false;

			return $language_description_node->removeChild('./'.$node_name);
		}

		public function removeLanguageDescription($languageID)
		{
			$descriptions_node = $this->getDescriptionsNode();
			if ($descriptions_node === false) return false;

			return $description_node->removeChild('./DESCRIPTION[@languageID="'.$languageID.'"]');
		}

		public function setCustomDescription($languageID, $custom_node_name, $custom_node_value)
		{
			$this->createDescriptionsNode(); // just in case

			if ($this->getLanguageDescriptionNode($languageID) === false)
			{
				$node_string = '<DESCRIPTION languageID="'.$languageID.'"/>';
				$this->getDescriptionsNode()->appendChild($node_string);
			}

			$this->createCustomDescriptionNode($languageID, $custom_node_name, $custom_node_value);
		}

		/* GET/SEARCH OPERATIONS */

		public function setNoReturn()
		{
			$this->createMainReturnNode(); // just in case
			$this->createReturnServiceNode('NOTHING');
		}
		
		public function addXMLReturn($xml, $depth='')
		{
			$this->createMainReturnNode($depth); // just in case
			$this->getReturnNode()->appendChild($xml);
		}

		public function setReturnService($service_name, $depth='', $attribute_name='', $attribute_value='')
		{	
			$this->createMainReturnNode($depth); // just in case
			$this->createReturnServiceNode($service_name, $attribute_name, $attribute_value);
		}
		
		public function setReturnDependencyByType($type_name, $depth='2')
		{
			$this->setReturnService('DEPENDENCIES', $depth);
			$this->createReturnDependencyNode($type_name);
		}

		public function setReturn($fields_array, $depth='')
		{			
			$this->createMainReturnNode($depth); // just in case
			
			$count = count($fields_array);

			for ($i=0; $i<$count; $i++)
			{
				list($service, $node_name) = explode('/', $fields_array[$i]);
				$service = strtoupper($service);

				// TODO : DESCRIPTIONS
				switch ($service)
				{
					case 'INFO':
						$this->createReturnServiceNode('INFO');
						if (!empty($node_name))
						{
							$this->createReturnInfoNode($node_name, $i+1);
						}
						break;

					default:
						// FORMAT EXPECTED :
						//  e.g. : DEPENDENCY[@type=\"ipa:memberOf\"]/IPA:SOCIETY_TYPE
						if (substr($service, 0, 10) == 'DEPENDENCY')
						{
							$service = stripcslashes($service);
							$service_slices = explode('"', $service);

							$dependency_type = $service_slices[1];
							$this->setReturnDependencyByType($dependency_type, $depth);

							if (!empty($node_name))
							{
								$this->createReturnDependencyServiceNode($dependency_type, 'INFO');
								$this->createReturnDependencyInfoNode($dependency_type, $node_name, $i+1);
							}
						}
						else if (substr($service, 0, 11) == 'DESCRIPTION')
						{
							$this->createReturnServiceNode('DESCRIPTION');
							if (!empty($node_name))
							{
								$this->createReturnDescriptionNode($node_name, $i+1);
							}
						}
						break;
				}
			}

			//$this->logCommand();
		}
		
		public function setSearchReturn($fields_array, $depth='')
		{			
			$this->createMainReturnNode($depth); // just in case
			
			$count = count($fields_array);

			for ($i=0; $i<$count; $i++)
			{
				list($service, $node_name) = explode('/', $fields_array[$i]);

				// TODO : DESCRIPTIONS
				switch (strtoupper($service))
				{
					case 'INFO':
						$this->createReturnServiceNode('INFO');
						if (!empty($node_name))
						{
							$this->createReturnInfoNode($node_name, $i+1);
							
						    if (($node_name == 'CREATORID') !== false)
						    {
						    	$this->addReturnInfoAttribute('creator_info', 'small');
						    }
						    if (($node_name == 'MODIFIERID') !== false)
						    {
						    	$this->addReturnInfoAttribute('modifier_info', 'small');
						    }
							if (($node_name == 'OWNERID') !== false)
						    {
						    	$this->addReturnInfoAttribute('owner_info', 'small');
						    }
						}
						break;
						
					case 'CATEGORIES':
						$this->createReturnServiceNode('CATEGORIES', 'order', $i+1);
						break;

					default:
						// FORMAT EXPECTED :
						//  e.g. : DEPENDENCY[@type=\"ipa:memberOf\"]/IPA:SOCIETY_TYPE
						if (substr($service, 0, 10) == 'DEPENDENCY')
						{
							$service = stripcslashes($service);
							$service_slices = explode('"', $service);

							$dependency_type = $service_slices[1];
							$this->setReturnDependencyByType($dependency_type, $depth);

							if (!empty($node_name))
							{
								$this->createReturnDependencyServiceNode($dependency_type, 'INFO');
								$this->createReturnDependencyInfoNode($dependency_type, $node_name, $i+1);
								
								if (($node_name == 'CREATORID') !== false)
							    {
							    	$this->addReturnDepInfoAttribute($dependency_type, 'creator_info', 'small');
							    }
							    if (($node_name == 'MODIFIERID') !== false)
							    {
							    	$this->addReturnDepInfoAttribute($dependency_type, 'modifier_info', 'small');
							    }
								if (($node_name == 'OWNERID') !== false)
							    {
							    	$this->addReturnDepInfoAttribute($dependency_type, 'owner_info', 'small');
							    }
							}
						}
						else if (substr($service, 0, 11) == 'DESCRIPTION')
						{
							$this->createReturnServiceNode('DESCRIPTION');
							if (!empty($node_name))
							{
								$this->createReturnDescriptionNode($node_name, $i+1);
							}
						}
						break;
				}
			}

			//$this->logCommand();
		}
		
		public function addReturnInfoAttribute($attr_name, $attr_value)
		{
			$return_info_node = $this->getReturnNode('INFO');
			if ($return_info_node === false)
			{
				$this->createReturnServiceNode('INFO', $attr_name, $attr_value);
			}
			else
			{
				$return_info_node->setAttribute($attr_name, $attr_value);
			}
		}
		
		public function addReturnDepInfoAttribute($dep_type, $attr_name, $attr_value)
		{
			$return_info_node = $this->getReturnDependencyNode($dep_type, 'INFO');
			if ($return_info_node === false)
			{
				$this->createReturnDependencyServiceNode($dep_type, 'INFO');
			}
			
			$return_info_node = $this->getReturnDependencyNode($dep_type, 'INFO');
			$return_info_node->setAttribute($attr_name, $attr_value);
		}
		
		/* SEARCH OPERATIONS */

		public function setSort(&$node_name, &$order, $data_type='')
		{
			$this->createSortNode();

			$node_name = exists($node_name) ? strtoupper($node_name) : DEFAULT_SORT_FIELD;
			$order = exists($order) ? strtolower($order) : DEFAULT_SORT_ORDER;

			$sort_node = $this->getSortNode();
			$sort_node->setAttribute('select', $node_name);
			$sort_node->setAttribute('order', $order);

			if (!empty($data_type))
			{
				$sort_node->setAttribute('data-type', $data_type);
			}
		}

		public function setDefaultSort($node_name=DEFAULT_SORT_FIELD, $order=DEFAULT_SORT_ORDER, $data_type='')
		{
			$this->setSort($node_name, $order, $data_type);
		}

		public function setPaginate(&$display, &$page)
		{			
			$this->createPaginateNode();

			$display = exists($display) ? $display : DEFAULT_PAGINATION;
			$page = exists($page) ? $page : 1;

			$paginate_node = $this->getPaginateNode();
			$paginate_node->setAttribute('display', $display);
			$paginate_node->setAttribute('page', $page);
		}

		public function setDefaultPaginate($display=DEFAULT_PAGINATION, $page=1)
		{
			$this->setPaginate($display, $page);
		}

		public function buildSearchFilter()
		{
			$element_node = $this->getElementNode();

			$filter = new FilterElement();

			// add the <QUERYXML> content
			$query_nodes = $element_node->getElements('./*');

			if (!empty($query_nodes))
			{
				$filter->setInfo('ISQUERY', 1);
				$query_node = $filter->getInfoNode('QUERYXML');
				$query_node->appendChild($element_node->toString());
			}
			else
			{
				$filter->setInfo('ISQUERY', 0);
			}

			// add the <DISPLAYXML> content
			$display_nodes = array();

			if (($return_node = $this->getReturnNode()) !== false)
			{
				// NB : the order attribute has been set in setReturn()
				$display_nodes[] = $return_node;
			}
			if (($sort_node = $this->getSortNode()) !== false)
			{
				$display_nodes[] = $sort_node;
			}
			if (($paginate_node = $this->getPaginateNode()) !== false)
			{
				$display_nodes[] = $paginate_node;
			}

			if (!empty($display_nodes))
			{
				$filter->setInfo('ISDISPLAY', 1);

				$display_node = $filter->getInfoNode('DISPLAYXML');
				foreach ($display_nodes as $node)
				{
					$display_node->appendChild($node->toString());
				}
			}
			else
			{
				$filter->setInfo('ISDISPLAY', 0);
			}

			return $filter;
		}

		public function applySearchFilter(&$target_filter, $apply_query=true, $apply_display=true)
		{
			$this->createCommandNode('SEARCH');
			$element_node = $this->getElementNode();

			if ($apply_query == true)
			{
				$query_nodes = $target_filter->getInfoNode('QUERYXML')->getElements('./*');
				// the filter <QUERYXML> node should have a single child : the element node
				$query_element_node = $query_nodes[0];

				// check if it matches the name of this module
				if (strtoupper($query_element_node->nodeName()) == $this->getDenomination())
				{
					$target_query_nodes = $query_element_node->getElements('./*');

					foreach ($target_query_nodes as $query_node)
					{
						$element_node->removeChild($query_node->nodeName());
						$element_node->appendChild($query_node->toString());
					}
				}
			}

			if ($apply_display == true)
			{
				$command_node = $this->getCommandNode();

				$target_display_nodes = $target_filter->getInfoNode('DISPLAYXML')->getElements('./*');

				foreach ($target_display_nodes as $display_node)
				{
					$command_node->removeChild($display_node->nodeName());

					/*if ($display_node->nodeName() == 'RETURN')
					{
						$return_nodes = $display_node->getElements('.//*[@order]');
						foreach ($return_nodes as &$return_node)
						{
							$return_node->removeAttribute('order');
						}
					}*/
					$command_node->appendChild($display_node->toString());
				}
			}
		}

		/*public function fetchFields($field, $value, $display=DEFAULT_PAGINATION)
		{
			$field = strtoupper($field);

			$this->addSearchInfo($field, 'contains', $value);
			$this->setReturn(array('INFO/'.$field));
			$this->setDefaultPaginate($display);
			$this->search();

		}*/

		/* EXPORTS */

		public function export2PDF(&$report, $use_current_display=true, $restore_report_filter=false)
		{ 
			$nql = new Sushee_Shell(false);

            if (method_exists($this, 'enablePDFNl2br'))
            {
                $nql->enablePDFNl2br();
            }

			$nql->addCommand('
				<GET name="user">
					<CONTACT ID="visitor" />
					<RETURN>
						<INFO>
							<FIRSTNAME/><LASTNAME/><EMAIL1/><DENOMINATION/>
						</INFO>
					</RETURN>
				</GET>
			');

			$nql->addCommand('
				<GET name="original-template">
					<OFFICITY:REPORT ID="'.$report->getID().'" />
					<RETURN>						
                        <DESCRIPTIONS/>
					</RETURN>
				</GET>
			');
            
            $report_command = $report->getStaticCommand('template');
			$nql->addCommand($report_command);
            //die($report_command);

            $filter_usage = ($restore_report_filter == true) ? 'report' : 'user';
			$search_filter = FilterElement::restore_last_filter($this->getDenomination(), $filter_usage);

			if ($use_current_display == false)
			{
				$search_filter->cloneDisplay($report);
			}

            $this->initSearchConfigCommand($nql);
            $this->addSearchCommands($nql, $search_filter);
            //die($nql->getQuery());

			$this->applySearchFilter($search_filter);
			$this->setDefaultPaginate(ReportElement::getMaxPagination());
			$this->search('data', false);
			$search_command = $this->getNQLCommand();
			$nql->addCommand($search_command);
			//die($search_command);

            $additional_query = $report->getQueryString();
            if (!empty($additional_query))
            {
                $nql->addCommand($additional_query);
            }

			$template_stylesheet = $report->getInfo('TEMPLATE');
			if (empty($template_stylesheet))
			{
				 $template_stylesheet = 'pdf/reporting.xsl';
			}
            else // Avery stickers
            {
                // COUNTRYID may not be SEARCHABLE, so, addSearchCommands hasn't added the countries list needed by stickers_avery.xsl
                $nql->addCommand('
                    <GET name="countries" refresh="monthly">
                        <COUNTRIES/>
                    </GET>
                ');
            }

            //die($nql->getQuery());
            //var_dump(file_exists(SYSTEM_TOOLS_PATH.'report/'.$template_stylesheet));
            //die(SYSTEM_TOOLS_PATH.'report/'.$template_stylesheet);
			$pdf_folder = '/pdf';
			$pdf_folder = new Folder($pdf_folder);
			if (!$pdf_folder->exists())
			{
				if ($pdf_folder->create() == false)
                {
                    $this->setLastError('PDF folder creation failed! ('.$pdf_folder.')');
                    return false;
                }
			}
            			
			//xml_out($nql->transform(SYSTEM_TOOLS_PATH.'report/'.$template_stylesheet)); // FO output
			$pdf = $nql->transformToPDF(SYSTEM_TOOLS_PATH.'report/'.$template_stylesheet, false);
			if (empty($pdf))
			{
                $this->setLastError('transformToPDF failed!');
				return false;
			}

			/* *** */

			$user = new OfficityUser();
			$folderpath = '/contact/'.$user->getID().'/reports/pdf/'.date('Y').'/'.date('m');

			$folder = new Folder($folderpath);
			if (!$folder->exists())
			{
				if ($folder->create() == false)
                {
                    $this->setLastError('folder creation failed! ('.$folderpath.')');
                    return false;
                }
			}

			$title = cleanFilename($report->getInfo('TITLE'));
			$filename = date('YmdHis').'-'.$title.'.pdf';
			$target_filename = $folder->getPath().$filename;

			$move_nql = new Sushee_Shell(false);

			$move_nql->addCommand('
				<FILEORDIRECTORY action="rename">
					<SOURCE>'.$pdf.'</SOURCE>
					<TARGET>'.$target_filename.'</TARGET>
				</FILEORDIRECTORY>
			');

			$move_nql->execute();

			/* *** */

            $report->removeID();
			$report->removeInfo('OWNERID');
			$report->removeInfo('CREATORID');
			$report->removeInfo('GROUPID');
			$report->removeInfo('MODIFIERID');
			$report->setInfo('FILE', $target_filename);
			$report->setInfo('TYPE', 'pdf');
			$report->setInfo('MODEL', 0);
            $report_date = $report->getInfo('DATE');
            if (empty_date($report_date))
            {
                $report->setInfo('DATE', date('Ymd'));
            }
            $report->setInfo('QUERYXML', $search_command, false); // do not encode node value to XML
			$report->create(); // TODO : check result

			return $target_filename;
		}

		public function export2CSV(&$report, $use_current_display=true, $encoding='utf8', $separator='semicolon')
		{
            $nql = new Sushee_Shell(false);

            $search_filter = FilterElement::restore_last_filter($this->getDenomination());
			if ($use_current_display == false)
			{
				$search_filter->cloneDisplay($report);
			}

            $this->initSearchConfigCommand($nql);
            $this->addSearchCommands($nql, $search_filter, false); // do not add static filter command to $nql - see below
            //die($nql->getQuery());

			$this->applySearchFilter($search_filter);
			$this->setDefaultPaginate(ReportElement::getMaxPagination());
			$this->search('data', false);
			$search_command = $this->getNQLCommand();
            $filter_command = $search_filter->getStaticCommand('search-filter');
			//die($search_command);
			//die($filter_command);

			// check if recursivity is needed
			$search_xml = new XML($search_command);
			$search_node = $search_xml->getElement('/SEARCH');
			$deps = $search_node->getElements('RETURN//DEPENDENCY');
			$maxs = $search_node->getElements('RETURN//DEPENDENCY[not(@max)]');

			if ($deps && $maxs)
			{
				// there are dependencies without limits
				// process results to obtain the max dependency items for each dependency types
	 			// update search filter command accordingly
				$max_nql = new Sushee_Shell(false);

				$max_nql->addCommand($filter_command);
				$max_nql->addCommand($search_command);

				$max_commands = $max_nql->transform(SYSTEM_TOOLS_PATH.'report/csv/generic-csv-export-max-deps.xsl');
			   	$nql->addCommands($max_commands);
                //die($max_commands);
			}
			else
			{
				// NO dependencies || ONLY limited dependencies ("max" attribute set on the DEPENDENCY node within the RETURN node)
				// add filter and search directly
				$nql->addCommand($filter_command);
				$nql->addCommand($search_command);
			}
            
			if ($separator == 'comma')
			{
				$sep_char = ',';
			}
            else
            {
                $sep_char = ';';
            }

			$export_config_command =
				'<RESULTS name="csv-config" static="true">
					<!--TITLE></TITLE>
					<SUBTITLE>Export date: '.date('d/m/Y@H:i:s').'</SUBTITLE-->
					<SEPCHAR>'.$sep_char.'</SEPCHAR>
					<!--EOLSEQ>(EOL)</EOLSEQ-->
				</RESULTS>';
			$nql->addCommand($export_config_command);

            $additional_query = $report->getQueryString();
            if (!empty($additional_query))
            {
                $nql->addCommand($additional_query);
            }

			$template_stylesheet = $report->getInfo('TEMPLATE');
			if (empty($template_stylesheet))
			{
				 $template_stylesheet = 'csv/generic-csv-export.xsl'; // default
			}

            //xml_out($nql->getQuery());
			$csv_data = $nql->transformToText(SYSTEM_TOOLS_PATH.'report/'.$template_stylesheet);

            //$eol_seq = chr(10);
            if ($encoding == 'windows')
            {
                /*$csv = entities_to_utf8($csv);
                $csv = decode_from_xml($csv);
                $csv = utf8_decode($csv);*/
                $csv_data = unutf8($csv_data);
                //$eol_seq = chr(13).$eol_seq;
            }
            else // mac
            {

            }
            //$csv_data = str_replace('(EOL)', $eol_seq, $csv_data);
            
			/* *** */

			$user = new OfficityUser();
			$folderpath = '/contact/'.$user->getID().'/reports/csv/'.date('Y').'/'.date('m');

			$folder = new Folder($folderpath);
			if (!$folder->exists())
			{
				if ($folder->create() == false)
                {
                    $this->setLastError('folder creation failed! ('.$folderpath.')');
                    return false;;
                }
			}

			$title = cleanFilename($report->getInfo('TITLE'));
			$filename = date('Ymd_His').'-';
			$filename .= $title.'.csv';
			$target_filename = $folder->getPath().$filename;
            $target_path = $_SERVER['DOCUMENT_ROOT'].'/Files'.$target_filename;

			if (($file_handle = @fopen($target_path, 'w')) === false)
            {
                $this->setLastError('fopen failed! ('.$target_path.')');
                return false;
            }
			if ((@fwrite($file_handle, $csv_data)) === false)
            {
                $this->setLastError('fwrite failed! ('.$target_path.')');
                return false;
            }
			@fclose($file_handle);

			/* *** */

            $report->removeID();
			$report->removeInfo('OWNERID');
			$report->removeInfo('CREATORID');
			$report->removeInfo('GROUPID');
			$report->removeInfo('MODIFIERID');
			$report->setInfo('FILE', $target_filename);
			$report->setInfo('TYPE', 'csv');
			$report->setInfo('MODEL', 0);
            $report_date = $report->getInfo('DATE');
            if (empty_date($report_date))
            {
                $report->setInfo('DATE', date('Ymd'));
            }
            $report->setInfo('QUERYXML', $search_command, false); // do not encode node value to XML
			$report->create(); // TODO : check result

			return $target_filename;
		}
		
		/* *** */
		
		protected function assignSecurity($parent_node_name, $target_node_name, $targets_IDs, $new_security_setting, $execute=true)
		{
			$this->setReturn(array('INFO/'.$parent_node_name));
			if ($this->get() == false)
			{
				return false;
			} 
			          
			if ($new_security_setting == 'remove')
			{
				$parent_node = $this->nql_command->getElement('//'.$this->getDenomination().'/INFO/'.$parent_node_name);
	            foreach ($targets_IDs as $id)
	            {
	            	$parent_node->removeChild($target_node_name.'[@ID="'.$id.'"]');
	            }
			}
			else
			{	
				$current_settings = array();			
	            $current_nodes = $this->nql_command->getElements('//'.$this->getDenomination().'/INFO/'.$parent_node_name.'/'.$target_node_name);
	            foreach ($current_nodes as $node)
	            {
	            	$current_settings[$node->valueOf('@ID')] = $node->valueOf('@security');
	            }
				
				$new_settings = array();
				foreach ($targets_IDs as $id)
				{
					$new_settings[$id] = $new_security_setting;
				}

				//var_dump($current_settings);
	            //var_dump($new_settings);
				$new_settings += $current_settings;
	            //var_dump($new_settings);

				$this->reset();
				$this->setInfo($parent_node_name, '');	            
	            $parent_node = $this->nql_command->getElement('//'.$this->getDenomination().'/INFO/'.$parent_node_name);
	            
	            foreach ($new_settings as $target_id => $security_setting)
	            {
	            	$parent_node->appendChild('<'.$target_node_name.' ID="'.$target_id.'" security="'.$security_setting.'"/>');
	            }
			}
									
			return $this->update($execute);
		}
		
		public function assignGroupsSecurity($groups_IDs, $new_security_setting, $execute=true)
		{			
			return $this->assignSecurity('GROUPS', 'GROUP', $groups_IDs, $new_security_setting, $execute=true);
		}
		
		public function assignOwnersSecurity($contacts_IDs, $new_security_setting, $execute=true)
		{
			return $this->assignSecurity('OWNERS', 'CONTACT', $contacts_IDs, $new_security_setting, $execute=true);
		}
	}
?>