<?php

/**
 * Ideas
 * code page : hover -> wait 1sec -> if(focus) -> expand {-5 lines, + 5 lines}
 * Translations in EN / ES
 */

/**
 * Manage includes
 * @author franzz
 */
class ClassManagement extends PhpObject
{
	const INC_FOLDER = 'inc/';
	const INC_EXT = '.php';
	const SETTINGS_FILE = 'settings.php';

	private $asIncFiles;

	function __construct()
	{
		parent::__construct();
		$this->asIncFiles = array();
		$this->incFile(self::SETTINGS_FILE);
	}
	
	function __destruct()
	{
		parent::__destruct();
	}
	
	public function incClass($sClassName)
	{
		return $this->incFile(self::INC_FOLDER.$sClassName.self::INC_EXT);
	}
	
	public function incFile($sFilePath, $bMandatory=true)
	{
		$bIncluded = false;
		if(!in_array($sFilePath, $this->asIncFiles))
		{
			$sMissingFile = 'File "'.$sFilePath.'" missing.';
			if(file_exists($sFilePath))
			{
				$bIncluded = require_once($sFilePath);
				$this->asIncFiles[] = $sFilePath;
			}
			elseif($bMandatory)
			{
				die($sMissingFile.' Stopping process.');
			}
			else
			{
				$this->addError($sMissingFile);
			}
		}
		return $bIncluded;
	}
}

/**
 * Common class for all classes.
 * Handles the error log process
 * @author franzz
 */
class PhpObject
{
	//Log file name
	const LOG_FILENAME = 'log.html';

	//Message types
	const NOTICE_TAB = 'Notice';
	const WARNING_TAB = 'Warning';
	const ERROR_TAB = 'Error';
	const ALL_TAB = 'All';

	//Extraction mode
	const MODE_ARRAY = 0;
	const MODE_TEXT = 1;
	const MODE_HTML = 2;
	const MODE_FILE = 3;

	//Class variables
	private $asMessageStack;
	private $iExtractMode;
	private $bDebug;
	
	function __construct($iExtractMode=self::MODE_FILE)
	{
		$this->resetMessageStack();
		$this->iExtractMode = $iExtractMode;
		$this->setDebug(false);
	}
	
	protected function setDebug($bSwitch)
	{
		$this->bDebug = $bSwitch;
	}

	private static function getLogPath()
	{
		return dirname(__FILE__).'/'.self::LOG_FILENAME;
	}

	private function resetMessageStack($sType=self::ALL_TAB)
	{
		if($sType==self::ALL_TAB)
		{
			$this->resetMessageStack(self::NOTICE_TAB);
			$this->resetMessageStack(self::WARNING_TAB);
			$this->resetMessageStack(self::ERROR_TAB);
		}
		else
		{
			$this->asMessageStack[$sType] = array();
		}
	}

	protected function addNotice($sNotice)
	{
		$this->addTrace(self::NOTICE_TAB, $sNotice);
	}

	protected function addWarning($sWarning)
	{
		$this->addTrace(self::WARNING_TAB, $sWarning);
	}

	protected function addError($sError)
	{
		$this->addTrace(self::ERROR_TAB, $sError);
	}

	private function addTrace($sType, $sMessage)
	{
		$this->asMessageStack[$sType][] = $sMessage;
	}

	protected function getCleanMessageStack($sType=self::ALL_TAB)
	{
		$asMessages = ($sType==self::ALL_TAB)?$this->asMessageStack:$this->asMessageStack[$sType];
		$this->resetMessageStack($sType);

		return $this->glueMessages($asMessages);
	}

	protected function getCleanMessageStacks($aoExtsources, $sType=self::ALL_TAB)
	{
		$aoExtsources[] = $this;
		$aoMessages = array();
		foreach($aoExtsources as $oExtSource)
		{
			$oMessages = $oExtSource->getCleanMessageStack($sType);
			if($oMessages!='')
			{
				$aoMessages[get_class($oExtSource)] = $oMessages;
			}
		}
		return $this->glueMessages($aoMessages);
	}

	private function glueMessages($asMessages)
	{
		switch($this->iExtractMode)
		{
			case self::MODE_TEXT:
				$oMessageStack = self::recursiveImplode("\n", $asMessages);
				break;
			case self::MODE_HTML:
				$oMessageStack = self::recursiveImplode('<br />', $asMessages);
				break;
			case self::MODE_ARRAY:
				$oMessageStack = $asMessages;
				break;
			case self::MODE_FILE:
				$oMessageStack = self::recursiveImplode("\n", $asMessages);
				break;
		}
		return $oMessageStack;
	}

	private static function flattenMessageStack($asTab, $sGlobalKey='')
	{
		$asFlatTab = array();
		foreach($asTab as $oKey=>$oRow)
		{
			$sKey = is_numeric($oKey)?$sGlobalKey:$oKey.' - ';
			if(is_array($oRow))
			{
				$asFlatTab = array_merge($asFlatTab, self::flattenMessageStack($oRow, $sKey));
			}
			else
			{
				$asFlatTab[] = $sKey.$oRow;
			}
		}
		return $asFlatTab;
	}

	private static function recursiveImplode($sGlue, $asTab)
	{
		$asTab = self::flattenMessageStack($asTab);
		return implode($sGlue, $asTab);
	}

	function __destruct()
	{
		$sErrorStack = $this->getCleanMessageStack($this->bDebug?self::ALL_TAB:self::ERROR_TAB);
		if($sErrorStack!='')
		{
			switch($this->iExtractMode)
			{
				case self::MODE_TEXT:
					echo $sErrorStack;
					break;
				case self::MODE_HTML:
					echo $sErrorStack;
					break;
				case self::MODE_ARRAY:
					break;
				case self::MODE_FILE:
					@file_put_contents(self::getLogPath(), "\n\n".date('r')."\n".$sErrorStack, FILE_APPEND);
					break;
			}
		}
	}
}

/**
 * Application Core
 * @author franzz
 */
class Databap extends PhpObject
{
	//Common Constants
	const EXPECTED_PAGE_COOKIE = 'exp_page';
	const MAIN_SEPARATOR = ' ';
	const DATE_FORMAT = 'd/m/Y';
	const TIME_FORMAT = 'H:i:s';
	const DATE_TIME_FORMAT = 'd/m/Y H:i:s';
	const DATE_COMPACT_FORMAT = 'YmdHis';
	const DATE_TIME_SQL_FORMAT = 'Y-m-d H:i:s';
	const DATE_SQL_FORMAT = 'Y-m-d';
	const HISTORY_LENGTH = 10;
	const STYLE_PATH = 'style.css';
	public static $UPLOAD_IMG_EXTS = array('jpg', 'jpeg', 'gif', 'png');
	public static $UPLOAD_DOC_EXTS = array('jpg', 'jpeg', 'gif', 'png', 'doc', 'ppt', 'pdf', 'xls', 'docx', 'pptx', 'xlsx');
	const ID_SEPARATOR = '_';
	
	//Code
	const MAX_LIST_LENGTH = 5;
	
	//Authorizations
	const CLEARANCE_MEMBER = 0;
	const CLEARANCE_ADMIN = 9;
	const EXT_ACCESS = 'external_access';
	const USER_COOKIE_ID = 'id_user';
	const USER_COOKIE_PASS = 'checksum';
	const COOKIE_LIFETIME = 7;
	const TOKEN_LENGTH = 32;
	
	//HTTP Requests response
	const DISCONNECTED = '__DISCONNECTED__';
	const ERROR = '__ERROR__';
	const SUCCESS = '__SUCCESS__';
	
	//Chat Constants
	//TODO Transfer these constants to chat page
	const MESSAGE_USER = 'U';
	const MESSAGE_ADD_CODE = 'A';
	const MESSAGE_EDIT_CODE = 'E';
	const MESSAGE_ADD_PROC = 'PA';
	const MESSAGE_EDIT_PROC = 'PE';
	const MESSAGE_ADD_DOC = 'DA';
	const MESSAGE_EDIT_DOC = 'DE';
	const MESSAGE_ACTION = 'M';
	const MESSAGE_PRIVATE = 'P';
	const MESSAGE_IMG = 'I';
	const MESSAGE_9GAG = '9';
	const MESSAGE_NICK = 'N';
	const MESSAGE_STATUS = 'S';
	const MESSAGE_CONN = 'C';
	const MESSAGE_INVITE = 'V';
	const MESSAGE_REBOOT = 'R';
	const MESSAGE_ARTICLE = 'B';
	const DEFAULT_COMPANY_LOGO = 'tux_24.png';
	const DEFAULT_CHAN = 'Principal';
	const ALL_CHAN_ID = 1;
	const ALL_CHAN_TEXT = 'A.l.l_C.h.a.n_I.n.c.l.u.d.e.d';
	const KEEP_ALIVE = 600; //seconds
	const REBOOT_DELAY = 15; //seconds
	const PM_SEP = '___';
	const CHAT_IMG_MAX_WIDTH = 700;
	const CHAT_IMG_MAX_HEIGHT = 2000;
	const JSON_PREFIX = '[\-JSON-/]';
	
	//Options Constants
	const LANG_FR = 'FR';
	const OPT_TEXT = 'T';
	const OPT_SELECT = 'S';
	const OPT_NICKNAME = 1;
	const OPT_BG = 2;
	const OPT_BRIGHT_BG = 3;
	const OPT_HOVER = 4;
	const OPT_TOKEN = 5;
	const OPT_IMAGE_CHAT = 6;
	const OPT_STATUS = 7;
	
	//Search Constants
	const PROC_TYPE = 'p';
	const CODE_TYPE = 'c';
	const DOC_TYPE = 'd';
	const ART_TYPE = 'a';
	
	//Doc constants
	const DOC_FOLDER = 'docs/';
	const DOC_TMP_FOLDER = 'docs/tmp/';
	
	//Objects
	private $oMySql;
	private $oProcedure;
	private $oSearchEngine;
	private $oClassManagement;
	
	//Variables
	private $iUserId;
	private $asUserInfo;
	private $sLanguage;

	function __construct($oClassManagement)
	{
		parent::__construct();
		$this->oMySql = new MySqlManager();
		if($this->oMySql->sDbState == MySqlManager::DB_NO_DATA) $this->install();
		$this->oProcedure = new Procedure($this->oMySql);
		$this->setUserId(0);
		$this->sLanguage = self::LANG_FR;
		$this->oSearchEngine = new SearchEngine($this->oMySql);
		$this->oClassManagement = $oClassManagement;
	}
	
	private function install()
	{
		//Install DB
		$this->oMySql->install();
		
		//Options
		$sOptionNameCol = MySqlManager::getText(MySqlManager::OPTNAME_TABLE);
		$sOptionNameIdCol = MySqlManager::getId(MySqlManager::OPTNAME_TABLE);
		$iNicknameOptId = $this->oMySql->insertRow(MySqlManager::OPTNAME_TABLE, array($sOptionNameIdCol=>self::OPT_NICKNAME, $sOptionNameCol=>'pseudo du chat', 'type'=>self::OPT_TEXT, 'language'=>self::LANG_FR));
		$iBgColorOptId = $this->oMySql->insertRow(MySqlManager::OPTNAME_TABLE, array($sOptionNameIdCol=>self::OPT_BG, $sOptionNameCol=>'couleur de fond', 'type'=>self::OPT_TEXT, 'language'=>self::LANG_FR));
		$iBgColorOpt2Id = $this->oMySql->insertRow(MySqlManager::OPTNAME_TABLE, array($sOptionNameIdCol=>self::OPT_BRIGHT_BG, $sOptionNameCol=>'couleur de fond 2 (claire)', 'type'=>self::OPT_TEXT, 'language'=>self::LANG_FR));
		$iHoverColorOptId = $this->oMySql->insertRow(MySqlManager::OPTNAME_TABLE, array($sOptionNameIdCol=>self::OPT_HOVER, $sOptionNameCol=>'couleur de survol', 'type'=>self::OPT_TEXT, 'language'=>self::LANG_FR));
		$iHoverColorOptId = $this->oMySql->insertRow(MySqlManager::OPTNAME_TABLE, array($sOptionNameIdCol=>self::OPT_TOKEN, $sOptionNameCol=>'token', 'type'=>self::OPT_TEXT, 'language'=>self::LANG_FR));
		$iChatImageOptId = $this->oMySql->insertRow(MySqlManager::OPTNAME_TABLE, array($sOptionNameIdCol=>self::OPT_IMAGE_CHAT, $sOptionNameCol=>'image du chat', 'type'=>self::OPT_TEXT, 'language'=>self::LANG_FR));
		$iChatImageOptId = $this->oMySql->insertRow(MySqlManager::OPTNAME_TABLE, array($sOptionNameIdCol=>self::OPT_STATUS, $sOptionNameCol=>'Mission en cours', 'type'=>self::OPT_TEXT, 'language'=>self::LANG_FR));
		
		//Insert default and all channels
		$this->oMySql->insertRow(MySqlManager::CHAN_TABLE, array('safe_name'=>self::getChanSafeName(self::ALL_CHAN_TEXT), MySqlManager::getText(MySqlManager::CHAN_TABLE)=>self::ALL_CHAN_TEXT));
		$this->oMySql->insertRow(MySqlManager::CHAN_TABLE, array('safe_name'=>self::getChanSafeName(self::DEFAULT_CHAN), MySqlManager::getText(MySqlManager::CHAN_TABLE)=>self::DEFAULT_CHAN));
		
		//Install default users : admin and test
		$this->addUser('francois', 'lutran', 'cgi', 'francois@lutran.fr', self::CLEARANCE_ADMIN);
		$this->addUser('test', 'test', 'test', 'test@test.com');
	}

	private function setUserId($iUserId)
	{
		$this->iUserId = $iUserId;
		$this->asUserInfo = ($iUserId>0)?$this->getUserInfo($iUserId):array();
	}

	public function getUserId()
	{
		return $this->iUserId;
	}
	
	public function getPage($sPage, $asVars)
	{
		$oPage = new Mask('index');
		$oPage->setTag('first_page', $sPage);
		$oPage->setTag('index_link', $asVars['serv_name']);
		$oPage->setTag('line_break', "Global databap constants\n");
		
		//Rss Link
		$oPage->setTag('rss_link', $this->getUserOptionValue(self::OPT_TOKEN));
		
		//Pass GET variables down to jQuery
		$asVars['user_id'] = $this->getUserId();
		$sLastKey = end(array_keys($asVars));
		foreach($asVars as $sName => $sValue)
		{
			$oPage->addInstance('VARS', array('var_name'=>$sName, 'var_value'=>$sValue, 'coma'=>$sName==$sLastKey?'':', '));
		}
		
		//Pages
		$asMaskPaths = glob(Mask::MASK_FOLDER.'*.html');
		$asMaskNames = array_map('basename', $asMaskPaths, array_fill(1, count($asMaskPaths), '.html'));
		$sLastKey = end(array_keys($asMaskNames));
		foreach($asMaskNames as $sKey=>$sMaskName)
		{
			$oPage->addInstance('PAGES', array('page_name'=>$sMaskName, 'page_value'=>$sMaskName, 'coma'=>$sKey==$sLastKey?'':', '));
		}
		
		//Constants
		$asConstants = array(	'disconnected'=>self::DISCONNECTED,
								'error'=>self::ERROR,
								'keep_alive'=>self::KEEP_ALIVE,
								'opt_type_text'=>self::OPT_TEXT,
								'opt_type_select'=>self::OPT_SELECT,
								'max_size'=>self::getMaxSize(),
								'authorized_img_exts'=>self::$UPLOAD_IMG_EXTS,
								'authorized_file_exts'=>self::$UPLOAD_DOC_EXTS,
								'id_sep'=>self::ID_SEPARATOR,
								'mask_folder'=>Mask::MASK_FOLDER,
								'image_folder'=>Procedure::IMAGE_FOLDER,
								'image_folder_tmp'=>Procedure::IMAGE_FOLDER_TMP,
								'image_folder_thumb'=>Procedure::IMAGE_FOLDER_THUMB,
								'default_chan'=>self::DEFAULT_CHAN,
								'all_chan_id'=>self::ALL_CHAN_ID,
								'all_chan_text'=>self::ALL_CHAN_TEXT,
								'pm_separator'=>self::PM_SEP,
								'reboot_delay'=>self::REBOOT_DELAY);
		$oPage->setTag('constants', $this->jsonConvert($asConstants));
		
		return $oPage->getMask();
	}
	
	public function getRss($sCat='')
	{
		//Class Feed
		$this->oClassManagement->incClass('rss');

		//Building header
		$asDesc = array
		(
			'title'=>'Flux RSS Databap'.($sCat==''?'':' - '.$sCat),
			'link'=>$_GET['serv_name'].'rss'.($sCat==''?'':'-'.$sCat),
			'copyright'=>'Powered by Francois Lutran. RSS Feed Generator under GPLv3 License',
			'description'=>'Flux RSS du chat'.($sCat==''?'':', section '.$sCat),
			'language'=>'fr',
			'webmaster_mail'=>'francois@lutran.fr'
		);
		$oFeed = new Feed($asDesc);
		
		//Building items
		switch(strtolower($sCat))
		{
			case '9gag':
				$sRegEx = '^(https?://|www\\.)(.*)9gag\\.com';
				break;
			case 'youtube':
				$sRegEx = '^(https?://|www\\.)(.*)youtube\\.com';
				break;
			case '':
				$sRegEx = '^(https?://|www\\.)[\.A-Za-z0-9\-]+\\.[a-zA-Z]{2,4}';
				break;
			default:
				$sRegEx = '^(https?://|www\\.)(.*)'.$sCat.'(.*)\\.[a-zA-Z]{2,4}';
				break;
		}
		$asResult = $this->oMySql->selectRows(array('select'=>array('id_message', 'nickname', 'message', 'led'), 'from'=>'messages', 'constraint'=>array('message'=>$sRegEx), 'constOpe'=>array('message'=>' REGEXP ')));
		$sPattern = '/(https?\:\/\/|www\.)[\S]+\.[a-zA-Z]{2,4}([\S]*)/i';
		
		foreach($asResult as $iLinkId=>$asRow)
		{
			//get link out of message
			preg_match($sPattern, $asRow['message'], $asMatches);
			$asRow['link'] = $asMatches[0];
			
			//add item
			$asItem = array
			(
				'title'=>'Lien'.($sCat==''?'':' - '.$sCat).' #'.($iLinkId+1),
				'category'=>$sCat,
				'description'=>'Lien post&eacute; par '.self::getNickNameFormat($asRow['nickname']).' &agrave; '.self::getDateFormat($asRow['led']).' : <a href="'.$asRow['link'].'" target="_blank">'.$asRow['message'].'</a>',
				'author'=>$asRow['nickname'],
				'link'=>$_GET['serv_name'].'chat-'.$asRow['id_message'],
				'pub_date'=>$asRow['led'],
				'guid'=>$_GET['serv_name'].'chat-'.$asRow['id_message']
			);
			$oFeed->addItem($asItem);
		}
			
		return $oFeed->getFeed();
	}
	
	public function syncSapBlog()
	{
		//Get articles list
		$asArticles = $this->getSAPBlogRss();
		
		//Update and spread the news
		$asResult = array();
		if(date('N')>5) $asResult[] = "Pas de mise-&agrave;-jour pendant le week end !";
		else
		{
			foreach($asArticles as $asArticle)
			{
				$iArticleId = $this->oMySql->selectValue(MySqlManager::ART_TABLE, MySqlManager::getId(MySqlManager::ART_TABLE), array('title'=>$asArticle['title']));
	
				if($iArticleId==0)
				{
					$iArticleId = $this->oMySql->insertRow(MySqlManager::ART_TABLE, $asArticle);
					$this->addMessage($iArticleId, self::MESSAGE_ARTICLE, self::ALL_CHAN_ID);
					$this->oSearchEngine->buildIndex($iArticleId, self::ART_TYPE);
					$sResult = 'ADDED';
				}
				else $sResult = 'OK';
				$asResult[$iArticleId] = $sResult.' | '.implode(' | ', $asArticle);
			}
		}
		return MySqlManager::implodeAll($asResult, '->', "\n");
	}
	
	private function getSAPBlogRss()
	{
		$sSAPDomain = 'http://scn.sap.com';
		$sSAPBlogUrl = $sSAPDomain.'/community/data-warehousing/netweaver-bw/blog';
		
		$oDom = $this->getRemotePageDom($sSAPBlogUrl);
		$aoArticles = $oDom->getElementsByTagName('header');
		
		$asArticles = array();
		foreach($aoArticles as $oArticle)
		{
			if($oArticle->getAttribute('class')=='jive-blog-post-subject')
			{
				$asArticleInfo = array();
				$aoLinks = $oArticle->getElementsByTagName('a');
				foreach($aoLinks as $oLink)
				{
					switch($oLink->getAttribute('class'))
					{
						//Title & link
						case 'font-color-normal':
							$asArticleInfo['title'] = ucfirst(trim($oLink->nodeValue));
							$asArticleInfo['title'] = substr($asArticleInfo['title'], -1)=='.'?substr($asArticleInfo['title'], 0, -1):$asArticleInfo['title'];
							$asArticleInfo['link'] = $oLink->getAttribute('href');
							$asArticleInfo['date'] = substr(str_replace(array($sSAPBlogUrl.'/', '/'), array('', '-'), $asArticleInfo['link']), 0, 10);
							break;
							//Author
						case 'jiveTT-hover-user jive-username-link':
							$asNames = array_filter(explode(' ', ucwords(trim($oLink->nodeValue))));
							$asArticleInfo['first_name'] = array_shift($asNames);
							$asArticleInfo['last_name'] = implode('-', $asNames);
							$asArticleInfo['email'] = $sSAPDomain.$oLink->getAttribute('href');
							break;
					}
				}
				$asArticles[] = $asArticleInfo;
			}
		}
		
		return $asArticles;
	}
	
	private function get9gagPost($sUrl)
	{
		$asPost = array('url'=>$sUrl);
		$oDom = $this->getRemotePageDom($sUrl);

		$oDivs = $oDom->getElementsByTagName('section');
		foreach($oDivs as $oPost) {if($oPost->getAttribute('id')=='individual-post') break;}
		
		//Title
		$asPost['title'] = $oPost->getElementsByTagName('h2')->item(0)->nodeValue;
		
		//Image
		$oImages = $oPost->getElementsByTagName('img');
		foreach($oImages as $oImage)
		{
			switch($oImage->getAttribute('class'))
			{
				case 'badge-item-animated-img':
					$o9gagImage = $oImage;
					break 2;
				case 'badge-item-img':
					$o9gagImage = $oImage;
					break;					
			}
		}
		$asResult = $this->downloadToTmp($o9gagImage->getAttribute('src'));
		
		if($asResult['error']=='')
		{
			$asPost['url_img'] = $asResult['out'];
			$asPost['width'] = $asResult['width'];
			$asPost['height'] = $asResult['height'];
		}
		
		return $asPost;
	}
	
	private function getRemotePageDom($sUrl)
	{
		$oDom = new DOMDocument();
		@$oDom->loadHTML(file_get_contents($sUrl));
		return $oDom->getElementsByTagName('body')->item(0);
	}
	
	public function addUser($sFirstName, $sLastName, $sCompany, $sEmail='', $iClearance=self::CLEARANCE_MEMBER)
	{
		$sFirstName = strtolower($sFirstName);
		$sLastName = strtolower($sLastName);
		$sCompany = strtolower($sCompany);
		$sEmail = strtolower($sEmail);
		
		//Checking company existency in company table
		$sCompanyTextCol = MySqlManager::getText(MySqlManager::COMP_TABLE);
		$iCompanyId = $this->oMySql->selectInsert(MySqlManager::COMP_TABLE, array($sCompanyTextCol=>strtolower($sCompany), 'logo'=>self::DEFAULT_COMPANY_LOGO), array($sCompanyTextCol));
		
		$asInfo = array('first_name'=>$sFirstName,
						'last_name'=>$sLastName,
						'email'=>$sEmail,
						 MySqlManager::getId(MySqlManager::COMP_TABLE)=>$iCompanyId,
						'clearance'=>$iClearance,
						'led'=>'0000-00-00 00:00:00');
		$iUserId = $this->oMySql->insertRow(MySqlManager::USER_TABLE, $asInfo);
		
		//Admin Options
		$sOptionTable = MySqlManager::OPT_TABLE;
		$sUserIdCol = MySqlManager::getId(MySqlManager::USER_TABLE);
		$sOptNameIdCol = MySqlManager::getId(MySqlManager::OPTNAME_TABLE);
		$sOptionTextCol = MySqlManager::getText(MySqlManager::OPT_TABLE);
		$this->oMySql->insertRow($sOptionTable, array($sUserIdCol=>$iUserId, $sOptNameIdCol=>self::OPT_NICKNAME, $sOptionTextCol=>$sFirstName));
		//$this->oMySql->insertRow($sOptionTable, array($sUserIdCol=>$iUserId, $sOptNameIdCol=>self::OPT_BG, $sOptionTextCol=>'#04357B'));
		//$this->oMySql->insertRow($sOptionTable, array($sUserIdCol=>$iUserId, $sOptNameIdCol=>self::OPT_BRIGHT_BG, $sOptionTextCol=>'#D9E5F2'));
		//$this->oMySql->insertRow($sOptionTable, array($sUserIdCol=>$iUserId, $sOptNameIdCol=>self::OPT_HOVER, $sOptionTextCol=>'#EFAB00'));
		$this->oMySql->insertRow($sOptionTable, array($sUserIdCol=>$iUserId, $sOptNameIdCol=>self::OPT_TOKEN, $sOptionTextCol=>$this->generateRssLink($iUserId)));
		//$this->oMySql->insertRow($sOptionTable, array($sUserIdCol=>$iUserId, $sOptNameIdCol=>self::OPT_IMAGE_CHAT, $sOptionTextCol=>'images/sap_gold_332.jpg'));
	}
	
	public function resetToken()
	{
		$asRs = $this->oMySql->selectRows(array('from'=>'`options`', 'constraint'=>array('id_option_name'=>self::OPT_TOKEN)));
		foreach($asRs as $asR)
		{
			$sTokenLink = $this->generateRssLink($asR['id_user']);
			$this->oMySql->updateRow('`options`', array('id_option'=>$asR['id_option']), array('`option`'=>$sTokenLink));
		}
		return $asRs;
	}
	
	private function generateRssLink($iUserId=0)
	{
		if($iUserId==0) $iUserId = $this->getUserId();
		return $_GET['serv_name'].'?a='.self::EXT_ACCESS.'&p=rss&auth_token='.$iUserId.'_'.$this->generateToken($iUserId);
	}

	private function generateToken($iUserId)
	{
		$sEncryptedToken = '';
		if($iUserId>0)
		{
			$asUser = $this->oMySql->selectRow(MySqlManager::USER_TABLE, $iUserId, array('first_name', 'last_name', 'email', MySqlManager::getId(MySqlManager::COMP_TABLE)));
			$sEncryptedToken = self::encryptPassword($asUser['first_name'].$asUser['last_name'].$asUser['email'].$asUser[MySqlManager::getId(MySqlManager::COMP_TABLE)]);
		}
		else
		{
			$this->addError('generating token : invalid user id "'.$iUserId.'"');
		}
		return $sEncryptedToken;
	}
	
	public function buildCompleteIndex()
	{
		$asSearchTypes = array(	self::CODE_TYPE=>MySqlManager::CODE_TABLE, 
								self::PROC_TYPE=>MySqlManager::PROC_TABLE, 
								self::ART_TYPE=>MySqlManager::ART_TABLE);
		
		$this->oMySql->getArrayQuery("TRUNCATE ".MySqlManager::SEARCH_TABLE);
		foreach($asSearchTypes as $sSearchType=>$sSearchTable)
		{
			$asItemIds = $this->oMySql->selectRows(array('select'=>MySqlManager::getId($sSearchTable), 'from'=>$sSearchTable));
			foreach($asItemIds as $iItemId) $this->oSearchEngine->buildIndex($iItemId, $sSearchType);
		}
	}
	
	//insert new code / version
	public function addCode($asData)
	{
		$sContent = $asData['content'];
		$sDescription = $asData['description'];
		$sLink = str_replace(' ', '_', $asData['link']);

		//insert value in db
		$asInsert = array(	MySqlManager::getText(MySqlManager::CODE_TABLE)=>$sContent,
							'description'=>$sDescription,
							'id_user'=>$this->getUserId());
		$iCodeId = $this->oMySql->insertRow(MySqlManager::CODE_TABLE, $asInsert);
		
		//insert link if exists
		if($sLink!='')
		{
			$asInsert = array(MySqlManager::getId(MySqlManager::CODE_TABLE)=>$iCodeId, 'phrase'=>$sLink);
			$this->oMySql->insertRow(MySqlManager::URL_TABLE, $asInsert);
		}

		//refer id update
		$this->oMySql->updateRow(MySqlManager::CODE_TABLE, $iCodeId, array('refer_id'=>$iCodeId));
		
		//Add message
		$this->addMessage($iCodeId, self::MESSAGE_ADD_CODE, self::ALL_CHAN_ID);
		
		//Add record in Search Table
		$this->oSearchEngine->buildIndex($iCodeId, self::CODE_TYPE);
		
		return $iCodeId;
	}
	
	public function editCode($oCode, $sContent)
	{
		$asRef = $this->getCodeInfo($oCode, 'first');
		$iVersion = $this->oMySql->selectValue(MySqlManager::CODE_TABLE, 'COUNT(id_code)', array('refer_id'=>$asRef['id_code']))+1; 
		$asEdit = array('code'=>$sContent, 
						'description'=>$asRef['description'].' (v'.$iVersion.')',
						'id_user'=>$this->iUserId,
						'refer_id'=>$asRef['id_code']);
		
		$iCodeId = $this->oMySql->insertRow(MySqlManager::CODE_TABLE, $asEdit);
		
		//Add message
		$this->addMessage($iCodeId, self::MESSAGE_EDIT_CODE, self::ALL_CHAN_ID);
		
		//Add record in Search Table
		$this->oSearchEngine->buildIndex($iCodeId, self::CODE_TYPE);
		
		return $iCodeId;
	}
	
	public function addProcedure($asPost)
	{
		$asPost['id_user'] = $this->getUserId();
		$this->oProcedure->inputForm($asPost);
		$asErrors = $this->oProcedure->checkIntegrity();
		if(count($asErrors)==0)
		{
			//Previous procedure Id
			$iPrevProcId = isset($asPost['procedure_id'])?$asPost['procedure_id']:0;
			
			//Load procedure into database
			$bCreation = $this->oProcedure->saveProcedure($iPrevProcId);
			
			//Extract extra data
			$asProc = $this->oProcedure->getProcedure();
			$iNewProcId = $asProc['proc_id'];
			$asUser = $this->getUserInfo($this->getUserId());
			
			$oResult = array('result'=>'success',
							'proc_id'=>$iNewProcId,
							'led'=>$asProc['led'],
							'name'=>$asUser['name'],
							'company'=>$asUser['company']);
			
			//Add Message in chat
			if($bCreation)
			{
				$this->addMessage($iNewProcId, self::MESSAGE_ADD_PROC, self::ALL_CHAN_ID);
			}
			else
			{
				$this->addMessage($iNewProcId, self::MESSAGE_EDIT_PROC, self::ALL_CHAN_ID);
			}
			
			//Add record in Search Table
			$this->oSearchEngine->buildIndex($iNewProcId, self::PROC_TYPE);
		}
		else
		{
			$oResult = array('result'=>'fail', 'errors'=>$asErrors);
		}
		
		return self::jsonExport($oResult);
	}
	
	public function getProcedure($iProcId)
	{
		$this->oProcedure->loadProcedure($iProcId);
		$asProc = $this->oProcedure->getProcedure();
		
		//Adding user info
		$asUser = $this->getUserInfo($asProc['id_user']);
		$asProc['name'] = $asUser['name'];
		$asProc['company'] = $asUser['company'];
		
		return self::jsonExport($asProc);
	}
	
	public function addDoc($asPost)
	{
		//Previous Doc Id
		$iPrevDocId = $asPost['doc_id'];
		$bCreation = ($iPrevDocId==0);
		
		//User
		$iUserId = $this->getUserId();
		
		//Titles
		$sTitle = isset($asPost['title'])?$asPost['title']:'';
		$sDescription = isset($asPost['description'])?$asPost['description']:'';
		
		//Get docs
		$sImagePattern = '/c1_(?P<doc_id>\d+)_image_(?P<doc_info>\w+)/';
		foreach($asPost as $sFormId=>$sValue)
		{
			preg_match($sImagePattern, $sFormId, $asMatches);
			if($asMatches['doc_info'] == 'name' || $asMatches['doc_info'] == 'desc')
			{
				$asDocs[$asMatches['doc_id']][$asMatches['doc_info']] = $sValue;					
			}
		}
			
		//Load doc into database
		$asData = array('title'=>$sTitle, 'description'=>$sDescription, 'file_name'=>$sFileName, MySqlManager::getId(MySqlManager::USER_TABLE)=>$iUserId);
		$iDbDocId = $this->oMySql->insertRow(MySqlManager::DOC_TABLE, $asData);
		
		//Load doc files into database
		$asDocData[MySqlManager::getId(MySqlManager::DOC_TABLE)] = $iDbDocId;
		foreach($asDocs as $asDocInfo)
		{
			//insert into database
			$sFileName = $asDocInfo['name'];
			$asDocData['description'] = $asDocInfo['desc'];
			$asDocData['file_name'] = $sFileName;
			$iDbImageId = $this->oMySql->insertRow(MySqlManager::FILE_TABLE, $asDocData);
			
			//Move file 
			$sTempFilePath = self::DOC_TMP_FOLDER.$sFileName;
			$sFilePath = self::DOC_FOLDER.$sFileName;
			if(!rename($sTempFilePath, $sFilePath))
			{
				$this->addError('Unmoveable file : '.$sTempFilePath);
			}
		}
		
		//Add this doc to the group
		$iReferId = $bCreation?$iDbDocId:$this->oMySql->selectValue(MySqlManager::DOC_TABLE, 'refer_id', $iPrevDocId);
		$this->oMySql->updateRow(MySqlManager::DOC_TABLE, $iDbDocId, array('refer_id'=>$iReferId));
	
		//Add Message in chat
		if($bCreation)
		{
			$this->addMessage($iDbDocId, self::MESSAGE_ADD_DOC, self::ALL_CHAN_ID);
		}
		else
		{
			$this->addMessage($iDbDocId, self::MESSAGE_EDIT_DOC, self::ALL_CHAN_ID);
		}
		
		//Add record in Search Table
		$this->oSearchEngine->buildIndex($iDbDocId, self::DOC_TYPE);

		return self::jsonExport(array('result'=>'success','doc_id'=>$iDbDocId));
	}
	
	public function getDoc($iDocId)
	{
		//Extract doc data
		$asDoc = $this->oMySql->selectRow(MySqlManager::DOC_TABLE, $iDocId);
		
		//Extract extra data
		$asUser = $this->getUserInfo($asDoc['id_user']);
		$asDoc['name'] = $asUser['name'];
		$asDoc['company'] = $asUser['company'];
		
		//Extract doc files
		$asFiles = $this->oMySql->selectRows(array('from'=>MySqlManager::FILE_TABLE, 'constraint'=>array('id_doc'=>$iDocId)));
		foreach($asFiles as $asFile)
		{
			$asDoc['files'][$asFile[MySqlManager::getId(MySqlManager::FILE_TABLE)]] = array('description'=>$asFile['description']);
		}
		
		return self::jsonExport($asDoc);
	}
	
	public function getFile($iFileId)
	{
		$sFileName = $this->oMySql->selectValue(MySqlManager::FILE_TABLE, 'file_name', $iFileId);
		$sFilePath = self::DOC_FOLDER.$sFileName;
		$sFileFullPath = dirname($_SERVER['SCRIPT_FILENAME'])."/".$sFilePath;
		
		if(!file_exists($sFilePath))
		{
			die();
		}
		else
		{
			//get mime type
			if(class_exists('finfo'))
			{
				$oFileInfo = new finfo(FILEINFO_MIME);
				$sMimetype = $oFileInfo->file($sFileFullPath);
			}
			else
			{
				$sMimetype = 'application/force-download';
			}
	
			//set headers
			header('Pragma: public');
			header('Expires: 0');
			header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
			header('Cache-Control: public');
			header('Content-Description: File Transfer');
			header('Content-Type: '.$sMimetype);
			header('Content-Disposition: attachment; filename='.$sFileName);
			header('Content-Transfer-Encoding: binary');
			header('Content-Length: '.@filesize($sFilePath));
	
			// download
			if ($oFile = @fopen($sFilePath, 'rb'))
			{
				while(!feof($oFile))
				{
					print(fread($oFile, 1024*8));
					flush();
					if(connection_status() != 0)
					{
						@fclose($oFile);
					}
				}
				@fclose($oFile);
			}
		}
	}
	
	public function uploadImage()
	{
		$oFileUploader = new fileUploader(Procedure::IMAGE_FOLDER_TMP, self::$UPLOAD_IMG_EXTS);
		$asResult = $oFileUploader->handleUpload();
		
		return $this->jsonConvert($asResult);
	}
	
	public function uploadDoc()
	{
		$oFileUploader = new fileUploader(self::DOC_TMP_FOLDER, self::$UPLOAD_DOC_EXTS);
		$asResult = $oFileUploader->handleUpload();
		
		return $this->jsonConvert($asResult);
	}
	
	private function getCodeInfo($oCode, $sMode='', $bCode=false)
	{
		$iCodeId = is_numeric($oCode)?$oCode:$this->getIdCodeFromPhrase($oCode);
		
		//Mode (get last or first id_code)
		if($sMode!='')
		{
			$this->switchCodeId($iCodeId, $sMode);
		}
		
		//Efficient request  : don't request for the entire code
		if(!$bCode)
		{
			$asSelect = MySqlManager::getTablecolumns(MySqlManager::CODE_TABLE);
			unset($asSelect[MySqlManager::getText(MySqlManager::CODE_TABLE)]);
			$asSelect = array_keys($asSelect);
		}
		else
		{
			$asSelect = "*";
		}
		
		$asCode = $this->oMySql->selectRow(MySqlManager::CODE_TABLE, $iCodeId, $asSelect);
		$asCode['description'] = self::getDescriptionFormat($asCode['description']);
		$asCode['led'] = self::getDateFormat($asCode['led']);
		return $asCode;
	}
	
	private function getProcInfo($iProcId)
	{
		$asProc = $this->oMySql->selectRow(MySqlManager::PROC_TABLE, $iProcId);
		$asProc['description'] = self::getDescriptionFormat($asProc['description']);
		$asProc['led'] = self::getDateFormat($asProc['led']);
		return $asProc;
	}
	
	private function getArticleInfo($iArtId)
	{
		$asArt = $this->oMySql->selectRow(MySqlManager::ART_TABLE, $iArtId);
		$asTransferredInfo = array('link_art'=>$asArt['link'], 'title'=>$asArt['title'], 'link_auth'=>$asArt['email']);
		$asTransferredInfo['date'] = self::getDateFormat($asArt['date'], self::DATE_FORMAT);
		$asTransferredInfo['name'] = self::getNameFormat($asArt['first_name'], $asArt['last_name']);
		$asTransferredInfo['description'] = self::getDescriptionFormat($asArt['title']);
		$asTransferredInfo['led'] = self::getDateFormat($asArt['led']);
		$asTransferredInfo['company'] = 'SAP';
		return $asTransferredInfo;
	}
	
	public function getUserInfo($iUserId, $bJson=false)
	{
		if($iUserId==$this->getUserId() && !empty($this->asUserInfo))
		{
			$asUserInfo = $this->asUserInfo;
		}
		else
		{
			$asRow = $this->oMySql->selectRow(MySqlManager::USER_TABLE, $iUserId);
			$asCompany = $this->oMySql->selectRow(MySqlManager::COMP_TABLE, $asRow[MySqlManager::getId(MySqlManager::COMP_TABLE)]);
			$asUserInfo =  array(	'name'=>self::getNameFormat($asRow['first_name'], $asRow['last_name']),
									'nickname'=>self::getNickNameFormat($this->getChatNickNames($iUserId)),
									'email'=>$asRow['email'],
									'company'=>self::getCompanyFormat($asCompany[MySqlManager::getText(MySqlManager::COMP_TABLE)]),
									'status'=>$this->getDescriptionFormat($this->getUserOptionValue(self::OPT_STATUS, $iUserId)),
									'logo'=>$asCompany['logo'],
									'clearance'=>$asRow['clearance'],
									'user_led'=>self::getDateFormat($asRow['led'], self::DATE_FORMAT));
		}
		return $bJson?$this->jsonExport($asUserInfo):$asUserInfo;
	}
	
	public function getUserClearance()
	{
		$asUserInfo = $this->getUserInfo($this->getUserId());
		return $asUserInfo['clearance'];
	}
	
	public function getOptions()
	{
		$sOptValueIdCol = MySqlManager::getId(MySqlManager::OPTVAL_TABLE);
		$sOptionTextCol = MySqlManager::getText(MySqlManager::OPT_TABLE);
		$sOptNameTextCol = MySqlManager::getText(MySqlManager::OPTNAME_TABLE);
		$sOptValueTextCol = MySqlManager::getText(MySqlManager::OPTVAL_TABLE);
		
		//Available Options
		$asAvailableOptions = $this->getAvailableOptions();
		
		//User options
		$asUserOptions = $this->getUserOptions();
		
		//Build Options panel
		$asSelectedOptions = array();
		foreach($asAvailableOptions as $sOptNameId=>$asOption)
		{
			$asSelectedOptions[$sOptNameId]['option_name'] = self::getDescriptionFormat($asOption[$sOptNameTextCol]);
			$asSelectedOptions[$sOptNameId]['user_value_id'] = array_key_exists($sOptNameId, $asUserOptions)?$asUserOptions[$sOptNameId][$sOptValueIdCol]:0;
			$asSelectedOptions[$sOptNameId]['user_value'] = array_key_exists($sOptNameId, $asUserOptions)?$asUserOptions[$sOptNameId][$sOptionTextCol]:'';
			$asSelectedOptions[$sOptNameId]['type'] = $asOption['type'];
			if($asOption['type']==self::OPT_SELECT)
			{
				$asOptionValuesInfo = array('select'=>array($sOptValueIdCol, $sOptValueTextCol),
											'from'=>MySqlManager::OPTVAL_TABLE,
											'constraint'=>array('language'=>$this->sLanguage, 
																MySqlManager::getId(MySqlManager::OPTNAME_TABLE)=>$sOptNameId),
											'orderBy'=>array('led'=>'ASC')); 
				$asSelectedOptions[$sOptNameId]['select'] = $this->oMySql->selectRows($asOptionValuesInfo, true, $sOptValueIdCol);
			}
		}
		return $this->jsonExport($asSelectedOptions);
	}
	
	public function setOptions($asUserOptions, $bSilentUpdate=true)
	{
		foreach($this->getAvailableOptions() as $sOptNameId=>$asOption)
		{
			if(array_key_exists($sOptNameId, $asUserOptions))
			{
				switch($asOption['type'])
				{
					case self::OPT_SELECT: //insert id of option value table
						$sUpdateCol = MySqlManager::getId(MySqlManager::OPTVAL_TABLE);
						break;
					case self::OPT_TEXT: //insert value
						$sUpdateCol = MySqlManager::getText(MySqlManager::OPT_TABLE);
						break;
					default:
						continue 2;
				}
				$sNewValue = $asUserOptions[$sOptNameId];

				//Check identical data
				$asKeys = array(MySqlManager::getId(MySqlManager::USER_TABLE)=>$this->getUserId(), MySqlManager::getId(MySqlManager::OPTNAME_TABLE)=>$sOptNameId);
				$asData = array($sUpdateCol=>$sNewValue) + $asKeys;
				$sOldValue = $this->oMySql->selectValue(MySqlManager::OPT_TABLE, $sUpdateCol, $asKeys);
				
				//Update value
				if($sOldValue!=$sNewValue)
				{
					//Update value
					$this->oMySql->insertUpdateRow(MySqlManager::OPT_TABLE, $asData, array_keys($asKeys));
					
					//Spread the word
					if(!$bSilentUpdate)
					{
						//TODO rassembler les messages similaires dans une fonction de template
						switch($sOptNameId)
						{
							case self::OPT_NICKNAME:
								$sType = self::MESSAGE_NICK;
								$sChanName = self::ALL_CHAN_TEXT;
								$sMessage = $sOldValue.' a chang&eacute; son pseudo en '.$sNewValue;
								break;
							case self::OPT_STATUS:
								$sType = self::MESSAGE_STATUS;
								$sChanName = self::ALL_CHAN_TEXT;
								$sMessage = 'est sur une nouvelle mission : '.$this->getDescriptionFormat($sNewValue);
								break;
							default:
								continue 2;
						}
						$sChanId = $this->getChanId($sChanName);
						$this->addMessage($sMessage, $sType, $sChanId);
					}
				}
			}
		}
	}
	
	private function getAvailableOptions()
	{
		$sOptNameIdCol = MySqlManager::getId(MySqlManager::OPTNAME_TABLE);
		$sOptNameTextCol = MySqlManager::getText(MySqlManager::OPTNAME_TABLE);
		$asInfo = array('select'=>array($sOptNameIdCol, $sOptNameTextCol, 'type'),
						'from'=>MySqlManager::OPTNAME_TABLE,
						'constraint'=>array('language'=>$this->sLanguage)); 
		return $this->oMySql->selectRows($asInfo, true, $sOptNameIdCol);
	}
	
	private function getUserOptionValue($sOptionNameId, $iUserId=0)
	{
		$asUserOptions = $this->getUserOptions($sOptionNameId, $iUserId);
		$asOptionInfo = array_shift($asUserOptions);
		return $asOptionInfo['option'];
	}
	
	private function getUserOptions($oOptionNameIds=array(), $iUserId=0)
	{
		$iUserId = $iUserId>0?$iUserId:$this->getUserId();
		$sUserIdCol = MySqlManager::getId(MySqlManager::USER_TABLE);
		$sOptNameIdCol = MySqlManager::getId(MySqlManager::OPTNAME_TABLE);
		$sOptValueIdCol = MySqlManager::getId(MySqlManager::OPTVAL_TABLE);
		$sOptionTextCol = MySqlManager::getText(MySqlManager::OPT_TABLE);
		
		if(!is_array($oOptionNameIds))
		{
			$oOptionNameIds = array($oOptionNameIds);
		}
		
		$asUserinfo = array('select'=>array($sOptNameIdCol, $sOptValueIdCol, $sOptionTextCol),
							'from'=>MySqlManager::OPT_TABLE,
							'constraint'=>array($sUserIdCol=>$iUserId));
		if(count($oOptionNameIds)>0)
		{
			$asUserinfo['constraint'][$sOptNameIdCol] = "(".implode(", ", $oOptionNameIds).")";
			$asUserinfo['constOpe'] = array($sUserIdCol=>"=", $sOptNameIdCol=>" IN ");
			$asUserinfo['constVar'] = true;
		}
		return $this->oMySql->selectRows($asUserinfo, true, $sOptNameIdCol);	
	}

	public function getProfile($oUser)
	{
		switch($oUser)
		{
			case '':
				$iUserId = $this->getUserId();
				break;
			case is_numeric($oUser):
				$iUserId = $oUser;
				break;
			case is_string($oUser):
				$oRes = $this->oMySql->selectValue(MySqlManager::USER_TABLE, MySqlManager::getId(MySqlManager::USER_TABLE), array('user'=>$oUser));
				$iUserId = !$oRes?$this->getUserId():$oRes;
				break;
			default:
				$iUserId = $this->getUserId();
				break;
		}
		
		//User Info
		$asProfile = $this->getUserInfo($iUserId);
		
		//History Info
		$iFullCodeId = MySqlManager::getId(MySqlManager::CODE_TABLE, true);
		$iFullLedName = MySqlManager::getFullColumnName(MySqlManager::CODE_TABLE, 'led');
		$asInfo = array('select'=>array($iFullCodeId, 'description', 'refer_id', $iFullLedName, 'phrase'),
						'from'=>MySqlManager::CODE_TABLE,
						'join'=>array(MySqlManager::URL_TABLE=>MySqlManager::getId(MySqlManager::CODE_TABLE)),
						'constraint'=>array('id_user'=>$iUserId),
						'orderBy'=>array('led'=>'DESC'));
		
		$asHistory = $this->oMySql->selectRows($asInfo);
		foreach($asHistory as $asCode)
		{
			$asProfile['history'][$asCode['id_code']]['action'] = (($asCode['refer_id']==$asCode['id_code'])?'Création':'Modification').' de code';
			$asProfile['history'][$asCode['id_code']]['date'] = self::getDateFormat($asCode['led']);
			$asProfile['history'][$asCode['id_code']]['description'] = $asCode['description'];
			$asProfile['history'][$asCode['id_code']]['phrase'] = ($asCode['phrase']=='')?$asCode['id_code']:$asCode['phrase'];
		}
		return $this->jsonExport($asProfile);
	}
	
	private static function isPmChan($sChanSafeName)
	{	
		$asResult = array('is_pm'=>false, 'chan_name'=>$sChanSafeName);
		
		preg_match('/(?P<from>\d+)'.self::PM_SEP.'(?P<to>\d+)/', $sChanSafeName, $asMatch);
		if(!empty($asMatch))
		{
			$asResult['is_pm'] = true;
			$asResult['from'] = $asMatch['from'];
			$asResult['to'] = $asMatch['to'];
			if($asMatch['from'] > $asMatch['to']) $asResult['chan_name'] = $asMatch['to'].self::PM_SEP.$asMatch['from'];
			else $asResult['chan_name'] = $asMatch['from'].self::PM_SEP.$asMatch['to'];
		}
		
		return $asResult;
	}
	
	private function checkChanAuth($sChanSafeName, $iUserId=0)
	{
		$asUserInfo = ($iUserId > 0)?$this->getUserInfo($iUserId):array('company'=>'');
		$asCompanies = $this->oMySql->selectRows(array('select'=>MySqlManager::getText(MySqlManager::COMP_TABLE), 'from'=>MySqlManager::COMP_TABLE));
		$iCompChan = array_search($sChanSafeName, array_map(array('self', 'getChanSafeName'), $asCompanies));
		$asPm = $this->isPmChan($sChanSafeName);
		
		return	$sChanSafeName!='' && 																					//Empty channel name
				($iCompChan===false || $asUserInfo['company']==self::getCompanyFormat($asCompanies[$iCompChan])) &&	//Test Company Channel
				(!$asPm['is_pm'] || $iUserId==$asPm['from'] || $iUserId==$asPm['to']);										//Test PM
	}
	
	public function joinChan($sChanName, $bFirstConn=false, $asAttendees=array())
	{
		$asResult = array('success'=>self::ERROR);
		$sSafeChanName = self::getChanSafeName($sChanName);
		
		//Authorization to join channel
		$asMessages = array();
		if($this->checkChanAuth($sSafeChanName, $this->getUserId()))
		{
			//On first connection, display on-join message for all channels
			if($bFirstConn)
			{
				$asConnChans = $this->getChannels($this->getUserId());
				$bFirstChan = true;
				foreach($asConnChans as $iConnChanId=>$sConnChanName)
				{
					//Checking once for all channels
					if(!$bFirstChan || !$this->isUserConnected($iConnChanId))
					{
						$asMessages[$iConnChanId] = $sConnChanName;
					}
					else break;
					$bFirstChan = false;
				}
			}
				
			//Add connection link in DB
			$sUserIdCol = MySqlManager::getId(MySqlManager::USER_TABLE);
			$sChanIdCol = MySqlManager::getId(MySqlManager::CHAN_TABLE);
			
			$asData = array('safe_name'=>$sSafeChanName, MySqlManager::getText(MySqlManager::CHAN_TABLE)=>$sChanName);
			$iChanId = $this->oMySql->insertUpdateRow(MySqlManager::CHAN_TABLE, $asData, array('safe_name'), false);
			
			//Is user already connected to this chan
			$bConnectedUser = $this->isUserConnected($iChanId);
			$iConnId = $this->oMySql->insertUpdateRow(MySqlManager::CONN_TABLE, array($sUserIdCol=>$this->getUserId(), $sChanIdCol=>$iChanId), array($sUserIdCol, $sChanIdCol), false);
			if($iConnId>0) $asResult['success'] = self::SUCCESS;
			
			if($asResult['success']==self::SUCCESS)
			{
				//Return connected channels
				$asResult['channels'] = $this->getChannels($this->getUserId());
				
				//Add tab title (customized)
				//TODO delete this shit and insert the channel type into channels DB table
				foreach($asResult['channels'] as $iConnectedChanId=>$sConnectedChanName)
				{
					$asPm = $this->isPmChan($sConnectedChanName);
					$asResult['channel_tab_names'][$iConnectedChanId] = $asPm['is_pm']?$this->getChatNickNames($asPm['from']==$this->getUserId()?$asPm['to']:$asPm['from']):$sConnectedChanName;
				}
				$asResult['current_chan_id'] = $iChanId;
				
				//Communicate on user's connection
				if(!$bConnectedUser)
				{
					$asMessages[$iChanId] = $this->oMySql->selectValue(MySqlManager::CHAN_TABLE, MySqlManager::getText(MySqlManager::CHAN_TABLE), $iChanId);
				}
				$sStatus = $this->getUserOptionValue(self::OPT_STATUS);
				$asUserInfo = $this->getUserInfo($this->getUserId());
				foreach($asMessages as $iChanId=>$sMsgChanName)
				{
					$asPm = $this->isPmChan($sMsgChanName);
					$this->addMessage(' de '.$asUserInfo['company'].' ('.($sStatus==''?'aucune mission en cours':$this->getDescriptionFormat($sStatus)).') rejoint '.($asPm['is_pm']?'le chan priv&eacute;':'#'.$sMsgChanName), self::MESSAGE_CONN, $iChanId);
				}
				
				//Send invites to attendees
				if(!empty($asAttendees))
				{
					$asUserInfo = $this->getUserInfo($this->getUserId());
					foreach($asAttendees as $sAttendee)
					{
						if($this->checkChanAuth($sSafeChanName, $sAttendee))
						{
							$this->addMessage($sAttendee, self::MESSAGE_INVITE, $iChanId);
						}
					}
				}
				
				//Update chan leds
				$this->pingChans();
			}
		}
		
		return self::jsonExport($asResult);
	}
	
	private function isUserConnected($iChanId=0, $iUserId=0)
	{
		$iUserId = $iUserId>0?$iUserId:$this->getUserId();
		
		$sUserIdCol = MySqlManager::getId(MySqlManager::USER_TABLE);
		$asInfo = array('select'	=> MySqlManager::getId(MySqlManager::CONN_TABLE),
						'from'		=> MySqlManager::CONN_TABLE,
						'constraint'=> array($sUserIdCol=>$iUserId,	'led'=>"DATE_SUB(NOW(), INTERVAL ".self::KEEP_ALIVE." SECOND)"),
						'constOpe'	=> array($sUserIdCol=>'=',		'led'=>'>'),
						'constVar'	=> true,
						'groupBy'	=> $sUserIdCol);
		
		//Add chan constraint
		if($iChanId>0)
		{
			$asInfo['constraint'][MySqlManager::getId(MySqlManager::CHAN_TABLE)] = $iChanId;
			$asInfo['constOpe'][MySqlManager::getId(MySqlManager::CHAN_TABLE)] = '=';
		}
		//$this->oMySql->bTrace = true;
		return (count($this->oMySql->selectRows($asInfo))>0);
	}
	
	public function pingChans()
	{
		$aiConstraints = array(MySqlManager::getId(MySqlManager::USER_TABLE)=>$this->getUserId());
		$asUpdates = array('led'=>date(self::DATE_TIME_SQL_FORMAT));
		$this->oMySql->updateRows(MySqlManager::CONN_TABLE, $aiConstraints, $asUpdates);
	}
	
	public function quitChan($sChanName)
	{
		$iChanId = $this->getChanId($sChanName);
		$iConnId = $this->getConnId($iChanId);
		if($iConnId>0)
		{
			$this->oMySql->deleteRow(MySqlManager::CONN_TABLE, $iConnId);
			$asPm = $this->isPmChan($sChanName);
			$this->addMessage('quitte '.($asPm['is_pm']?'le chan priv&eacute;':'#'.$sChanName), self::MESSAGE_CONN, $iChanId);
		}
	}
	
	private function getActiveChannels()
	{
		//Get connected users
		$asActiveChanUsers = $this->getConnectedUsers(false, false);
		$asActiveChans = array_keys($asActiveChanUsers);
				
		//Get Channel names
		$sChanIdCol = MySqlManager::getId(MySqlManager::CHAN_TABLE);
		$asChanNames = $this->oMySql->selectRows(array('select'=>array($sChanIdCol, MySqlManager::getText(MySqlManager::CHAN_TABLE)), 'from'=>MySqlManager::CHAN_TABLE), true, $sChanIdCol);
		
		foreach($asActiveChans as $iChanId)
		{
			$asChannels[$asChanNames[$iChanId]] = count($asActiveChanUsers[$iChanId]);
		}
		
		//remove restricted channels
		$asPublicChannels = array();
		foreach($asChannels as $sChanName=>$iChanCount)
		{
			if($this->checkChanAuth($this->getChanSafeName($sChanName))) $asPublicChannels[$sChanName] = $iChanCount;
		}

		return $asPublicChannels;
	}
	
	private function getChannels($iUserId=0)
	{
		$sChanIdCol = MySqlManager::getId(MySqlManager::CHAN_TABLE, true);
		$sChanNameCol = MySqlManager::getText(MySqlManager::CHAN_TABLE);
	
		$asInfo = array('select'=>array($sChanIdCol, $sChanNameCol), 
						'from'=> MySqlManager::CONN_TABLE, 
						'join'=> array(MySqlManager::CHAN_TABLE=>MySqlManager::getId(MySqlManager::CHAN_TABLE)),
						'orderBy'=> array(MySqlManager::getId(MySqlManager::CONN_TABLE)=>'ASC'));
		if($iUserId > 0) $asInfo['constraint'] = array(MySqlManager::getId(MySqlManager::USER_TABLE)=>$iUserId);
		
		$asChannels = $this->oMySql->selectRows($asInfo, true, MySqlManager::getId(MySqlManager::CHAN_TABLE));
		
		//remove restricted channels
		$asPublicChannels = array();
		foreach($asChannels as $iChanId=>$sChanName)
		{
			if($this->checkChanAuth($this->getChanSafeName($sChanName), $iUserId)) $asPublicChannels[$iChanId] = $sChanName;
		}
		return $asPublicChannels;
	}
	
	private function getChanId($sChanName)
	{
		$sChanIdCol = MySqlManager::getId(MySqlManager::CHAN_TABLE);
		$sSafeChanName = self::getChanSafeName($sChanName);
		$iChanId = $this->oMySql->selectValue(MySqlManager::CHAN_TABLE, $sChanIdCol, array('safe_name'=>$sSafeChanName));
		if($iChanId==0)
		{
			
			$this->addError('No channel id found with channel name (safe): '.$sSafeChanName.', unsafe: '.$sChanName);
		}
		return $iChanId;
	}
	
	private function getConnId($iChanId, $iUserId=0)
	{
		$sUserIdCol = MySqlManager::getId(MySqlManager::USER_TABLE);
		$sConnIdCol = MySqlManager::getId(MySqlManager::CONN_TABLE);
		$sChanIdCol = MySqlManager::getId(MySqlManager::CHAN_TABLE);
		
		if($iUserId==0) $iUserId = $this->getUserId();
		$iConnId = $this->oMySql->selectValue(MySqlManager::CONN_TABLE, $sConnIdCol, array($sUserIdCol=>$iUserId, $sChanIdCol=>$iChanId));
		if($iConnId==0)
		{
			$this->addError('No connection found for user: '.$iUserId.' and channel id: '.$iChanId);
		}
		return $iConnId;
	}
	
	public function addChatMessage($sMessage, $sChanName)
	{
		$sMessage = htmlspecialchars($sMessage);
		$sType = self::MESSAGE_USER;
		if(substr($sMessage, 0, 1) == '/')
		{
			if(substr($sMessage, 0, 4) == '/me ')
			{
				$sType = self::MESSAGE_ACTION;
				$sMessage = substr($sMessage, 3);
			}
			elseif(substr($sMessage, 0, 6) == '/slap ')
			{
				$sType = self::MESSAGE_ACTION;
				$sMessage = ' fout une grosse tarte &agrave; '.substr($sMessage, 5);
			}
			elseif(substr($sMessage, 0, 4) == '/bs ')
			{
				$sType = self::MESSAGE_ACTION;
				$sMessage = ' bitch-slaps '.substr($sMessage, 3);
			}
			elseif(substr($sMessage, 0, 6) == '/kick ')
			{
				$sType = self::MESSAGE_ACTION;
				$sMessage = ' met un coup de pied au cul de '.substr($sMessage, 5);
			}
			elseif(substr($sMessage, 0, 6) == '/nick ' && strlen($sMessage)>6)
			{
				$sNewNick = $this->getNickNameFormat(substr($sMessage, 5));
				$sOldNick = $this->getNickNameFormat($this->getChatNickNames($this->getUserId()));
				
				//changing Nickname
				$this->setOptions(array(self::OPT_NICKNAME=>$sNewNick));
				
				//Message
				$sType = self::MESSAGE_NICK;
				$sChanName = self::ALL_CHAN_TEXT;
				$sMessage = $sOldNick.' a chang&eacute; son pseudo en '.$sNewNick;
			}
			elseif(substr($sMessage, 0, 9) == '/mission ' && strlen($sMessage)>9)
			{
				$sNewStatus = substr($sMessage, 9);
				$sNewFormatStatus = $this->getDescriptionFormat($sNewStatus);
			
				//changing Nickname
				$this->setOptions(array(self::OPT_STATUS=>$sNewStatus));
			
				//Message
				$sType = self::MESSAGE_STATUS;
				$sChanName = self::ALL_CHAN_TEXT;
				$sMessage = 'est sur une nouvelle mission : '.$sNewFormatStatus;
			}
			elseif(substr($sMessage, 0, 6) == '/mail ' && strlen($sMessage)>6)
			{
				$sImagePattern = '/\/mail (?P<nickname>\w+) (?P<message>.*)/';
				preg_match($sImagePattern, $sMessage, $asMatches);

				//Looking for user Id
				$asContraints = array(	'LOWER(`'.MySqlManager::getText(MySqlManager::OPT_TABLE).'`)'=>strtolower($asMatches['nickname']),
										MySqlManager::getId(MySqlManager::OPTNAME_TABLE)=>self::OPT_NICKNAME);
				$iUserIdTo = $this->oMySql->selectValue(MySqlManager::OPT_TABLE, MySqlManager::getId(MySqlManager::USER_TABLE), $asContraints);

				//Handling mail
				if($iUserIdTo>0)
				{
					if(trim($asMatches['message'])!='')
					{
						if($this->sendPM($iUserIdTo, $asMatches['message']))
						{
							$sMessage = 'a envoy&eacute; un mail &agrave; '.$asMatches['nickname'];
						}
						else
						{
							$sMessage = 'n\'a pas envoy&eacute; de mail &agrave; '.$asMatches['nickname'].' (raison inconnue, voir log)';
						}
					}
					else
					{
						$sMessage = 'n\'a pas envoy&eacute; de mail &agrave; '.$asMatches['nickname'].' (message vide)';
					}
				}
				else
				{
					$sMessage = 'n\'a pas envoy&eacute; de mail &agrave; '.$asMatches['nickname'].' (pseudo inconnu)';
				}
				$sType = self::MESSAGE_ACTION;
			}
			elseif(substr($sMessage, 0, 5) == '/mean')
			{
				$sPageContent = file_get_contents('http://www.randominsults.net/');
				$sStartText = '<strong><i>';
				$sEndText = '</i></strong>';
				$iStartPos = strpos($sPageContent, $sStartText);
				$iEndPos = strpos($sPageContent, $sEndText);
				
				$sMessage = substr($sPageContent, $iStartPos + strlen($sStartText), $iEndPos - $iStartPos);
			}
			elseif(substr($sMessage, 0, 5) == '/like')
			{
				$sType = self::MESSAGE_ACTION;
				$sMessage = ' plussoie';
			}
			elseif(substr($sMessage, 0, 4) == '/now')
			{
				$sType = self::MESSAGE_ACTION;
				$asWeekDays = array('lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche');
				$asMonths = array('janvier', 'f&eacute;vrier', 'mars', 'avril', 'mai', 'juin', 'juillet', 'ao&ucirc;t', 'septembre', 'octobre', 'novembre', 'd&eacute;cembre');
				$sMessage = ' a demand&eacute; l\'heure. Pour sa gouverne, il est exactement '.date(self::TIME_FORMAT).
							', le '.$asWeekDays[date('N')-1].' '.date('j').' '.$asMonths[date('n')-1].' '.date('Y').' (semaine '.date('W').')';
			}
			elseif($sMessage == '/channels' || substr($sMessage, 0, 5) == '/list' || $sMessage == '/chans')
			{
				//Always at least one channel open (the one the message is sent from)
				$sMessage = ' a demand&eacute; les chans disponibles. Pour sa gouverne, les chans ayant des membres connect&eacute;s sont #'.MySqlManager::implodeAll($this->getActiveChannels(), ' (', ', #', '', ')');
				$sType = self::MESSAGE_ACTION;
			}
			elseif(substr($sMessage, 0, 5) == '/img ' && strlen($sMessage)>5)
			{
				$sUrl = trim(substr($sMessage, 4));
				$asResult = $this->downloadToTmp($sUrl);
				if($asResult['error']=='')
				{
					$sMessage = $this->getJsonMessage(array($asResult['out'], $asResult['width'], $asResult['height'], $sUrl));
					$sType = self::MESSAGE_IMG;
				}
			}
			elseif(substr($sMessage, 0, 6) == '/9gag ' && strlen($sMessage)>6)
			{
				$sMessage = $this->getJsonMessage($this->get9gagPost(trim(substr($sMessage, 6))));
				$sType = self::MESSAGE_9GAG;
			}
			elseif(substr($sMessage, 0, 7) == '/reboot' && $this->getUserClearance()==self::CLEARANCE_ADMIN)
			{
				$sMessage = 'L\'administrateur a demand&eacute; un reboot. Votre page va se rafraichir automatiquement dans '.self::REBOOT_DELAY.' secondes.';
				$sType = self::MESSAGE_REBOOT;
			}
		}
		elseif(substr($sMessage, 0, 1) == '@' && strpos($sMessage, ' ')>1)
		{
			$sType = self::MESSAGE_PRIVATE;
		}
		$sChanId = $this->getChanId($sChanName);
		return $this->addMessage($sMessage, $sType, $sChanId);
	}
	
	private function getJsonMessage($asData)
	{
		return self::JSON_PREFIX.$this->jsonConvert($asData);
	}
	
	private function downloadToTmp($sUrl)
	{
		$sFileInfo = pathinfo($sUrl);
		$sImageExt = strtolower($sFileInfo['extension']);
		$sFilePath = self::DOC_TMP_FOLDER.uniqid().'.'.$sImageExt;
		return ToolBox::createThumbnail($sUrl, self::CHAT_IMG_MAX_WIDTH, self::CHAT_IMG_MAX_HEIGHT, $sFilePath);
	}
	
	private function sendPM($iUserIdTo, $sMessage)
	{
		$bSuccess = false;
		$asUserFrom = $this->getUserInfo($this->getUserId());
		$asUserTo = $this->getUserInfo($iUserIdTo);
		$sFrom = $asUserFrom['name'].' <www-data@lutran.fr>';
	
		$sTo = $asUserTo['name'].' <'.$asUserTo['email'].'>';
		$sMessage .=  "\n\n\n".'<i>Ne r&eacute;pondez pas &agrave; ce mail. Connectez-vous sur <a href="http://databap.lutran.fr/chat">Databap</a>.</i>';
	
		$sResult = ToolBox::sendMail($sFrom, $sTo, 'Databap <www-data@lutran.fr>', 'Databap PM', $sMessage);
		$bSuccess = ($sResult=='');
		if(!$bSuccess) $this->addError($sResult);
		return $bSuccess;
	}
	
	private function addMessage($sMessage, $sType, $iChanId)
	{
		$bResult = false;
		if($iChanId>0)
		{
			$asInsert = array(	MySqlManager::getId(MySqlManager::USER_TABLE)	=> $this->getUserId(),
								'nickname'										=> $this->getChatNickNames($this->getUserId()),
								MySqlManager::getId(MySqlManager::CHAN_TABLE)	=> $iChanId,
								MySqlManager::getText(MySqlManager::MSG_TABLE)	=> $sMessage,
								'type'											=> $sType,
								'date'											=> 'CURDATE()');
			$bResult = $this->oMySql->insertRow(MySqlManager::MSG_TABLE, $asInsert);
		}
		else
		{
			$this->addError('Message deleted: No channel found');
		}
		return $bResult;
	}
	
	public function getMessages($iFirstMsgId="0")
	{
		//Update chan ping
		$this->pingChans();
		
		//Get messages and users' infos
		$sChanIdCol 			= MySqlManager::getId(MySqlManager::CHAN_TABLE, true);
		$sChanTextCol 			= MySqlManager::getText(MySqlManager::CHAN_TABLE);
		$sMsgIdCol 				= MySqlManager::getId(MySqlManager::MSG_TABLE, true);
		$sMsgTextCol 			= MySqlManager::getText(MySqlManager::MSG_TABLE);
		$sMsgTableLed 			= MySqlManager::getFullColumnName(MySqlManager::MSG_TABLE, 'led');
		$sMsgTableChanIdCol 	= MySqlManager::getFullColumnName(MySqlManager::MSG_TABLE, MySqlManager::getId(MySqlManager::CHAN_TABLE));
		$sUserIdCol 			= MySqlManager::getId(MySqlManager::USER_TABLE, true);
		$sMsgTableUserIdCol 	= MySqlManager::getFullColumnName(MySqlManager::MSG_TABLE, MySqlManager::getId(MySqlManager::USER_TABLE));
		$sUserTableUserIdCol 	= MySqlManager::getId(MySqlManager::USER_TABLE, true);
		$sConnTableUserIdCol 	= MySqlManager::getFullColumnName(MySqlManager::CONN_TABLE, MySqlManager::getId(MySqlManager::USER_TABLE));
		$sConnTableChanIdCol 	= MySqlManager::getFullColumnName(MySqlManager::CONN_TABLE, MySqlManager::getId(MySqlManager::CHAN_TABLE));
		
		//channel related messages
		$sCurDate = date(Databap::DATE_SQL_FORMAT);
		$asInfo = array('select'	=> array($sMsgTableChanIdCol, $sMsgIdCol, $sMsgTextCol, 'type', $sMsgTableLed, 'first_name', 'last_name', $sUserIdCol, 'nickname'),
						'from'		=> MySqlManager::CONN_TABLE,
						'joinOn'	=> array(	MySqlManager::MSG_TABLE	=>array($sMsgTableChanIdCol, '=', $sConnTableChanIdCol),
												MySqlManager::USER_TABLE=>array($sUserTableUserIdCol, '=', $sMsgTableUserIdCol)),
						'constraint'=> array($sConnTableUserIdCol=>$this->getUserId(),	$sMsgIdCol=>$iFirstMsgId,	'date'=>$sCurDate),
						'constOpe'	=> array($sConnTableUserIdCol=>'=', 				$sMsgIdCol=>'>', 			'date'=>'='));
		$asSqlMessages = $this->oMySql->selectRows($asInfo, true, 'id_message');
		
		//Global messages
		$asInfo['from']			= MySqlManager::MSG_TABLE;
		$asInfo['joinOn']		= array(MySqlManager::USER_TABLE=>array($sUserTableUserIdCol, '=', $sMsgTableUserIdCol));
		$asInfo['constraint']	= array($sMsgTableChanIdCol=>self::ALL_CHAN_ID,	$sMsgIdCol=>$iFirstMsgId,	'date'=>$sCurDate);
		$asInfo['constOpe']		= array($sMsgTableChanIdCol=>'=', 					$sMsgIdCol=>'>', 				'date'=>'=');
		$asSqlGlobalMessages = $this->oMySql->selectRows($asInfo, true, 'id_message');

		//Invites messages
		$asInfo['constraint']	= array($sMsgIdCol=>$iFirstMsgId,	'date'=>$sCurDate,	'type'=>self::MESSAGE_INVITE,	'message'=>$this->getUserId());
		$asInfo['constOpe']		= array($sMsgIdCol=>'>', 			'date'=>'=', 		'type'=>'=', 					'message'=>'=');
		$asSqlInviteMessages = $this->oMySql->selectRows($asInfo, true, 'id_message');

		//Merge flows
		if(!empty($asSqlGlobalMessages)) $asSqlMessages = array_diff_key($asSqlMessages, $asSqlGlobalMessages) + $asSqlGlobalMessages;
		if(!empty($asSqlInviteMessages)) $asSqlMessages = array_diff_key($asSqlMessages, $asSqlInviteMessages) + $asSqlInviteMessages;
		
		//Sort messages
		ksort($asSqlMessages);

		//Sort out messages for Json Export
		$iPrefixLen = strlen(self::JSON_PREFIX);
		$asMessages = array('messages'=>array(), 'last_message_id'=>0);
		foreach($asSqlMessages as $iMessageId=>$asMessageInfo)
		{
			$iChanId = $asMessageInfo[MySqlManager::getId(MySqlManager::CHAN_TABLE)];
			$iUserId = $asMessageInfo[MySqlManager::getId(MySqlManager::USER_TABLE)];
			$sMessageType = $asMessageInfo['type'];
			$asMessages['messages'][$iMessageId]['id_chan'] = $iChanId;
			$asMessages['messages'][$iMessageId]['message'] = $asMessageInfo[$sMsgTextCol];
			$asMessages['messages'][$iMessageId]['msg_class'] = $sMessageType;
			$asMessages['messages'][$iMessageId]['time'] = self::getDateFormat($asMessageInfo['led'], self::TIME_FORMAT);
			$asMessages['messages'][$iMessageId]['name'] = self::getNameFormat($asMessageInfo['first_name'], $asMessageInfo['last_name']);
			$asMessages['messages'][$iMessageId]['nickname'] = self::getNickNameFormat($asMessageInfo['nickname']);
			
			//generated messages
			if($sMessageType==self::MESSAGE_ADD_CODE || $sMessageType==self::MESSAGE_EDIT_CODE)
			{
				$asCode = $this->getCodeInfo($asMessages['messages'][$iMessageId]['message']);
				$asMessages['messages'][$iMessageId]['description'] = $asCode['description'];
			}
			elseif($sMessageType==self::MESSAGE_ADD_PROC || $sMessageType==self::MESSAGE_EDIT_PROC)
			{
				$asProc = $this->getProcInfo($asMessages['messages'][$iMessageId]['message']);
				$asMessages['messages'][$iMessageId]['description'] = $asProc['description'];
			}
			elseif($sMessageType==self::MESSAGE_ARTICLE)
			{
				$asTransferredInfo = $this->getArticleInfo($asMessages['messages'][$iMessageId]['message']);
				$asMessages['messages'][$iMessageId]['message'] = $this->getJsonMessage($asTransferredInfo);
			}
			
			//Switch Chan ID with name for the user to join
			if($sMessageType==self::MESSAGE_INVITE)
			{
				$asMessages['messages'][$iMessageId]['id_chan'] = $this->oMySql->selectValue(MySqlManager::CHAN_TABLE, 'safe_name', $iChanId);
			}
			
			//Json message
			if(substr($asMessages['messages'][$iMessageId]['message'], 0, $iPrefixLen) == self::JSON_PREFIX)
			{
				$asMessages['messages'][$iMessageId]['message'] = json_decode(substr($asMessages['messages'][$iMessageId]['message'], $iPrefixLen));
			}
			else //Normal message
			{
				//Dynamic web link
				$asPatterns = array('`((?:https?|ftp)://\S+[[:alnum:]]/?)`si', '`((?<!//)(www\.\S+[[:alnum:]]/?))`si');
				$asLinks = array('<a href="$1" target="_blank">$1</a> ', '<a href="http://$1" target="_blank">$1</a>');
				$asMessages['messages'][$iMessageId]['message'] = preg_replace($asPatterns, $asLinks, $asMessages['messages'][$iMessageId]['message']);
				
				//Dynamic chan link
				$asPatterns = '/(^|\s)#(\w*[^\s]+\w*)/';
				$asLinks = '\1<span class="chan_link clickable">#<span class="chan_text">\2</span></span>';
				$asMessages['messages'][$iMessageId]['message'] = preg_replace($asPatterns, $asLinks, $asMessages['messages'][$iMessageId]['message']);
			}		
		}
		
		//Set last message Id (if new messages since $iFirstMsgId)
		if(!empty($asMessages['messages']))
		{
			$asMessages['last_message_id'] = max(array_keys($asMessages['messages']));
		}
		
		return $this->jsonExport($asMessages);
	}
	
	private function getConnectedChans($iuserId=0)
	{
		$iuserId = $iuserId>0?$iuserId:$this->getUserId();
		
		$sUserIdCol = MySqlManager::getId(MySqlManager::USER_TABLE);
		$asInfo = array('select'	=> array(MySqlManager::getId(MySqlManager::CHAN_TABLE, true), MySqlManager::getText(MySqlManager::CHAN_TABLE)),
						'from'		=> MySqlManager::CONN_TABLE,
						'join'		=> array(MySqlManager::CHAN_TABLE=>MySqlManager::getId(MySqlManager::CHAN_TABLE)),
						'constraint'=> array($sUserIdCol=>$iUserId,	'led'=>"DATE_SUB(NOW(), INTERVAL ".self::KEEP_ALIVE." SECOND)"),
						'constOpe'	=> array($sUserIdCol=>'=',		'led'=>'>'),
						'constVar'	=> true);
		
		return $this->oMySql->selectRows($asInfo, true, MySqlManager::getId(MySqlManager::CONN_TABLE));
	}
	
	public function getConnectedUsers($bUserRestricted=false, $bJson=true)
	{
		$sQuery = " SELECT /* config.php 1313 */	conn2.id_channel, conn2.id_user, conn2.led, IF(DATE_SUB(NOW(), INTERVAL 1 MINUTE) < conn2.led, 0, 1) AS afk,
													users.first_name, users.last_name, companies.company, companies.logo, option_nickname.option AS nickname, option_status.option AS status
						FROM connections AS conn1
							LEFT JOIN connections AS conn2 ON conn2.id_channel = conn1.id_channel
							LEFT JOIN users ON users.id_user = conn2.id_user
							LEFT JOIN companies ON companies.id_company = users.id_company
							LEFT JOIN `options` AS option_nickname ON option_nickname.id_user = users.id_user AND option_nickname.id_option_name = ".self::OPT_NICKNAME."
							LEFT JOIN `options` AS option_status ON option_status.id_user = users.id_user AND option_status.id_option_name = ".self::OPT_STATUS."
						WHERE conn2.led > DATE_SUB(NOW(), INTERVAL ".self::KEEP_ALIVE." SECOND)".
							($bUserRestricted?"AND conn1.id_user = ".$this->getUserId():"")."
						GROUP BY conn2.id_channel, conn2.id_user
						ORDER BY option_nickname.option ASC";
		$asUserChannels = $this->oMySql->getArrayQuery($sQuery, true);

		//Last messages
		$iUserIdCol = MySqlManager::getId(MySqlManager::USER_TABLE);
		$asLastMsg = $this->oMySql->selectRows(array('select'=>array($iUserIdCol, 'MAX(led)'), 'from'=>MySqlManager::MSG_TABLE), true, $iUserIdCol);

		$asConnectedUsers = array();
		foreach($asUserChannels as $asUser)
		{
			$sChanId = $asUser[MySqlManager::getId(MySqlManager::CHAN_TABLE)];
			$iUserId = $asUser[$iUserIdCol];
			$asConnectedUsers[$sChanId][$iUserId]['id_user'] = $iUserId;
			$asConnectedUsers[$sChanId][$iUserId]['name'] = self::getNameFormat($asUser['first_name'], $asUser['last_name']);
			$asConnectedUsers[$sChanId][$iUserId]['company'] = self::getCompanyFormat($asUser['company']);
			$asConnectedUsers[$sChanId][$iUserId]['status'] = $asUser['status'];
			$asConnectedUsers[$sChanId][$iUserId]['logo'] = $asUser['logo'];
			$asConnectedUsers[$sChanId][$iUserId]['nickname'] = self::getNickNameFormat($asUser['nickname']==''?$asUser['first_name']:$asUser['nickname']);
			$asConnectedUsers[$sChanId][$iUserId]['last_activity'] = array_key_exists($iUserId, $asLastMsg)?self::getDateFormat($asLastMsg[$iUserId], self::TIME_FORMAT):'';
			$asConnectedUsers[$sChanId][$iUserId]['ping'] = self::getDateFormat($asUser['led'], self::TIME_FORMAT);
			$asConnectedUsers[$sChanId][$iUserId]['afk'] = $asUser['afk'];
		}
		return $bJson?$this->jsonExport($asConnectedUsers):$asConnectedUsers;
	}
	
	private function getChatNickNames($iUserId=0)
	{
		$sUserIdCol = MySqlManager::getId(MySqlManager::USER_TABLE);
		$sNicknameCol = MySqlManager::getText(MySqlManager::OPT_TABLE);
		$asConstraints = array(MySqlManager::getId(MySqlManager::OPTNAME_TABLE)=>self::OPT_NICKNAME);

		if($iUserId>0)
		{
			$asConstraints[MySqlManager::getId(MySqlManager::USER_TABLE)] = $iUserId;
			$oResult = $this->oMySql->selectValue(MySqlManager::OPT_TABLE, $sNicknameCol, $asConstraints);
		}
		else
		{
			$asInfo = array('select'=>array($sUserIdCol, $sNicknameCol),
							'from'=>MySqlManager::OPT_TABLE,
							'constraint'=>$asConstraints);
			$oResult = $this->oMySql->selectRows($asInfo, true, $sUserIdCol);
		}
		return $oResult;
	}
	
	private function switchCodeId(&$iCodeId, $sMode)
	{
		switch($sMode)
		{
			case 'first':
				$iCodeId = $this->oMySql->selectValue(MySqlManager::CODE_TABLE, 'refer_id', $iCodeId);
				break;
			case 'last':
				$iRefId = $this->oMySql->selectValue(MySqlManager::CODE_TABLE, 'refer_id', $iCodeId);
				$iCodeId = $this->oMySql->selectValue(MySqlManager::CODE_TABLE, 'MAX(id_code)', array('refer_id'=>$iRefId));
				break;
		}
	}
	
	private function getIdCodeFromPhrase($sPhrase)
	{
		$iCodeId = $this->oMySql->selectValue(MySqlManager::URL_TABLE, MySqlManager::getId(MySqlManager::CODE_TABLE), array('phrase'=>$sPhrase));
		$this->switchCodeId($iCodeId, 'last');
		return $iCodeId;
	}

	private function getPhraseFromIdCode($iCodeId)
	{
		$this->switchCodeId($iCodeId, 'first');
		return $this->oMySql->selectValue(MySqlManager::URL_TABLE, 'phrase', array('id_code'=>$iCodeId));
	}
	
	private function getCodeVersions($iRefCodeId)
	{
		$asInfo = array('select'=>array(MySqlManager::getId(MySqlManager::CODE_TABLE)),
						'from'=>MySqlManager::CODE_TABLE, 
						'constraint'=>array('refer_id'=>$iRefCodeId), 
						'orderBy'=>array('id_code'=>'asc'));
		$asCodeIds = $this->oMySql->selectRows($asInfo);
		
		$asCodeVersions = array();
		foreach($asCodeIds as $iCodeId)
		{
			$asCodeVersions[] = $this->getCodeInfo($iCodeId);
		}
		return $asCodeVersions;
	}
		
	public function getColoredCode($oCode)
	{
		$asCode = $this->getCodeInfo($oCode, '', true);

		//code
		$oReader = new Reader($asCode['code']);
		$asCode['code'] = $oReader->getColoredCode();
		
		//phrase
		$sPhrase = $this->getPhraseFromIdCode($asCode['id_code']);
		if($sPhrase !== false)
		{
			$asCode['phrase'] = $sPhrase; 
		}
		
		//user
		$asUsers[$asCode['id_user']] = $this->getUserInfo($asCode['id_user']);
		$asCode = array_merge($asCode, $asUsers[$asCode['id_user']]);

		//versions
		$asCodeVersions = $this->getCodeVersions($asCode['refer_id']);
		$iCodeRowId = 0;
		foreach($asCodeVersions as $iRowId=>$asCodeVersion)
		{
			if($asCodeVersion['id_code']==$asCode['id_code']) $iCodeRowId = $iRowId;
		}
		$asCode['truncated'] = ($iCodeRowId > self::MAX_LIST_LENGTH)?$asCodeVersions[$iCodeRowId-1-self::MAX_LIST_LENGTH]['id_code']:0;
		foreach($asCodeVersions as $iRowId=>$asCodeVersion)
		{
			if($iCodeRowId - $iRowId <= self::MAX_LIST_LENGTH)
			{
				if(!array_key_exists($asCodeVersion['id_user'], $asUsers))
				{
					$asUsers[$asCodeVersion['id_user']] = $this->getUserInfo($asCodeVersion['id_user']);
				}
				
				if($asCodeVersion['id_code']!=$asCode['id_code'])
				{
					$asVersionInfo = array_merge($asCodeVersion, $asUsers[$asCodeVersion['id_user']]);
					$asCode['other_versions'][] = $asVersionInfo;
				}
			}
		}

		return $this->jsonExport($asCode);
	}

	public function getNudeCode($oCode)
	{
		$asCode = $this->getCodeInfo($oCode, '', true);
		//$sCode = Reader::convText2Html($asCode['code']);
		return $asCode['code'];
	}
	
	public function getRawCode($oCode)
	{
		$asCode = $this->getCodeInfo($oCode, '', true);
		$asUser = $this->getUserInfo($asCode['id_user']);		
		$sEncodedCode = $this->jsonConvert($asCode['code']);
		$sEncodedDesc = $this->jsonConvert($asCode['description']);
		
		return '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
				<html xmlns="http://www.w3.org/1999/xhtml">
				<head>
					<meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
					<meta name="author" content="'.$asUser['name'].' ('.$asUser['company'].')" />
					<link rel="shortcut icon" href="images/favicon_gc3.ico" />
					<title>Databap &bull;</title>
				</head>
				<body>
					<pre id="content"></pre>
					<script>
						document.getElementById("content").textContent = '.$sEncodedCode.';
						document.title = document.title+" "+'.$sEncodedDesc.';
					</script>
				</body>
				</html>';
	}

	public function getSavedCode($oCode)
	{
		$asCode = $this->getCodeInfo($oCode, '', true);
		$sPhrase = $this->getPhraseFromIdCode($asCode['id_code']);
		if(!$sPhrase)
		{
			$sPhrase = 'code_numero_'.$asCode['id_code'];
		}
		
		//set headers
		header('Pragma: public');
		header('Expires: 0');
		header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
		header('Cache-Control: public');
		header('Content-Description: File Transfer');
		header('Content-Type: application/force-download');
		header('Content-Disposition: attachment; filename='.$sPhrase.'.abap');
		header('Content-Transfer-Encoding: binary');
		//header('Content-Length: '.filesize($sFilePath));

		return $asCode['code'];
	}

	public function getPrintCode($oCode)
	{
		return $this->getRawCode($oCode).'<script>window.print();</script>';
	}
	
	public function getItemList()
	{
		$sIdCodeCol = MySqlManager::getId(MySqlManager::CODE_TABLE);
		$sIdProcCol = MySqlManager::getId(MySqlManager::PROC_TABLE);
		$sIdArtCol = MySqlManager::getId(MySqlManager::ART_TABLE);
		$sIdCodeColFull = MySqlManager::getId(MySqlManager::CODE_TABLE, true);
		$sIdUserCol = MySqlManager::getId(MySqlManager::USER_TABLE);
		$sIdUserColFull = MySqlManager::getId(MySqlManager::USER_TABLE, true);
		$sLedColFull = MySqlManager::getFullColumnName(MySqlManager::CODE_TABLE, 'led') ;
		
		//Get code Ids
		$asInfo = array('select'=>array("MAX($sIdCodeCol) AS id_item"),
						'from'=>MySqlManager::CODE_TABLE,
						'groupBy'=>array('refer_id'),
						'orderBy'=>array('id_item'=>'desc'));
		$asCodeIds = $this->oMySql->selectRows($asInfo);
		
		//get Proc Ids
		$asInfo = array('select'=>array("MAX($sIdProcCol) AS id_item"),
						'from'=>MySqlManager::PROC_TABLE,
						'constraint'=>array('refer_id'=>"0"),
						'orderBy'=>array('id_item'=>'desc'));
		$asProcIds = $this->oMySql->selectRows($asInfo);

		//get Article Ids
		$asInfo = array('select'=>array($sIdArtCol." AS id_item"),
						'from'=>MySqlManager::ART_TABLE,
						'orderBy'=>array('id_item'=>'desc'));
		$asArtIds = $this->oMySql->selectRows($asInfo);
		
		$asItemIds = array(Databap::CODE_TYPE=>$asCodeIds, Databap::PROC_TYPE=>$asProcIds, Databap::ART_TYPE=>$asArtIds);
		
		//Build code info structure
		$asUsers = $asCode = $asItemList = array();
		foreach($asItemIds as $sType=>$asTypedItemIds)
		{
			switch($sType)
			{
				case Databap::CODE_TYPE:
					$sFuncType = 'Code';
					break;
				case Databap::PROC_TYPE:
					$sFuncType = 'Proc';
					break;
				case Databap::ART_TYPE:
					$sFuncType = 'Article';
					break;	
			}
			foreach($asTypedItemIds as $iItemId)
			{
				//null value returned from query (MAX(...))
				if($iItemId>0)
				{
					$asItem = call_user_func(array($this, 'get'.$sFuncType.'Info'), $iItemId);
					if(array_key_exists($sIdUserCol, $asItem))
					{
						$iUserId = $asItem[$sIdUserCol];
						if(!array_key_exists($iUserId, $asUsers))
						{
							$asUsers[$iUserId] = $this->getUserInfo($iUserId);
						}
						$asItemList[$sType][$iItemId] = array_merge($asUsers[$iUserId], $asItem);
					}
					else $asItemList[$sType][$iItemId] = $asItem;
					
					//$asCodeList[$iItemId]['phrase'] = $this->getPhraseFromIdCode($iCodeId);
					
				}
			}
		}
		return $this->jsonExport($asItemList);
	}

	public function getCodeBlock()
	{
		$oMask = new Mask();
		$oMask->initFile('code_block');
		return $oMask->getMask();
	}

	public static function checkNoPassResetAction($sAction)
	{
		return in_array($sAction, array('messages', 'upload_image', 'user_info', 'rss'));
	}
	
	public function logMeIn($sName, $sCompany, $sToken, $sAction)
	{
		$iUserId = 0;
		$bResetPass = true;
		$sUserTableId = MySqlManager::getId(MySqlManager::USER_TABLE);
		$sCompanyTableId = MySqlManager::getId(MySqlManager::COMP_TABLE);
		$sCompanyTableText = MySqlManager::getText(MySqlManager::COMP_TABLE);

		//login using form
		if($sName!='' && $sCompany!='')
		{
			$asNames = explode(' ', strtolower($sName));
			$sCompany = strtolower($sCompany);
			
			//Get Company's Id
			$iCompanyId = $this->oMySql->selectValue(MySqlManager::COMP_TABLE, $sCompanyTableId, array($sCompanyTableText=>$sCompany)); 
			
			$asConsts = array('first_name'=>$asNames[0], 'last_name'=>$asNames[1], $sCompanyTableId=>$iCompanyId);
			$iUserId = $this->oMySql->selectValue(MySqlManager::USER_TABLE, $sUserTableId, $asConsts);
			if(!$iUserId)
			{
				$asInvConsts = array('first_name'=>$asNames[1], 'last_name'=>$asNames[0], $sCompanyTableId=>$iCompanyId);
				$iUserId = $this->oMySql->selectValue(MySqlManager::USER_TABLE, $sUserTableId, $asInvConsts);
			}
		}
		//auto login by cookie
		elseif(isset($_COOKIE[self::USER_COOKIE_ID]))
		{
			$asConstraints = array(	$sUserTableId=>$_COOKIE[self::USER_COOKIE_ID],
									'pass'=>$_COOKIE[self::USER_COOKIE_PASS]);
			
			$asUserInfo = $this->oMySql->selectRow(MySqlManager::USER_TABLE, $asConstraints, array($sUserTableId, 'led'));
			$iUserId = $asUserInfo[$sUserTableId];
			
			//Check pass reset necessity
			$bResetPass = (substr($asUserInfo['led'], 0, 10) != date(Databap::DATE_SQL_FORMAT));
		}
		//Login using Token (limited access)
		elseif($sAction==self::EXT_ACCESS && $sToken!='')
		{
			$iUserId = $this->checkToken($sToken);
			$bResetPass = false;
		}
		
		if($iUserId>0)
		{
			$this->setUserId($iUserId);
			if(!self::checkNoPassResetAction($sAction) && $bResetPass)
			{
				$this->resetPassword();
			}
		}

		return ($this->getUserId()>0);
	}
	
	public function checkToken($sKey)
	{
		$iUserId = strstr($sKey, '_', true);
		$sToken = substr($sKey, strlen($iUserId)+1);
		return (strlen($sToken)==self::TOKEN_LENGTH && $this->generateToken($iUserId)==$sToken)?$iUserId:0;
	}
	
	public function resetNecessary($iUserId)
	{
		$sLed = substr($this->oMySql->selectValue(MySqlManager::USER_TABLE, 'led', $iUserId), 0, 10);
		return $sLed != date(Databap::DATE_SQL_FORMAT);
	}
	
	public function resetPassword()
	{
		$iUserId = $this->getUserId();
		$sNewPass = self::getCookiePass();
		$iTimeLimit = time()+60*60*24*self::COOKIE_LIFETIME;
		$this->oMySql->updateRow(MySqlManager::USER_TABLE, $iUserId, array('pass'=>$sNewPass));
		setcookie(self::USER_COOKIE_ID, $iUserId, $iTimeLimit);
		setcookie(self::USER_COOKIE_PASS, $sNewPass, $iTimeLimit);
	}

	public function logMeOut()
	{
		$this->disconnectChat();
		$this->setUserId(0);
		setcookie(self::USER_COOKIE_ID, '', time()-60*60);
		setcookie(self::USER_COOKIE_PASS, '', time()-60*60);
	}

	/* Not needed so far
	public function setExpectedPage($sExpectedPage)
	{
		setcookie(self::EXPECTED_PAGE_COOKIE, $sExpectedPage, time()+60*60);
	}

	public function redirectExpectedPage()
	{
		if(array_key_exists(self::EXPECTED_PAGE_COOKIE, $_COOKIE))
		{
			$sLocation = $_COOKIE[self::EXPECTED_PAGE_COOKIE];
			setcookie(self::EXPECTED_PAGE_COOKIE, '', time()-60*60);
			header('Location:'.$sLocation);
		}
	}
	*/
	
	public function redirectArticle($iArtId)
	{
		$asArtInfo = $this->getArticleInfo($iArtId);
		header('Location:'.$asArtInfo['link_art']);
	}
	
	public function disconnectChat()
	{
		//$this->getConnectedChans();
		
		$sTime = $this->oMySql->selectRows(array('select'=>'DATE_SUB(NOW(), INTERVAL '.self::KEEP_ALIVE.' SECOND)'));
		$this->oMySql->updateRows(MySqlManager::CONN_TABLE, array(MySqlManager::getId(MySqlManager::USER_TABLE)=>$this->getUserId()), array('led'=>$sTime));
		$this->addMessage('se d&eacute;connecte', self::MESSAGE_CONN, self::ALL_CHAN_ID);
	}

	public function checkValue($sTableName, $asConstraints)
	{
		return $this->oMySql->selectValue($sTableName, 'COUNT(1)', $asConstraints);
	}

	private static function getCookiePass()
	{
		return self::encryptPassword(	$_SERVER['HTTP_USER_AGENT'].
		$_SERVER['REMOTE_ADDR'].
		$_SERVER['REQUEST_TIME'].
		strstr(microtime(), ' ', true).
		$_SERVER['SERVER_SIGNATURE'].
		$_SERVER['SERVER_ADMIN']);
	}

	private static function encryptPassword($sPass)
	{
		$sRandomText = 'F_RA-1H"2{bvj)5f?0sd3r#fP,K]U|w}hGiN@(sZ.sDe!7*x/:Mq+&';
		for($iIndex=0; $iIndex < strlen($sPass); $iIndex++)
		{
			$sPass[$iIndex] = $sRandomText[$iIndex%strlen($sRandomText)] ^ $sPass[$iIndex];
		}
		return md5($sPass);
	}
	
	public static function getChanSafeName($sChanName)
	{
		$sChanSafeName = preg_replace('/[^a-z0-9]/', '_', strtolower($sChanName));
		
		//Sort PM chans
		$asPm = self::isPmChan($sChanSafeName);
		if(!$asPm['is_pm']) $sChanSafeName = $asPm['chan_name'];
		
		return $sChanSafeName;
	}

	public function getResults($sSearchWords)
	{
		$this->oSearchEngine->setWords($sSearchWords);
		$asResults = $this->oSearchEngine->getResults();
		
		//complementary infos
		$asCompleteResults = array();
		foreach($asResults as $iCodeId=>$asCodeInfo)
		{
			//phrase
			$sPhrase = $this->getPhraseFromIdCode($asCodeInfo['id_code']);
			if($sPhrase !== false)
			{
				$asCodeInfo['phrase'] = $sPhrase; 
			}
			$asCompleteResults[$iCodeId] = $asCodeInfo;
		}
		
		return $this->jsonExport($asCompleteResults);
	}
	
	public function getStyleSheet()
	{
		$sStyle = file_get_contents(self::STYLE_PATH);
		$asDefaultColors = array(	self::OPT_BG=>'#04357B',
									self::OPT_BRIGHT_BG=>'#D9E5F2',
									self::OPT_HOVER=>'#EFAB00',
									self::OPT_IMAGE_CHAT=>'images/sap_gold_332.jpg');
		
		//Inserting Color Ids
		foreach($asDefaultColors as $iOptionNameId=>$sDefaultColor)							
		{							
			$asColorAnchors[$iOptionNameId] = '[OPT#'.$iOptionNameId.']';
		}
		$sStyle = str_replace($asDefaultColors, $asColorAnchors, $sStyle);
		
		//Switching color Ids with user colors
		$asOptionvalues = $this->getUserOptions(array_keys($asDefaultColors));
		foreach($asColorAnchors as $iOptionNameId=>$sColorAnchor)
		{
			$sOptionValue = (array_key_exists($iOptionNameId, $asOptionvalues) && $asOptionvalues[$iOptionNameId]['option']!='')?$asOptionvalues[$iOptionNameId]['option']:$asDefaultColors[$iOptionNameId];
			$sStyle = str_replace($sColorAnchor, $sOptionValue, $sStyle);
		}
		
		//setting header content type
		header("Content-Type: text/css");
		return $sStyle;
	}

	public static function getDateFormat($oTime, $sFormat=Databap::DATE_TIME_FORMAT)
	{
		$iTimeStamp = is_numeric($oTime)?$oTime:strtotime($oTime);
		return date($sFormat, $iTimeStamp);
	}
	
	public static function getNameFormat($sFirstName, $sLastName)
	{
		return ToolBox::capitalizeWords(trim($sFirstName.' '.$sLastName), ' -');;
	}
	
	public static function getNickNameFormat($sNickName)
	{
		$sNickName = ToolBox::capitalizeWords(trim($sNickName), ' -');
		return str_replace(' ', '_', $sNickName);
	}

	public static function getCompanyFormat($sCompany)
	{
		return ucwords($sCompany);
	}
	
	public static function getDescriptionFormat($sDescription)
	{
		return ucfirst($sDescription);
	}
	
	public static function jsonExport($asData, $bConvert=false)
	{
		header('Content-type: application/json');
		if($bConvert)
		{
			$asData = MySqlManager::cleanData($asData, 'utf8_encode');
		}
		return self::jsonConvert($asData);
	}
	
	public static function jsonConvert($asData)
	{
		//return htmlspecialchars(json_encode($asData), ENT_NOQUOTES);
		return json_encode($asData);
	}
	
	public static function getMaxSize()
	{
		$iPostSize = self::toBytes(ini_get('post_max_size'));
		$iUploadSize = self::toBytes(ini_get('upload_max_filesize'));
		
		return min($iPostSize, $iUploadSize);
	}
	
	public static function toBytes($str)
	{
		$val = trim($str);
		$last = strtolower($str[strlen($str)-1]);
		switch($last) {
			case 'g': $val *= 1024;
			case 'm': $val *= 1024;
			case 'k': $val *= 1024;		
		}
		return $val;
	}
}

/**
 * TODO
 * Code Manager
 * @author franzz
 */
class Code extends PhpObject
{
	//Objects
	private $oMySql;
	
	//Database tables and columns
	private $sProcTable;
	
	
	function __construct($oMySql)
	{
		parent::__construct();
		$this->oMySql = $oMySql;
	}
}

/**
 * Procedure Manager
 * @author franzz
 */
class Procedure extends PhpObject
{
	//Constants
	const THUMB_MAX_SIZE = 100;
	const IMAGE_FOLDER = 'screenshot/';
	const IMAGE_FOLDER_TMP = 'screenshot/tmp/';
	const IMAGE_FOLDER_THUMB = 'screenshot/thumbnails/';
	
	//Objects
	private $oMySql;

	//Procedure Elements
	private $iProcId;
	private $iUserId;
	private $sTitle;
	private $sDescription;
	private $dLed;
	private $asSteps; // [id_step]=>text
	private $asImages; // [id_step][id_image]=>array('name'=>file name, 'desc'=> description)
	
	function __construct($oMySql)
	{
		parent::__construct();
		$this->oMySql = $oMySql;
		$this->iPrevProcId = 0;
		$this->setProcedureId(0);
		$this->sTitle = '';
		$this->sDescription = '';
		$this->dLed = '';
		$this->asSteps = array();
		$this->asImages = array();
	}
	
	public function getProcedureId()
	{
		return $this->iProcId;
	}
	
	public function getProcedureLed()
	{
		return $this->dLed;
	}
	
	public function setProcedureId($iProcId)
	{
		$this->iProcId = $iProcId;
	}
	
	public function inputForm($asPost)
	{
		//User
		$this->iUserId = $asPost['id_user'];
		
		//Titles
		$this->sTitle = isset($asPost['title'])?$asPost['title']:'';
		$this->sDescription = isset($asPost['description'])?$asPost['description']:'';
		
		//Steps
		$sStepPattern = '/c(?P<step_id>\d+)_step_text/';
		$sImagePattern = '/c(?P<step_id>\d+)_(?P<image_id>\d+)_image_(?P<image_info>\w+)/';
		foreach($asPost as $sFormId=>$sValue)
		{
			//Step Text
			preg_match($sStepPattern, $sFormId, $asMatches);
			if(isset($asMatches['step_id']))
			{
				$this->asSteps[$asMatches['step_id']] = $sValue;
			}
			else
			{
				preg_match($sImagePattern, $sFormId, $asMatches);
				if
				(
					isset($asMatches['step_id'], $asMatches['image_id'], $asMatches['image_info']) &&
					($asMatches['image_info'] == 'name' || $asMatches['image_info']== 'desc')
				)
				{
					$this->asImages[$asMatches['step_id']][$asMatches['image_id']][$asMatches['image_info']] = $sValue;					
				}
			}
		}
	}
	
	private function isImageSaved($sFileName, $bInTemp=false)
	{
		$sPath = $bInTemp?self::IMAGE_FOLDER_TMP:self::IMAGE_FOLDER;
		return file_exists($sPath.$sFileName);
	}
	
	public function checkIntegrity()
	{
		$asErrors = array();
		
		//Check title
		if($this->sTitle=='') $asErrors['title'] = 'Un titre est n&eacute;cessaire';
		
		//Check steps
		if(count($this->asSteps)==0) 
		{
			$asErrors['step'] = 'Au moins une &eacute;tape est n&eacute;cessaire';
		}
		else
		{
			foreach($this->asSteps as $iStepId=>$sText)
			{
				if($sText=='')
				{
					$asErrors['step_'.$iStepId] = 'Une &eacute;tape ne poss&egrave;de pas de texte';
				}
			}
		}
		
		//Check images
		foreach($this->asImages as $iStepId=>$asImages)
		{
			foreach($asImages as $iImageId=>$asFileInfo)
			{
				//file exists in temp or already stored
				if(!$this->isImageSaved($asFileInfo['name']) && !$this->isImageSaved($asFileInfo['name'], true))
				{
					$asErrors['image_'.$iStepId.'_'.$iImageId] = 'Une image n\'a pas &eacute;t&eacute; t&eacute;l&eacute;charg&eacute;e';
				}
				if($asFileInfo['desc']=='')
				{
					$asErrors['image_'.$iStepId.'_'.$iImageId] = 'Une image n\'a pas de nom (description)';
				}
			}
		}
		return $asErrors;
	}
	
	public function saveProcedure($iPrevProcId)
	{
		//Add new procedure
		$asData = array('title'=>$this->sTitle, 'description'=>$this->sDescription, MySqlManager::getId(MySqlManager::USER_TABLE)=>$this->iUserId);
		$iDbProcId = $this->oMySql->insertRow(MySqlManager::PROC_TABLE, $asData);
		
		//Add new steps
		$asStepData = $asImgData = array(MySqlManager::getId(MySqlManager::PROC_TABLE)=>$iDbProcId);
		foreach($this->asSteps as $iStepId=>$sStepDesc)
		{
			$asStepData['description'] = $sStepDesc;
			$iDbStepId = $this->oMySql->insertRow(MySqlManager::STEP_TABLE, $asStepData);
			
			//Add new images
			$asImgData[MySqlManager::getId(MySqlManager::STEP_TABLE)] = $iDbStepId;
			foreach($this->asImages[$iStepId] as $asImageInfo)
			{
				$asImgData['description'] = $asImageInfo['desc'];
				$asImgData['file_name'] = $asImageInfo['name'];
				$iDbImageId = $this->oMySql->insertRow(MySqlManager::IMG_TABLE, $asImgData);
				$this->saveImage($asImgData['file_name']);
			}
		}
		
		//Add this procedure to the group
		$bCreation = ($iPrevProcId==0);
		//$this->oMySql->deleteRow(MySqlManager::PROC_TABLE, $this->iPrevProcId);
		//$this->oMySql->updateRow(MySqlManager::PROC_TABLE, $iPrevProcId, array('refer_id'=>$iDbProcId));
		$iReferId = $bCreation?$iDbProcId:$this->oMySql->selectValue(MySqlManager::PROC_TABLE, 'refer_id', $iPrevProcId);
		$this->oMySql->updateRow(MySqlManager::PROC_TABLE, $iDbProcId, array('refer_id'=>$iReferId));
		
		//Update new Procedure Id
		//$this->setProcedureId($iDbProcId);
		
		//Reload Procedure
		$this->loadProcedure($iDbProcId);
		
		//return creation or edition
		return $bCreation;
	}
	
	private function saveImage($sFileName)
	{
		if(!$this->isImageSaved($sFileName))
		{
			$sTempFilePath = self::IMAGE_FOLDER_TMP.$sFileName;
			$sFilePath = self::IMAGE_FOLDER.$sFileName;
			
			//Real Size Image
			if(!rename($sTempFilePath, $sFilePath))
			{
				$this->addError('Unmoveable file : '.$sTempFilePath);
			}
			
			//Thumbnail picture
			$asResult = ToolBox::createThumbnail($sFilePath, self::THUMB_MAX_SIZE, self::THUMB_MAX_SIZE, self::IMAGE_FOLDER_THUMB);
			if($asResult['error']!='') $this->addError('Unable to create thumbnail : '.$asResult['out']. '('.$asResult['error'].')');
		}
	}
	
	public function loadProcedure($iProcId)
	{
		//Column names
		$sProcIdCol = MySqlManager::getId(MySqlManager::PROC_TABLE);
		$sStepIdCol = MySqlManager::getId(MySqlManager::STEP_TABLE);
		$sImageIdCol = MySqlManager::getId(MySqlManager::IMG_TABLE);
		
		//Get last id available
		$this->setProcedureId($iProcId);
		$this->refreshProcId();
		$asInfo = array('from'=>MySqlManager::PROC_TABLE, 'constraint'=>array($sProcIdCol=>$this->getProcedureId()));
		
		//Load procedure info
		$asResult = $this->oMySql->selectRow(MySqlManager::PROC_TABLE, $asInfo['constraint']);
		$this->sTitle = $asResult['title'];
		$this->sDescription = $asResult['description'];
		$this->iUserId = $asResult[MySqlManager::getId(MySqlManager::USER_TABLE)];
		$this->dLed = $asResult['led'];
		
		//Load steps info
		$asInfo['from'] = MySqlManager::STEP_TABLE;
		$this->asSteps = $this->oMySql->selectRows($asInfo, true, $sStepIdCol);
		
		//Load images info
		$asInfo['from'] = MySqlManager::IMG_TABLE;
		foreach($this->oMySql->selectRows($asInfo) as $asRow)
		{
			$this->asImages[$asRow[$sStepIdCol]][$asRow[$sImageIdCol]]['name'] = $asRow['file_name'];
			$this->asImages[$asRow[$sStepIdCol]][$asRow[$sImageIdCol]]['desc'] = $asRow['description'];
		}
	}
	
	public function getProcedure()
	{
		// [id_step]=>text
		// [id_step][id_image]=>array('name'=>file name, 'desc'=> description)
		/*
		$asSteps = $asImages = array();
		$iLocalStepId = 1;
		foreach($this->asSteps as $iStepId=>$sStepDesc)
		{
			$asSteps[$iLocalStepId] = $sStepDesc;
			if(array_key_exists($iStepId, $this->asImages))
			{
				$asImages[] = 
			}
			$iLocalStepId++;
		}*/
		return array(	'proc_id'=>$this->iProcId,
						'id_user'=>$this->iUserId,
						'title'=>Databap::getDescriptionFormat($this->sTitle), 
						'description'=>Databap::getDescriptionFormat($this->sDescription),
						'led'=>Databap::getDateFormat($this->dLed),
						'steps'=>$this->asSteps,
						'images'=>$this->asImages);
	}
	
	private function refreshProcId()
	{
		/*
		$iReferId = $this->getProcedureId();
		do
		{
			$iProcId = $iReferId;
			$iReferId = $this->oMySql->selectValue(MySqlManager::PROC_TABLE, 'refer_id', $iProcId);
		}
		while($iReferId!=0);
		$this->setProcedureId($iProcId);
		*/
		$iReferId = $this->oMySql->selectValue(MySqlManager::PROC_TABLE, 'refer_id', $this->getProcedureId());
		$sSelect = "MAX(".MySqlManager::getId(MySqlManager::PROC_TABLE).")";
		$iProcId = $this->oMySql->selectValue(MySqlManager::PROC_TABLE, $sSelect, array('refer_id'=>$iReferId));
		$this->setProcedureId($iProcId);
	}
}

/* if feeling the need of creating a tables for code lines :
 codes :
 ALTER TABLE codes ADD line_low int(10) AFTER code;
 ALTER TABLE codes ADD line_high int(10) AFTER line_low;

 lines :
 id_line
 id_code
 line
 */
class SearchEngine extends PhpObject
{
	//Objects
	private $oMySql;

	//variables
	private $asWords;
	private $iLevelMax;
	private $asItemRanks;
	private $asItemInfos;
	private $asCodeInfos;
	private $asUserInfos;

	//Constants
	const RESULT_A_PAGE = 20;
	const KEYWORDS_SEPARATOR = ' ';

	function __construct($oMySql)
	{
		parent::__construct();
		$this->oMySql = $oMySql;
		$this->asWords = $this->setWords('');
		$this->asItemRanks = array();
		$this->asItemInfos = array();
		$this->asCodeInfos = array();
		$this->asUserInfos = array();
	}
	
	public function buildIndex($iItemId, $sType)
	{
		//Build keywords
		$sWords = '';
		switch($sType)
		{
			case Databap::CODE_TYPE:
				$asItemData = $this->oMySql->selectRow(MySqlManager::CODE_TABLE, $iItemId);
				$sWords = $asItemData[MySqlManager::getText(MySqlManager::CODE_TABLE)].self::KEYWORDS_SEPARATOR.$asItemData['description'];
				break;
			case Databap::PROC_TYPE:
				$asItemData = $this->oMySql->selectRow(MySqlManager::PROC_TABLE, $iItemId);
				$asItemStepsData = $this->oMySql->selectRows(array('select'=>'description', 'from'=>MySqlManager::STEP_TABLE, 'constraint'=>array('id_procedure'=>$iItemId)));	
				$sWords =	$asItemData['description'].self::KEYWORDS_SEPARATOR.
							$asItemData['title'].self::KEYWORDS_SEPARATOR.
							implode(self::KEYWORDS_SEPARATOR, $asItemStepsData);
				break;
			case Databap::ART_TYPE:
				$asItemData = $this->oMySql->selectRow(MySqlManager::ART_TABLE, $iItemId);
				$sWords = 	$asItemData['first_name'].self::KEYWORDS_SEPARATOR.
							$asItemData['last_name'].self::KEYWORDS_SEPARATOR.
							$asItemData['title'];
				break;
			default:
				$this->addError('function'.__FUNCTION__.'(): Incorrect type "'.$sType.'"');
				break;
		}
		$sWords = strtolower(str_replace("\n", self::KEYWORDS_SEPARATOR, $sWords));
		$sWords = preg_replace('/(\W+)/', self::KEYWORDS_SEPARATOR, $sWords);
		
		//Add / Modify search database
		$asData = array('id_item'=>$iItemId, 'type'=>$sType, 'refer_id'=>$asItemData['refer_id'], 'keywords'=>$sWords);
		$this->oMySql->insertUpdateRow(MySqlManager::SEARCH_TABLE, $asData, array('id_item', 'type'));
	}

	public function setWords($sSearchWords)
	{
		$this->asWords = $this->getParsedWords($sSearchWords);
		$this->iLevelMax = count($this->asWords);
		$this->setResults();
	}

	private function setResults()
	{
		if($this->iLevelMax>0)
		{
			//set Results and Ranking
			$aiLevels = range(1, $this->iLevelMax);
			arsort($aiLevels);
			foreach($aiLevels as $iLevel)
			{
				//all possibilies at level $iLevel
				$iIndex = 0;
				while(($iIndex + $iLevel) <= $this->iLevelMax)
				{
					//building query
					$asSequence = array_slice($this->asWords, $iIndex, $iLevel);
					$this->oMySql->cleanSql($asSequence);
					
					//$sRegExp = implode('(.{0,2})', $asSequence);
					$sRegExp = implode(' ?', $asSequence);
					
					//TODO replace with selectRow()
					$sQuery = "SELECT id_search, id_item, type FROM searchs WHERE keywords REGEXP '{$sRegExp}'";
					
					//search sequence
					$asRows = $this->oMySql->getArrayQuery($sQuery, true);
					foreach($asRows as $asRow)
					{
						$iItemId = $asRow['id_item'];
						unset($asRow['id_item']);
						
						//TODO switch case type
						switch($asRow['type'])
						{
							case Databap::CODE_TYPE:
								$this->setCodeInfo($iItemId);
								$this->setUserInfo($this->getCodeInfo($iItemId, MySqlManager::getId(MySqlManager::USER_TABLE)));
								break;
						}
						
						$this->asItemInfos[$iItemId] = $asRow;
						$this->incItemRank($iItemId, $iLevel);
					}
					$iIndex++;
				}
			}
		}
	}
	
	public function getResults()
	{
		$asResult = array();
		
		//Mixing info
		arsort($this->asItemRanks);
		foreach($this->asItemRanks as $iItemId=>$iRank)
		{
			switch($this->asItemInfos[$iItemId]['type'])
			{
				case Databap::CODE_TYPE:
					//$asRow['code'] = $this->formatCode($asRow['code'], $sRegExp);
					break;
				case Databap::PROC_TYPE:
					
					break;
				default:
					continue;
			}
			
			//code
			$iUserId = $this->getCodeInfo($iItemId, MySqlManager::getId(MySqlManager::USER_TABLE));
			/* TODO
			$sCodeLines = implode("\n...\n", $this->getCodeInfo($iItemId, 'code')); 
			$oCode = new Reader($sCodeLines);
			$sCodeLines = $oCode->getColoredCode();
		 	*/
			$sCodeLines = '';
			$asResult[$iItemId] = array('id_code'=>$iItemId,
										'code'=>$sCodeLines,
										'description'=>$this->getCodeInfo($iItemId, 'description'),
										'name'=>Databap::getNameFormat($this->getUserInfo($iUserId, 'first_name'), $this->getUserInfo($iUserId, 'last_name')),
										'company'=>Databap::getCompanyFormat($this->getUserInfo($iUserId, 'company')),
										'led'=>Databap::getDateFormat($this->getCodeInfo($iItemId, 'led')));
		}
		
		return $asResult;
	}

	private function getCodeInfo($iCodeId, $sInfoName)
	{
		return $this->asCodeInfos[$iCodeId][$sInfoName];
	}
	
	private function getUserInfo($iUserId, $sInfoName)
	{
		return $this->asUserInfos[$iUserId][$sInfoName];
	}

	private function setCodeInfo($iCodeId)
	{
		if(!array_key_exists($iCodeId, $this->asCodeInfos))
		{
			$this->asCodeInfos[$iCodeId] = $this->oMySql->selectRow
			(
				MySqlManager::CODE_TABLE,
				$iCodeId,
				array(MySqlManager::getId(MySqlManager::USER_TABLE), 'description', 'led')
			);
		}
		/*
		else
		{
			$this->asCodeInfos[$iCodeId]['code'] = array_unique(array_merge($this->asCodeInfos[$iCodeId]['code'], $asInfos['code']));
		}
		*/
	}

	private function setUserInfo($iUserId)
	{
		if(!array_key_exists($iUserId, $this->asUserInfos))
		{
			$sCompanyIdCol = MySqlManager::getId(MySqlManager::COMP_TABLE);
			$this->asUserInfos[$iUserId] = $this->oMySql->selectRow
			(
				MySqlManager::USER_TABLE,
				$iUserId,
				array('first_name', 'last_name', $sCompanyIdCol)
			);
			
			$this->asUserInfos[$iUserId]['company'] = $this->oMySql->selectValue(MySqlManager::COMP_TABLE, MySqlManager::getText(MySqlManager::COMP_TABLE), $this->asUserInfos[$iUserId][$sCompanyIdCol]);
			unset($this->asUserInfos[$iUserId][$sCompanyIdCol]);
		}
	}

	private function incItemRank($iItemId, $iRank)
	{
		if(array_key_exists($iItemId, $this->asItemRanks))
		{
			$this->asItemRanks[$iItemId] += $iRank;
		}
		else
		{
			$this->asItemRanks[$iItemId] = $iRank;
		}
	}

	private function formatCode($sCode, $sPattern)
	{
		preg_match_all('/\n(.*)\n(.*)'.$sPattern.'(.*)\n(.*)\n/i', $sCode, $asMatches);
		if(!array_key_exists(0, $asMatches) || !is_array($asMatches[0]) || count($asMatches[0])==0)
		{
			$asResult = array(implode("\n", array_slice(explode("\n", $sCode), 0, 3))."\n[...]");
		}
		else
		{
			$asResult = $asMatches[0];
		}
		return $asResult;
	}

	private function getParsedWords($sSearchWords)
	{
		return array_unique(array_filter(explode(' ', $sSearchWords), array($this, 'checkSearchedWords')));
	}

	private function checkSearchedWords($sWord)
	{
		return (strlen($sWord) >= 2);
	}
}

/**
 * Abap Reader
 * @author franzz
 */
class Reader
{
	//objects
	//private $oMask;

	//general
	private $sCode;
	private $bImprovedColoring;

	//Special Wors / Chars
	private $asAbap;

	//constants
	const CASE_SENSITIVE = false;
	const NOT_FOUND = '* Code introuvable...';

	function __construct($sCode, $bImprovedColoring = false)
	{
		//settings
		$this->bImprovedColoring = $bImprovedColoring;

		//get code in db
		$this->sCode = $sCode;
		if($this->sCode=='')
		{
			$this->sCode = self::NOT_FOUND;
		}

		//ABAP key words and chars
		$this->asAbap = array
		(
			'word'=>array
			(
				'wCore'=>array(	/*'&lt;icon&gt;','&lt;itab&gt;',*/'ABBREVIATED','ABSTRACT','ABSTRACTFINAL','ACCEPT','ACCEPTING','ACCORDING','ACTUAL','ADD',
								'ADD-CORRESPONDING','ADDITIONS','ADJACENT','ALIASES','ALL','ALLOCATE','ANALYZER', 'AND','APPEND','APPENDING','AS',
								'ASCENDING','ASCENDINGDESCENDING','AT','ATTRIBUTE','AUTHORITY-CHECK','BACKGOUND','BEFORE','BEGIN','BETWEEN',
								'BIGLITTLE','BINARY','BIT','BLANK','BLOCK','BREAK-POINT','BY','BYPASSING','BYTE','BYTECHARACTER','CASE',
								'CASERESPECTING','CASTING','CENTERED','CHAIN','CHANGE','CHANGING','CHARACTER','CHECK','CHECKBOX',
								'CHECKBOXSYMBOLICONLINE','CLASS','CLASS-DATA','CLASS-EVENTS','CLASS-METHODS','CLEANUP','CLEAR','CLIENT','CLOCK',
								'CODE','COL_BACKGROUND','COL_HEADING','COL_NORMAL','COL_TOTAL','COLLECT','COLOR','COLUMN','COMMENT','COMMIT',
								'COMMON','COMMUNICATION','COMPARING','COMPONENTS','COMPUTE','CONCATENATE','CONDENSE','CONSTANTS','CONTEXT',
								'CONTEXTS','CONTINUE','CONTROL','CONTROLS','CONVERSION','CONVERT','COUNTRY','COUNTY','CURRENT','CURSOR',
								'CUSTOMER-FUNCTION','DATA','DATABASE','DATASET','DATE','DEALLOCATE','DECIMALS','DEFAULT','DEFAULTUTF-8NON-UNICODE',
								'DEFERRED','DEFINE','DEFINING','DEFINITION','DELETE','DELETING','DEMAND','DESCENDING','DESCRIBE','DESTINATION',
								'DIALOG','DIRECTORY','DISTANCE','DISTINCT','DIVIDE-CORRESPONDING','DO','DUPLICATE','DUPLICATES','DURING','DYNAMIC',
								'EDIT','EDITOR-CALL','ELSE','ELSEIF','ENCODING','END','ENDAT','ENDCASE','ENDCATCH','ENDCHAIN','ENDCLASS','ENDDO',
								'ENDEXEC','ENDFORM','ENDFUNCTION','ENDIAN','ENDIF','ENDING','ENDINTERFACE','ENDLOOP','ENDMETHOD','ENDMODULE',
								'ENDON','ENDPROVIDE','ENDSELECT','ENDTRY','ENDWHILE','END-OF_FILE','END-OF-DEFINITION','END-OF-PAGE',
								'END-OF-SELECTION','ENTRIES','EQ','ERRORS','EVENT','EVENTS','EXCEPTION','EXCEPTIONS','EXCEPTION-TABLE','EXCLUDE',
								'EXCLUDING','EXEC','EXIT','EXIT-COMMAND','EXPORT','EXPORTING','EXTENDED','EXTRACT','FETCH','FIELD','FIELD-GROUPS',
								'FIELDSNO','FIELD-SYMBOLS','FILTER','FINAL','FIND','FIRST','FOR','FORM','FORMAT','FORWARDBACKWARD','FOUND','FRAME',
								'FREE','FRIENDS','FROM','FUNCTION','FUNCTION-POOL','GE','GET','GIVING','GROUP','GT','HANDLER','HASHED','HAVING',
								'HEADER','HEADING','HELP-ID','HIDE','HIGHLOW','HOLD','HOTSPOT','ID','IF','IGNORING','IMMEDIATELY','IMPLEMENTATION',
								'IMPORT','IMPORTING','IN','INCREMENT','INDEX','INDEX-LINE','INHERITING','INIT','INITIAL','INITIALIZATION','INNER',
								'INNERLEFT','INSERT','INSTANCES','INTENSIFIED','INTERFACE','INTERFACES','INTERVALS','INTO','INTOAPPENDING',
								'INVERTED-DATE','IS','ITAB','JOIN','KEEPING','KEY','KEYS','KIND','LANGUAGE','LAST','LE','LEAVE','LEFT',
								'LEFT-JUSTIFIED','LEFTRIGHT','LEFTRIGHTCIRCULAR','LEGACY','LENGTH','LIKE','LINE','LINE-COUNT','LINES','LINES',
								'LINE-SELECTION','LINE-SIZE','LIST','LIST-PROCESSING','LOAD','LOAD-OF-PROGRAM','LOCAL','LOCALE','LOOP','LT',
								'MARGIN','MARK','MASK','MATCH','MAXIMUM','MEMORY','MESSAGE','MESSAGE-ID','MESSAGES','METHOD','METHODS','MOD','MODE',
								'MODEIN','MODIF','MODIFIER','MODIFY','MODULE','MOVE','MOVE-CORRESPONDING','MULTIPLY-CORRESPONDING','NE','NEW',
								'NEW-LINE','NEW-PAGE','NEXT','NODES','NODETABLE','NO-DISPLAY','NO-EXTENSION','NO-GAP','NO-GAPS',
								'NO-HEADINGWITH-HEADING','NON','NO-SCROLLING','NO-SCROLLINGSCROLLING','NOT','NO-TITLEWITH-TITLE','NO-ZERO','NUMBER',
								'OBJECT','OBLIGATORY','OCCURENCE','OCCURENCES','OCCURS','OF','OFF','OFFSET','ON','ONLY','ONOFF','OPEN','OPTION',
								'OPTIONAL','OR','ORDER','OTHERS','OUTPUT-LENGTH','OVERLAY','PACK','PACKAGE','PAGE','PAGELAST','PAGEOF','PAGEPAGE',
								'PAGES','PARAMETER','PARAMETERS','PARAMETER-TABLE','PART','PERFORM','PERFORMING','PFN','PF-STATUS','PLACES',
								'POS_HIGH','POS_LOW','POSITION','POSITIONS','PRIMARY','PRINT','PRINT-CONTROL','PRIVATE','PROCESS','PROGRAM',
								'PROPERTY','PROTECTED','PROVIDE','PUBLIC','PUBLICPROTECTEDPRIVATE','PUSHBUTTON','PUT','QUICKINFO','RADIOBUTTON',
								'RAISE','RAISING','RANGE','RANGES','READ','RECEIVE','RECEIVING','REDEFINITION','REFERENCE','REFRESH','REJECT',
								'RENAMING','REPLACE','REPLACEMENT','REPORT','RESERVE','RESET','RESOLUTION','RESULTS','RETURN','RETURNING','RIGHT',
								'RIGHT-JUSTIFIED','ROLLBACK','ROWS','RUN','SCAN','SCREEN','SCREEN-GROUP1','SCREEN-GROUP2','SCREEN-GROUP3',
								'SCREEN-GROUP4','SCREEN-GROUP5','SCREEN-INPUT','SCREEN-INTENSIFIED','SCROLL','SCROLL-BOUNDARY','SEARCH','SECTION',
								'SELECT','SELECTION','SELECTIONS','SELECTION-SCREEN','SELECTION-SET','SELECTION-TABLE','SELECT-OPTIONS','SEND',
								'SEPARATED','SET','SHARED','SHIFT','SIGN','SINGLE','SINGLEDISTINCT','SIZE','SKIP','SORT','SORTABLE','SPACE',
								'SPECIFIED','SPLIT','SQL','STABLE','STAMP','STANDARD','START','STARTING','START-OF-SELECTION','STATICS','STEP-LOOP',
								'STOP','STRLEN','STRUCTURE','SUBMIT','SUBTRACT','SUBTRACT-CORRESPONDING','SUFFIX','SUPPLY','SUPPRESS','SYMBOLS',
								'SYSTEM-EXCEPTIONS','TABLE','TABLES','TABLEVIEW','TASK','TEXT','THEN','TIME','TIMES','TITLE','TITLEBAR','TO',
								'TOPIC','TOP-OF-PAGE','TRAILING','TRANSACTION','TRANSFER','TRANSLATE','TRY','TYPE','TYPELIKE','TYPE-POOL',
								'TYPE-POOLS','TYPES','ULINE','UNION','UNIQUE','UNTIL','UP','UPDATE','UPPERLOWER','USER-COMMAND','USING','VALUE',
								'VALUES','VARY','VARYING','VERSION','VIA','WAIT','WHEN','WHERE','WHILE','WINDOW','WITH','WORK','WRITE','ZONE','ADD',
								'ADD-CORRESPONDING','APPEND','BREAK-POINT','CORRESPONDING','FIELDS','ASSIGN','AVG','BACK','CALL','CATCH','CHANGE',
								'CHECK','CLEAR','CLOSE','COMMIT','COMPUTE','COUNT','CREATE','DATASET','DELETE','DESCRIBE','DIVIDE','DO','ELSE',
								'ELSEIF','EXIT','FIND','FOR','FORMAT','GET','IF','INCLUDE','LOOP','MAX','MESSAGE','METHOD','MIN','MODIFY','MOVE',
								'MULTIPLY','NEW-PAGE','OBJECT','OPEN','ORDER','READ','SORT','SUM','TRANSLATE','ULINE','WORK','WRITE','XSTRLEN',
								'ABS','ACOS','AFTER','ALL','AND','AS','ASIN','ASSIGNING','AT','ATAN','BEGIN','BUFFER','BY','CA','CEIL','CENTERED',
								'CN','CO','COLOR','COMPARING','COMPONENT','COS','COSH','CP','CS','DEFAULT','END','EQ','EXP','FLOOR','FRAC','GE',
								'GROUP','GT','ICON','IN','INDEX','INNER','INTENSIFIED','INTO','IS','JOIN','LE','LEADING','LEAVE','LEFT','LIKE',
								'LOG','LOG10','LOWER','LT','M','MOD','NA','NE','NOT','NP','NS','O','OF','OFF','ON','OR','OTHERS','OUTER','REF',
								'RIGHT','SIGN','SIN','SINH','SQRT','TABLE','TABLENAME','TAN','TANH','TITLE','TO','TRAILING','TRUNC','TYPE','UPPER',
								'USING','VALUE','WITH', 'TRANSPORTING', 'TYPE-POOLS'),
				'wBwCore'=>array('SOURCE_PACKAGE', 'RESULT_PACKAGE', '<SOURCE_FIELDS>', '<RESULT_FIELDS>'),
				'cOperator'=>array('(', ')', ',', '.', ':', '-', '~', '[', ']', '&lt;', '&gt;'),
				'cCalculation'=>array('+', '-', '*', '/', '=', '(', ')'),
				'cComment'=>array('*'),
				'cPartComment'=>array('&quot;'),
				'cString'=>array('&quot;', '&#039;'),
				'iNumber'=>array('1', '2', '3', '4', '5', '6', '7', '8', '9', '0'),
				'wExpand'=>array('loop'=>'endloop', 'if'=>'endif'),
				'wGlobal'=>array('SY'),
				'wCodePart'=>array('main', 'global', 'global2')
			),
			'color'=>array
			(
				'wCore'=>'core',
				'wBwCore'=>'core',
				'cOperator'=>'operator',
				'cCalculation'=>'operator',
				'cComment'=>'comment',
				'cPartComment'=>'comment',
				'cString'=>'string',
				'iNumber'=>'number',
				'wExpand'=>'expand',
				'wGlobal'=>'glogal',
				'wCodePart'=>'code_part'
			)
		);
	}

	private function getWords($sKey)
	{
		return $this->asAbap['word'][$sKey];
	}

	private function getColor($sKey)
	{
		return $this->asAbap['color'][$sKey];
	}

	private function addColoring()
	{
		//Safe characters
		$sCode = $this->convText2Html($this->sCode);
		$sServName = $_GET['serv_name'];
		$sSafeRexExServName = preg_quote(str_replace('http://', '', $sServName), '/');
		
		$asLines = explode("\n", $sCode);
		$asLineClasses = array();
		$iOffSet = 0;
		
		//building lines
		foreach($asLines as $iLineNb=>$sLine)
		{
			/*	Code ref:
			 *	<li id="#line_nb#" class="#li_class#">
			 *		<span class="expand_line">
			 *			<span id="#expand_id#" class="#expand_class#">-</span>
			 *		</span>
			 *		<span class="#code_class#">#line#</span>
			 *	</li>
			 */
			$sLiClass = $sExpandSign = $sCodeClass = $sExpandHtml = $sExpandButton = '';
			$sFirstWord = $this->getFirstWord($sLine);
			$sFirstChar = $this->getFirstChar($sLine);
				
			//Apply current classes
			$sLiClass =  implode(' ', $asLineClasses);

			//Expanding starting/ending point
			if(in_array($sFirstWord, array_keys($this->getWords('wExpand')))) //start
			{
				$iLoopId = count($asLineClasses)+$iOffSet;
				$asLineClasses[] = 'loop'.$iLoopId;
				$sExpandButton = '<input type="button" id="expand_loop'.$iLoopId.'" class="minus" value="" />';
			}
			elseif(in_array($sFirstWord, $this->getWords('wExpand'))) //end
			{
				array_pop($asLineClasses);
				$iOffSet++;
				$sExpandButton = '<span></span>';
			}
			
			//Enlarge line
			if(strpos($sLine, $sServName) !== false)
			{
				$sLiClass .= ' bigline';
			}

			//comments (entire line)
			$sCodeClass = (in_array($sFirstChar, $this->getWords('cComment')))?'comment':'code';
				
			$asColoredLines[] = '<li id="'.($iLineNb+1).'" class="round_right '.$sLiClass.'"><span class="expand_line">'.$sExpandButton.'</span><span class="'.$sCodeClass.'">'.$sLine.'</span></li>';
		}
		$sCode = implode('', $asColoredLines);

		//Strings
		foreach($this->getWords('cString') as $sStringWord)
		{
			$sPattern = '/>([^<]*?)'.$sStringWord.'([^<]*?)'.$sStringWord.'/';
			$sCode = preg_replace($sPattern, '>$1<span class="string">'.$sStringWord.'$2'.$sStringWord.'</span>', $sCode);
		}

		//Part comment
		$sPattern = '/>([^<]*)\&quot\;([^<]*)\<\/span\>\<\/li\>/';
		$sCode = preg_replace($sPattern, '>$1<span class="comment">&quot;$2</span>', $sCode);

		//Internal Url
		//$sPattern = '/>([^<]*)'.preg_quote($_GET['serv_name'], '/').'\/r\-([^<]*)/';
		$sPattern = '/>([^<\/]*)(http:\/\/'.$sSafeRexExServName.'|'.$sSafeRexExServName.')(c|code|p|proc|procedure)\-([^<]*)/';
		$sCode = preg_replace($sPattern, '>$1<a class="share internal_link" href="'.$sServName.'$3-$4" target="_blank">$3 $4</a>', $sCode);
		
		//Global / Main
		$sPattern = '/>\*\ \[('.implode('|', $this->getWords('wCodePart')).')\]/';
		$sCode = preg_replace($sPattern, '><span class="code_part">&#62; $1</span>', $sCode);
		
		//Core Words
		foreach($this->getWords('wCore') as $sCoreWord)
		{
			$sCoreWord = strtolower($sCoreWord);
			$sPattern = '/>(([^<]*)([^\w&<]{1})|.{0})('.$sCoreWord.')([\W])/';
			$sCode = preg_replace($sPattern, '>$1<span class="core">$4</span>$5', $sCode);
		}
		//$sCoreWords = str_replace(' ',  '\ ', implode('|', array_map('strtolower', $this->getWords('wCore'))));
		//$sPattern = '/>(([^<]*)([^\w<]{1})|.{0})('.$sCoreWords.')(?=[\W])/U';
		//$sCode = preg_replace($sPattern, '>$1<span class="core">$4</span>', $sCode);

		//Operators
		//TODO distinguer operator / calculation
		$asOperators = array_unique(array_merge($this->getWords('cOperator'), $this->getWords('cCalculation')));
		foreach($asOperators as $sOpWord)
		{
			$sPattern = '/>([^<]*)\\'.$sOpWord.'/';
			$sCode = preg_replace($sPattern, '>$1<span class="operator">'.$sOpWord.'</span>', $sCode);
		}
		//$sPattern = '/>([^<]*)['.implode(array_map('strtolower', ($this->getWords('cOperator')))).']/';
		//echo $sPattern;
		//$sCode = preg_replace($sPattern, '>$1<span class="operator">$2$3</span>', $sCode);

		//Numbers
		$sPreChar = '\\'.implode('\\', $this->getWords('cCalculation')).'\s';
		$sPattern = '/>((([^<]*)['.$sPreChar.']{1})|.{0})([0-9]+)([\W])/';
		$sCode = preg_replace($sPattern, '>$1<span class="number">$4</span>$5', $sCode);

		return $sCode;
	}

	private static function getFirstWord($sText)
	{
		return strstr(str_replace('.', ' ', trim($sText)), ' ', true);
	}

	private static function getFirstChar($sText)
	{
		return substr(str_replace('.', ' ', trim($sText)), 0, 1);
	}

	private function addColor(&$sText, $sWord, $sClassColor)
	{
		$sText = str_replace($sWord, '<span class="'.$sClassColor.'">'.$sWord.'</span>', $sText);
	}

	private static function getColoredWord($sWord, $sClass)
	{
		return '<span class="'.$sClass.'">'.$sWord.'</span>';
	}

	public static function convText2Html($sCode)
	{
		return htmlspecialchars(strtolower($sCode), ENT_QUOTES);
	}
	
	public function getColoredCode()
	{
		return $this->addColoring();
	}
}

/**
 * Mask Reader
 * @author franzz
 *
 */
class Mask extends PhpObject
{
	public $sMaskName;
	public $sFilePath;
	private $sMask;
	private $asTags;
	private $asPartsSource;
	private $aoInstances;

	const MASK_FOLDER = 'masks/';
	const START_TAG = 'START';
	const END_TAG = 'END';
	const TAG_MARK = '#';

	public function __construct($sFileName='')
	{
		//init
		parent::__construct();
		$this->sMaskName = '';
		$this->sFilePath = '';
		$this->sMask = '';
		$this->asTags = array();
		$this->asPartsSource = array();
		$this->aoInstances = array();
		$this->sFilePath = '';

		//load file
		if($sFileName!='')
		{
			$this->initFile($sFileName);
		}
	}

	public function initFile($sFileName)
	{
		$sFilePath = self::MASK_FOLDER.basename($sFileName).'.html';
		if(file_exists($sFilePath))
		{
			$this->sFilePath = $sFilePath;
			$sSource = file_get_contents($this->sFilePath);
			$this->initMask($sFileName, $sSource);
		}
		else
		{
			$this->addError('Fichier introuvable &agrave; l\'adresse : '.$sFilePath);
		}
	}

	public function initFileFromString($sSource, $sPartName='', $iInstanceNb=0)
	{
		$this->initMask($sPartName.' (from row) '.$iInstanceNb, $sSource);
	}

	private function initMask($sMaskName, $sSource)
	{
		$this->sMaskName = $sMaskName;
		$this->sMask = $sSource;
		$this->setParts();
	}

	private function setParts()
	{
		while(preg_match('/\<\!-- \[PART\] (?P<part>\S+) \[START\] --\>/', $this->sMask, $asMatch))
		{
			$sPartName = $asMatch['part'];

			$this->asPartsSource[$sPartName] = $this->getCleanPart($sPartName);
			$this->aoInstances[$sPartName] = array();
		}
	}

	private function getCleanPart($sPartName)
	{
		$iStartPos = $this->getPartStartPos($sPartName);
		$iEndPos = $this->getPartEndPos($sPartName);
		$sPart = substr($this->sMask, $iStartPos, $iEndPos-$iStartPos);
		$sExtendedPart = $this->getPartPattern($sPartName, self::START_TAG).$sPart. $this->getPartPattern($sPartName, self::END_TAG);
		$this->sMask = str_replace($sExtendedPart, $this->getPartTagPattern($sPartName), $this->sMask);
		return $sPart;
	}

	private function getPartStartPos($sPartName)
	{
		$sPartStartPattern = $this->getPartPattern($sPartName, self::START_TAG);
		return strpos($this->sMask, $sPartStartPattern) + strlen($sPartStartPattern);
	}

	private function getPartEndPos($sPartName)
	{
		$sPartEndPattern = $this->getPartPattern($sPartName, self::END_TAG);
		return strpos($this->sMask, $sPartEndPattern);
	}

	private function getPartPattern($sPartName, $sAction)
	{
		return '<!-- [PART] '.$sPartName.' ['.$sAction.'] -->';
	}

	private function getPartTagPattern($sPartName, $bMark=true)
	{
		$sPartTag = 'PART '.$sPartName;
		return $bMark?$this->addTagMark($sPartTag):$sPartTag;
	}

	public function addInstance($sPartName, $asTags)
	{
		$this->newInstance($sPartName);
		foreach($asTags as $sTagName=>$sTagValue)
		{
			$this->setInstanceTag($sPartName, $sTagName, $sTagValue);
		}
	}

	public function newInstance($sPartName)
	{
		//Finding the part
		$oMask = &$this->findPart($this, $sPartName);

		//Retrieving source html
		$sPartSource = $oMask->asPartsSource[$sPartName];

		//Creating new instance
		$oInstance = new Mask();
		$oInstance->initFileFromString($sPartSource, $sPartName);
		$oMask->aoInstances[$sPartName][] = $oInstance;
	}

	public function setInstanceTag($sPartName, $sTagName, $sTagValue)
	{
		$oMask = &$this->findPart($this, $sPartName);
		$oMask->getCurrentInstance($sPartName)->setTag($sTagName, $sTagValue);
	}

	private function &findPart($oMask, $sPartName)
	{
		if(array_key_exists($sPartName, $oMask->aoInstances))
		{
			return $oMask;
		}
		else //not tested
		{
			foreach($oMask->aoInstances as $sLevelPartName=>$aoInstances)
			{
				if(!empty($aoInstances))
				{
					//take last instances
					return $this->findPart($oMask->getCurrentInstance($sLevelPartName), $sPartName);
				}
			}
		}
		$this->addError('No part found : '.$sPartName);
	}

	private function getCurrentInstance($sPartName)
	{
		if(!empty($this->aoInstances[$sPartName]))
		{
			return end($this->aoInstances[$sPartName]);
		}
		else
		{
			return false;
		}
	}

	public function setTag($sTagName, $sTagValue)
	{
		$this->asTags[$sTagName] = $sTagValue;
	}

	public function getMask()
	{
		$sCompletedMask = $this->sMask;

		//build parts
		foreach($this->aoInstances as $sPart=>$aoParts)
		{
			$sTagValue = '';
			foreach($aoParts as $oInstance)
			{
				$sTagValue .= $oInstance->getMask();
			}
			$this->setTag($this->getPartTagPattern($sPart, false), $sTagValue);
		}

		//replace tags
		if(!empty($this->asTags))
		{
			$asTags = $this->addTagMark(array_keys($this->asTags));
			$sCompletedMask = str_replace($asTags, $this->asTags, $sCompletedMask);
		}
		return $sCompletedMask;
	}

	private function addTagMark($oData)
	{
		return ToolBox::array_map_encapsulate($oData, self::TAG_MARK);
	}
}

class fileUploader
{
	const GET_NAME = 'qqfile';
	
	private $asAllowedExtensions;
	private $iSizeLimit;
	private $sFolderPath;

	function __construct($sFolderPath, $asAllowedExtensions = array()/*, $sizeLimit = 10485760*/)
	{
		$this->sFolderPath = $sFolderPath;
		$this->asAllowedExtensions = array_map("strtolower", $asAllowedExtensions);	
		//$this->iSizeLimit = $sizeLimit;
		//$this->checkServerSettings();	   
		$this->iSizeLimit = Databap::getMaxSize();
		
		/*
		if(isset($_GET[self::GET_NAME]))
		{
			//$this->file = new qqUploadedFileXhr();
		}
		elseif(isset($_FILES[self::GET_NAME])) {
			$this->file = new qqUploadedFileForm();
		}
		else
		{
			$this->file = false; 
		}
		*/
	}
	
	/*
	private function checkServerSettings()
	{		
		$postSize = $this->toBytes(ini_get('post_max_size'));
		$uploadSize = $this->toBytes(ini_get('upload_max_filesize'));		
		
		
		if($postSize < $this->iSizeLimit || $uploadSize < $this->iSizeLimit){
			$size = max(1, $this->iSizeLimit / 1024 / 1024) . 'M';			 
			die("{'error':'increase post_max_size and upload_max_filesize to $size'}");	
		}
	}
	
	private function toBytes($str)
	{
		$val = trim($str);
		$last = strtolower($str[strlen($str)-1]);
		switch($last) {
			case 'g': $val *= 1024;
			case 'm': $val *= 1024;
			case 'k': $val *= 1024;		
		}
		return $val;
	}
	*/
	
	function save($sFileName)
	{
		$oInput = fopen("php://input", "r");
		$sTemp = tmpfile();
		$iRealSize = stream_copy_to_stream($oInput, $sTemp);
		fclose($oInput);
		
		if($iRealSize != $this->getSize())
		{
			return false;
		}
		
		$sPath = $this->sFolderPath.$sFileName;
		$oTarget = fopen($sPath, "w");		
		fseek($sTemp, 0, SEEK_SET);
		stream_copy_to_stream($sTemp, $oTarget);
		fclose($oTarget);
		
		return true;
	}
	
	function getName()
	{
		return isset($_GET[self::GET_NAME])?$_GET[self::GET_NAME]:false;
	}
	
	function getSize()
	{
		if(isset($_SERVER["CONTENT_LENGTH"]))
		{
			return (int)$_SERVER["CONTENT_LENGTH"];			
		}
		else
		{
			throw new Exception('Getting content length is not supported.');
		}	  
	}
	
	//Returns array('success'=>true) or array('error'=>'error message')
	function handleUpload(/*$sUploadDir, $replaceOldFile = false*/)
	{
		if(!is_writable($this->sFolderPath))
		{
			return array('error' => "Erreur serveur. Le dossier est prot&eacute;g&eacute; en &eacute;criture");
		}
		if(!$this->getName())
		{
			return array('error' => 'Aucun fichier t&eacute;l&eacute;charg&eacute;');
		}
		
		$iSize = $this->getSize();
		if($iSize == 0)
		{
			return array('error' => 'Fichier vide');
		}
		if($iSize > $this->iSizeLimit)
		{
			return array('error' => 'Fichier trop volumineux');
		}
		
		$asPathInfo = pathinfo($this->getName());
		$sExt = strtolower($asPathInfo['extension']);
		$sExt = ($sExt=='jpg')?'jpeg':$sExt;
		if($this->asAllowedExtensions && !in_array($sExt, $this->asAllowedExtensions))
		{
			$sAuthorizedExts = implode(', ', $this->asAllowedExtensions);
			return array('error' => 'Le fichier a une extension invalide, les extensions autoris&eacute;es sont : '.$sAuthorizedExts);
		}
		
		//
		
		
		//$sFileName = $asPathInfo['filename'];
		$sFileName = md5(uniqid());
		/*
		if(!$replaceOldFile){
			// don't overwrite previous files that were uploaded
			//while (file_exists($sUploadDir . $sFileName . '.' . $sExt)) {
			//	$sFileName .= rand(10, 99);
			//}
			if(file_exists($sUploadDir . $sFileName . '.' . $sExt))
			{
				$sFileName .= date(Databap::DATE_COMPACT_FORMAT);
			}
		}
		*/
		
		if($this->save($sFileName.'.'.$sExt))
		{
			return array('success'=>true, 'file_name'=>$sFileName.'.'.$sExt);
		}
		else
		{
			return array('error'=> 'Erreur server. Impossible de sauvegarder le fichier.');
		}
	}
}

/**
 * MySql query manager and generator
 * @author franzz
 */
class MySqlManager extends PhpObject
{
	const DB_SERVER = Settings::DB_SERVER;
	const DB_LOGIN = Settings::DB_LOGIN;
	const DB_PASS = Settings::DB_PASS;
	const DB_NAME = Settings::DB_NAME;
	const DB_NO_CONN = 'ERR_1';
	const DB_NO_DATA = 'ERR_2';
	const ID_TAG = 'id_';

	const USER_TABLE = 'users';
	const COMP_TABLE = 'companies';
	const CODE_TABLE = 'codes';
	const URL_TABLE = 'urls';
	const MSG_TABLE = 'messages';
	const CHAN_TABLE = 'channels';
	const CONN_TABLE = 'connections';
	const ART_TABLE = 'articles';
	
	const OPT_TABLE = 'options';
	const OPTVAL_TABLE = 'option_values';
	const OPTNAME_TABLE = 'option_names';
	
	const PROC_TABLE = 'procedures';
	const STEP_TABLE = 'steps';
	const IMG_TABLE = 'images';
	
	const DOC_TABLE = 'docs';
	const FILE_TABLE = 'files';
	
	const SEARCH_TABLE = 'searchs';

	public $sDbState;
	private $oConnection;
	private $bTrace;

	public function __construct()
	{
		parent::__construct();
		$this->oConnection = mysql_connect(self::DB_SERVER, self::DB_LOGIN, self::DB_PASS);
		$this->setTrace(false);
		if(!$this->oConnection)
		{
			$this->addError('bug connection');
			$this->sDbState = self::DB_NO_CONN;
		}
		else
		{
			if(!mysql_select_db(self::DB_NAME, $this->oConnection))
			{
				$this->addError('bug selecting database. Installing...');
				$this->sDbState = self::DB_NO_DATA;
			}
		}
	}

	public function __destruct()
	{
		parent::__destruct();
		mysql_close($this->oConnection);
	}
	
	public function setTrace($bAction)
	{
		$this->bTrace = $bAction;
	}
	
	public static function getTables()
	{
		$oReflect = new ReflectionClass(__CLASS__);	
		$asConstants = $oReflect->getConstants();
		$sTableTag = '_TABLE';
		$asTables = array();
		foreach($asConstants as $sConstant=>$sConstantValue)
		{
			if(strpos($sConstant, $sTableTag)!==false && strpos($sConstant, $sTableTag)==(strlen($sConstant) - strlen($sTableTag)))
			{
				$asTables[] = $sConstantValue;
			}
		}
		return $asTables;
	}

	public function install()
	{
		//Create Database
		$this->setQuery("DROP DATABASE IF EXISTS ".self::DB_NAME);
		$this->setQuery("CREATE DATABASE ".self::DB_NAME);
		mysql_select_db(self::DB_NAME, $this->oConnection);

		//Create tables
		@array_walk($this->getInstallQueries(), array($this, 'setQuery'));
	}
	
	//For debug purposes
	public function getFullInstallQuery()
	{
		$asInstallQueries = $this->getInstallQueries();
		return str_replace("\n", "<br />", implode(";\n\n", $asInstallQueries))."\n\n<!-- \n".implode(";\n\n", $asInstallQueries)."\n -->";
	}

	private function getInstallQueries()
	{
		$asTables = $this->getTables();
		$asInstallQueries = array_map(array($this, 'getInstallQuery'), $asTables);
		$asAlterQueries = $this->getForeignKeyQueries($asTables);
		return array_merge($asInstallQueries, $asAlterQueries);
	}
	
	private function getInstallQuery($sTableName)
	{
		$asTableColumns = $this->getTableColumns($sTableName);
		$sQuery = "\n".$this->implodeAll($asTableColumns, "` ", "\n", "`", ",")."\n".implode(", \n", $this->getTableConstraints($sTableName));
		return "CREATE TABLE `{$sTableName}` ({$sQuery})";
	}

	private function getForeignKeyQueries($asTableNames)
	{
		$asForeignKeyQueries = array();
		foreach($asTableNames as $sTableName)
		{
			$asTableColumns = array_keys(self::getTablecolumns($sTableName));
			foreach($asTableColumns as $sColumnName)
			{
				if(self::isId($sColumnName) && $sColumnName!=self::getId($sTableName))
				{
					$asForeignKeyQueries[] = "ALTER TABLE ".$sTableName." ADD INDEX(`".$sColumnName."`)";
					$asForeignKeyQueries[] = "ALTER TABLE ".$sTableName." ADD FOREIGN KEY (`".$sColumnName."`) REFERENCES ".self::getTable($sColumnName)."(`".$sColumnName."`)";
				}
			}
		}
		return $asForeignKeyQueries;
	}
	
	private function setQuery($sQuery, $sTypeQuery=__FUNCTION__)
	{
		$this->getQuery($sQuery, $sTypeQuery);
		return (mysql_affected_rows()!=0);
	}

	private function getQuery($sQuery, $sTypeQuery=__FUNCTION__)
	{
		$sQuery = str_replace(array("\n", "\t"), array(" ", ""), $sQuery);
		$oResult = mysql_query($sQuery, $this->oConnection);
		
		if($this->bTrace)
		{
			$this->setDebug(true);
			$this->addNotice($sQuery);
		}

		if(!$oResult)
		{
			$this->addError("\nErreur SQL : \n".str_replace("\t", "", $sQuery)."\n\n".str_replace(array("\t", "\n"), "", mysql_error()));
		}
		return $oResult;
	}

	public function getArrayQuery($sQuery, $bStringOnly=false, $sGroupBy='', $sTypeQuery=__FUNCTION__)
	{
		$iIndex = 0;
		$iColumnCount = 0;
		$asResult = array();
		$oResult = $this->getQuery($sQuery, true, $sTypeQuery);
		if($oResult!==false)
		{
			while($asCurrentRow = mysql_fetch_array($oResult))
			{
				if($bStringOnly)
				{
					$asCurrentRow = $this->arrayKeyFilter($asCurrentRow, 'is_string');
				}
				
				//Add table reel keys
				if($sGroupBy!='' && array_key_exists($sGroupBy, $asCurrentRow))
				{
					$iRowKey = $asCurrentRow[$sGroupBy];
					unset($asCurrentRow[$sGroupBy]);
				}
				else
				{
					$iRowKey = $iIndex;
				}
				
				//For first loop, check table width
				if($iIndex==0)
				{
					$iColumnCount = count($asCurrentRow);
				}
				
				//One column case : collapse a level
				if($iColumnCount==1)
				{
					$asCurrentRow = array_shift($asCurrentRow);
				}

				$asResult[$iRowKey] = $asCurrentRow;
				$iIndex++;
			}
		}
		return $asResult;
	}

	private function getMaxIncrementedValue($sTable)
	{
		return $this->selectValue($sTable, "MAX(".$this->getId($sTable).")");
	}

	public static function getId($sTableName, $bFull=false)
	{
		$sColumnName = self::ID_TAG.self::getText($sTableName);
		return $bFull?self::getFullColumnName($sTableName, $sColumnName):$sColumnName;
	}

	public static function getText($sTableName, $bFull=false)
	{
		$sColumnName = substr(str_replace('`', '', $sTableName), 0, -1);
		$sColumnName = substr($sColumnName, -2)=='ie'?substr($sColumnName, 0, -2).'y':$sColumnName;
		return $bFull?self::getFullColumnName($sTableName, $sColumnName):$sColumnName;
	}
	
	public static function getFullColumnName($sTableName, $sColumnName)
	{
		return $sTableName.".".$sColumnName;
	}

	private static function isId($sColumnName, $sTableName='')
	{
		$asTables = ($sTableName=='')?self::getTables():array($sTableName);
		$asTableIds = array_map(array('self', 'getId'), $asTables);
		return in_array($sColumnName, $asTableIds);
	}
	
	private static function getTable($sTableId)
	{
		$asTables = self::getTables();
		$asTableIds = array_map(array('self', 'getId'), $asTables);
		if(in_array($sTableId, $asTableIds))	return $asTables[array_search($sTableId, $asTableIds)];
		else 
		{
			$this->addError('Id '.$sTableId.' pr&eacute;sent dans aucune table');
			return false;
		}
	}

	public static function getTablecolumns($sTableName)
	{
		$asTableColumns = array(self::getId($sTableName));
		switch($sTableName)
		{
			case self::USER_TABLE:
				$asTableColumns[] = 'first_name';
				$asTableColumns[] = 'last_name';
				$asTableColumns[] = 'email';
				$asTableColumns[] = self::getId(self::COMP_TABLE);
				$asTableColumns[] = 'pass';
				$asTableColumns[] = 'clearance';
				break;
			case self::COMP_TABLE:
				$asTableColumns[] = self::getText(self::COMP_TABLE);
				$asTableColumns[] = 'logo';
				break;
			case self::CODE_TABLE:
				$asTableColumns[] = self::getText(self::CODE_TABLE);
				$asTableColumns[] = 'description';
				$asTableColumns[] = self::getId(self::USER_TABLE);
				$asTableColumns[] = 'refer_id';
				break;
			case self::URL_TABLE:
				$asTableColumns[] = self::getId(self::CODE_TABLE);;
				$asTableColumns[] = 'phrase';
				break;
			case self::MSG_TABLE:
				$asTableColumns[] = self::getId(self::USER_TABLE);
				$asTableColumns[] = 'nickname';
				$asTableColumns[] = self::getId(self::CHAN_TABLE);
				$asTableColumns[] = self::getText(self::MSG_TABLE);
				$asTableColumns[] = 'type';
				$asTableColumns[] = 'date';
				break;
			case self::CHAN_TABLE:
				$asTableColumns[] = 'safe_name';
				$asTableColumns[] = self::getText(self::CHAN_TABLE);
				break;
			case self::CONN_TABLE:
				$asTableColumns[] = self::getId(self::USER_TABLE);
				$asTableColumns[] = self::getId(self::CHAN_TABLE);
				break;
			case self::OPT_TABLE:
				$asTableColumns[] = self::getId(self::USER_TABLE);
				$asTableColumns[] = self::getId(self::OPTNAME_TABLE);
				$asTableColumns[] = self::getId(self::OPTVAL_TABLE);
				$asTableColumns[] = self::getText(self::OPT_TABLE);
				break;
			case self::OPTNAME_TABLE:
				$asTableColumns[] = self::getText(self::OPTNAME_TABLE);
				$asTableColumns[] = 'type';
				$asTableColumns[] = 'language';
				break;
			case self::OPTVAL_TABLE:
				$asTableColumns[] = self::getId(self::OPTNAME_TABLE);
				$asTableColumns[] = self::getText(self::OPTVAL_TABLE);
				$asTableColumns[] = 'language';
				break;
			case self::PROC_TABLE:
				$asTableColumns[] = self::getId(self::USER_TABLE);
				$asTableColumns[] = 'title';
				$asTableColumns[] = 'description';
				$asTableColumns[] = 'refer_id';
				break;
			case self::STEP_TABLE:
				$asTableColumns[] = self::getId(self::PROC_TABLE);
				$asTableColumns[] = 'description';
				break;
			case self::IMG_TABLE:
				$asTableColumns[] = self::getId(self::PROC_TABLE);
				$asTableColumns[] = self::getId(self::STEP_TABLE);
				$asTableColumns[] = 'description';
				$asTableColumns[] = 'file_name';
				break;
			case self::DOC_TABLE:
				$asTableColumns[] = self::getId(self::USER_TABLE);
				$asTableColumns[] = 'title';
				$asTableColumns[] = 'description';
				$asTableColumns[] = 'refer_id';
				break;
			case self::FILE_TABLE:
				$asTableColumns[] = self::getId(self::DOC_TABLE);
				$asTableColumns[] = 'description';
				$asTableColumns[] = 'file_name';
				break;
			case self::SEARCH_TABLE:
				$asTableColumns[] = 'id_item';
				$asTableColumns[] = 'refer_id';
				$asTableColumns[] = 'type';
				$asTableColumns[] = 'keywords';			
				break;
			case self::ART_TABLE:
				$asTableColumns[] = 'title';
				$asTableColumns[] = 'link';
				$asTableColumns[] = 'date';
				$asTableColumns[] = 'first_name';
				$asTableColumns[] = 'last_name';
				$asTableColumns[] = 'email';
				break;
			default:
				return false;
		}
		$asTableColumns[] = 'led';
		$asTableName = array_fill(0, count($asTableColumns), $sTableName);
		return array_combine($asTableColumns, array_map(array('self', 'getColumnType'), $asTableColumns, $asTableName));
	}

	private static function getColumnType($sColumnName, $sTableName)
	{
		$sColumnType = '';
		switch($sColumnName)
		{
			case self::getText(self::USER_TABLE): //TODO delete use and field
				$sColumnType =  "varchar(50) NOT NULL";
				break;
			case 'first_name':
				$sColumnType = "varchar(20) NOT NULL";
				break;
			case 'last_name':
				$sColumnType = "varchar(20) NOT NULL";
				break;
			case 'nickname':
				$sColumnType = "varchar(50) NOT NULL";
				break;
			case 'email':
				$sColumnType = "varchar(100) NOT NULL";
				break;
			case 'pass':
				$sColumnType = "varchar(128) NOT NULL";
				break;
			case 'clearance':
				$sColumnType = "int(1) NOT NULL";
				break;
			case self::getText(self::CODE_TABLE):
				$sColumnType = "longtext NOT NULL";
				break;
			case 'title':
				$sColumnType = "varchar(200) NOT NULL";
				break;
			case 'description':
				$sColumnType = "varchar(500) NOT NULL";
				break;
			case 'link':
				$sColumnType = "varchar(200) NOT NULL";
				break;
			case 'refer_id':
				$sColumnType = "int(10) UNSIGNED NOT NULL";
				break;
			case 'phrase':
				$sColumnType = "varchar(50) NOT NULL";
				break;
			case self::getText(self::MSG_TABLE):
				$sColumnType = "varchar(500) NOT NULL";
				break;
			case 'type':
				$sColumnType = "varchar(2) NOT NULL";
				break;
			case 'safe_name':
				$sColumnType = "varchar(50) NOT NULL";
				break;
			case self::getText(self::CHAN_TABLE):
				$sColumnType = "varchar(50) NOT NULL";
				break;
			case self::getText(self::OPT_TABLE):
				$sColumnType = "varchar(100) NOT NULL";
				break;
			case self::getText(self::OPTNAME_TABLE):
				$sColumnType = "varchar(100) NOT NULL";
				break;
			case self::getText(self::OPTVAL_TABLE):
				$sColumnType = "varchar(50) NOT NULL";
				break;
			case 'language':
				$sColumnType = "varchar(2) NOT NULL";
				break;
			case 'file_name':
				$sColumnType = "varchar(40) NOT NULL";
				break;
			case 'preview_name':
				$sColumnType = "varchar(40) NOT NULL";
				break;
			case 'id_item':
				$sColumnType = "int(10) UNSIGNED NOT NULL";
				break;
			case 'keywords':
				$sColumnType = "longtext NOT NULL";
				break;
			case self::getText(self::COMP_TABLE):
				$sColumnType = "varchar(30) NOT NULL";
				break;
			case 'logo':
				$sColumnType = "varchar(20) NOT NULL";
				break;
			case 'date':
				$sColumnType = "date NOT NULL";
				break;
			case 'led':
				$sColumnType = "TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP DEFAULT CURRENT_TIMESTAMP";
				break;
			case self::isId($sColumnName, $sTableName):
				$sColumnType = "int(10) UNSIGNED auto_increment";
				break;
			case self::isId($sColumnName):
				$sColumnType = "int(10) UNSIGNED";
				break;
		}
		return $sColumnType;
	}

	private static function getTableConstraints($sTableName)
	{
		//Primary key
		$asTableConstraints = array('PRIMARY' => "PRIMARY KEY (`".self::getId($sTableName)."`)");

		//Foreign keys
		//Foreign keys applied using ALTER TABLE syntax at the end to prevent scheduling CREATE TABLE queries
				
		//Other constraints
		switch($sTableName)
		{
			case self::USER_TABLE :
				$asTableConstraints[] = "UNIQUE KEY `user_first_and_last_name` (`first_name`, `last_name`)";
				break;
			case self::URL_TABLE :
				$asTableConstraints[] = "UNIQUE KEY `uni_phrase` (`phrase`)";
				break;
			case self::MSG_TABLE :
				$asTableConstraints[] = "INDEX(`date`)";
				break;
			case self::ART_TABLE:
				$asTableConstraints[] = "INDEX(`title`)";
				break;
			
		}
		return $asTableConstraints;
	}

	private function addQuotes($oData)
	{
		//TODO remake
		$asTrustedFunc = array('CURDATE()', 'NOW()');
		$sChar = "'";
		if(is_array($oData))
		{
			$asChar = array_fill(1, count($oData), $sChar);
			return array_combine(array_keys($oData), array_map(array($this, 'addQuotes'), $oData, $asChar));
		}
		else
		{
			if(in_array($oData, $asTrustedFunc)) return $oData;
			else return $sChar.$oData.$sChar;
		}
	}

	private function getLastId()
	{
		return mysql_insert_id();
	}

	public function insertRow($sTableName, $asData)
	{
		$this->cleanSql($sTableName);
		$this->cleanSql($asData);

		$asQueryValues = $this->addQuotes($asData);
		$sQuery = "INSERT INTO ".$sTableName." (`".implode("`, `", array_keys($asQueryValues))."`) VALUES (".implode(", ", $asQueryValues).")";
		return $this->setQuery($sQuery)?$this->getLastId():0;
	}

	public function updateRow($sTableName, $asConstraints, $asData)
	{
		return $this->updateRows($sTableName, $asConstraints, $asData, 1);
	}
	
	public function updateRows($sTableName, $asConstraints, $asData, $iLimit=0)
	{
		if(!is_array($asConstraints))
		{
			$asConstraints = array($this->getId($sTableName)=>$asConstraints);
		}

		$this->cleanSql($sTableName);
		$this->cleanSql($iTableId);
		$this->cleanSql($asData);
		$this->cleanSql($asConstraints);
		$asQueryValues = $this->addQuotes($asData);
		$asConstraintsValues = $this->addQuotes($asConstraints);
		$this->addColumnSelectors($asQueryValues);
		$this->addColumnSelectors($asConstraintsValues);

		$sLimit = $iLimit>0?" LIMIT $iLimit":"";
		$sQuery = 	"UPDATE {$sTableName} ".
					"SET ".$this->implodeAll($asQueryValues, " = ", ", ")." ".
					"WHERE ".$this->implodeAll($asConstraintsValues, " = ", " AND ").$sLimit;
		
		$iResult = false;
		if($this->setQuery($sQuery))
		{
			$iResult = ($iLimit==1)?$this->selectValue($sTableName, $this->getId($sTableName), $asConstraints):true;
		}
		return $iResult;
	}

	public function insertUpdateRow($sTableName, $asData, $asKeys=array(), $bUpdate=true)
	{
		$sTableIdName = self::getId($sTableName);

		//check for data in the db
		if($asKeys==array())
		{
			$asKeys[] = $sTableIdName;
		}
		$asValues = array_intersect_key($asData, array_flip($asKeys));
		$iTableId = $this->selectValue($sTableName, $sTableIdName, $asValues);

		//insert
		if(!$iTableId)
		{
			$iTableId = $this->insertRow($sTableName, $asData);
		}
		//Update
		elseif($bUpdate)
		{
			if(array_key_exists($sTableIdName, $asData))
			{
				unset($asData[$sTableIdName]);
			}
			$iTableId = $this->updateRow($sTableName, $iTableId, $asData);
		}
		return $iTableId;
	}
	
	public function selectInsert($sTableName, $asData, $asKeys=array())
	{
		return $this->insertUpdateRow($sTableName, $asData, $asKeys, false);
	}

	public function deleteRow($sTableName, $iTableId)
	{
		$this->cleanSql($sTableName);
		$this->cleanSql($iTableId);

		//linked tables
		switch($sTableName)
		{
			case self::CODE_TABLE :
				$asTables = array($sTableName, self::URL_TABLE);
				break;
			case self::PROC_TABLE :
				$asTables = array($sTableName, self::STEP_TABLE, self::IMG_TABLE);
				break;
			case is_string($sTableName) :
				$asTables = array($sTableName);
				break;
			case is_array($sTableName):
				$asTables = $sTableName;
				break;
			default:
				$asTables = array();
		}
		foreach($asTables as $sTable)
		{
			$this->setQuery("DELETE FROM ".$sTable." WHERE ".$this->getId($sTableName)." = ".$iTableId);
		}
	}

	public function selectRows($asInfo, $bStringOnly=true, $sGroupBy='')
	{
		$asAttributes = array('select'=>"SELECT", 'from'=>"FROM", 'join'=>"LEFT JOIN", 'joinOn'=>"LEFT JOIN", 'constraint'=>"WHERE", 'groupBy'=>"GROUP BY", 'orderBy'=>"ORDER BY", 'limit'=>'LIMIT');
		$asRowSeparators = array('select'=>", ", 'from'=>"", 'join'=>" LEFT JOIN ", 'joinOn'=>" LEFT JOIN ", 'constraint'=>" AND ", 'groupBy'=>", ", 'orderBy'=>", ", 'limit'=>"");
		$asOperators = array('constraint'=>" = ", 'orderBy'=>" ", 'join'=>" USING(", 'joinOn'=>" ON ");
		$asEndOfStatement = array('constraint'=>"", 'orderBy'=>"", 'join'=>")", 'joinOn'=>"");

		//$sQuery = "/* ".str_replace(array("\n", "\t"), '', print_r($asInfo, true))." */";
		$sQuery = "";
		foreach($asAttributes as $sStatement => $sKeyWord)
		{
			$asSelection = array_key_exists($sStatement, $asInfo)?$asInfo[$sStatement]:array();
			if(!is_array($asSelection))
			{
				$asSelection = array($asSelection);
			}
				
			//if provided values
			if(!empty($asSelection))
			{
				$this->cleanSql($asSelection);

				if($sStatement=='constraint' && !array_key_exists('constVar', $asInfo))
				{
					$asSelection = $this->addQuotes($asSelection);
				}
				$this->addColumnSelectors($asSelection);

				$sQuery .= " ".$sKeyWord." ";

				//in case of double value input
				if(array_key_exists($sStatement, $asOperators))
				{
					if($sStatement=='constraint' && array_key_exists('constOpe', $asInfo))
					{
						$asOperators[$sStatement] = $asInfo['constOpe'];
					}
					$sQuery .= $this->implodeAll($asSelection, $asOperators[$sStatement], $asRowSeparators[$sStatement], "", $asEndOfStatement[$sStatement]);
				}
				else
				{
					$sQuery .= implode($asRowSeparators[$sStatement], $asSelection);
				}
			}
			//default value for select
			elseif($sStatement=='select')
			{
				$sQuery .= " ".$sKeyWord." * ";
			}
		}
		return $this->getArrayQuery($sQuery, $bStringOnly, $sGroupBy);
	}

	private function addColumnSelectors(&$asSelection)
	{
		//TODO get rid of this
		$sSqlWord = 'option';
		$sKey = array_search($sSqlWord, $asSelection);
		if($sKey!==false)
		{
			$asSelection[$sKey] = "`".$asSelection[$sKey]."`";
		}
		elseif(array_key_exists($sSqlWord, $asSelection))
		{
			$asSelection["`".$sSqlWord."`"] = $asSelection[$sSqlWord];
			unset($asSelection[$sSqlWord]);
		}
	}
	
	public function selectRow($sTableName, $asConstraints=array(), $sColumnName='*')
	{
		if(!is_array($asConstraints))
		{
			$asConstraints = array($this->getId($sTableName)=>$asConstraints);
		}
		$asResult = $this->selectRows(array('select'=>$sColumnName, 'from'=>$sTableName, 'constraint'=>$asConstraints));
		$iCountNb = count($asResult);
		switch($iCountNb)
		{
			case 0 :
				return false;
			case $iCountNb > 1 :
				$this->addError('Trop de r&eacute;sultats pour un selectRow() : '.$iCountNb.' lignes. Table: '.$sTableName.', contrainte: '.self::implodeAll($asConstraints, '=', ' ').', colonne: '.$sColumnName);
				break;
		}
		return array_shift($asResult);
	}

	public function selectValue($sTableName, $sColumnName, $oConstraints=array())
	{
		if(!is_array($oConstraints))
		{
			$oConstraints = array($this->getId($sTableName)=>$oConstraints);
		}
		return $this->selectRow($sTableName, $oConstraints, $sColumnName);
	}

	public function pingValue($sTableName, $oConstraints)
	{
		return $this->selectValue($sTableName, 'COUNT(1)', $oConstraints);
	}

	public static function cleanSql(&$oData)
	{
		self::cleanData($oData, 'mysql_real_escape_string');
		$oData = self::cleanData($oData, 'mysql_real_escape_string');
	}

	public static function implodeAll($asText, $asKeyValueSeparator='',  $sRowSeparator='', $sKeyPre='', $sValuePost=false)
	{
		if($sValuePost===false)
		{
			$sValuePost = $sKeyPre;
		}
		$asCombinedText = array();

		//if unique value for key value separator
		if(!is_array($asKeyValueSeparator) && !empty($asText))
		{
			$asKeyValueSeparator = array_combine(array_keys($asText), array_fill(0, count($asText), $asKeyValueSeparator));
		}

		foreach($asText as $sKey=>$sValue)
		{
			$asCombinedText[] = $sKeyPre.$sKey.$asKeyValueSeparator[$sKey].(is_array($sValue)?implode($sValue):$sValue).$sValuePost;
		}
		return implode($sRowSeparator, $asCombinedText);
	}

	public static function arrayKeyFilter($asArray, $sCallBack)
	{
		$asValidKeys = array_flip(array_filter(array_keys($asArray), $sCallBack));
		return array_intersect_key($asArray, $asValidKeys);
	}

	public static function cleanData($oData, $sCleaningFunc)
	{
		if(!is_array($oData))
		{
			$test = call_user_func($sCleaningFunc, $oData);
			return $test;
		}
		elseif(count($oData)>0)
		{
			$asCleaningFunc = array_fill(1, count($oData), $sCleaningFunc);
			$asKeys = array_map(array('self', 'cleanData'), array_keys($oData), $asCleaningFunc);
			$asValues = array_map(array('self', 'cleanData'), $oData, $asCleaningFunc);
			return array_combine($asKeys, $asValues);
		}
	}
}

/**
 * ToolBox - Only static functions missing from php librairy
 * @author franzz
 */
class ToolBox
{
	function __construct(){}
	
	public function toString($sSep, $asData)
	{
		if(is_array($asData) && count($asData)>0)
		{
			$sData = $sSep;
			foreach($asData as $oData)
			{
				$sData .= self::toString($oData).$sSep;
			}
			return $sData;
		}
		else return $asData;
	}
	
	public static function cleanPost(&$asData)
	{
		//get rid of magic quotes
		if(function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc())
		{
			$asData = MySqlManager::cleanData($asData, 'stripslashes');
		}
	}
	
	public static function fixGlobalVars($argv)
	{
		//Add CLI arguments
		if(defined('STDIN')) parse_str(implode('&', array_slice($argv, 1)), $_GET);

		//Add Server Name
		$sServerName = array_key_exists('SERVER_NAME', $_SERVER)?$_SERVER['SERVER_NAME']:$_SERVER['PWD'];
		$sAppPath = 'http://'.str_replace('http://', '', $sServerName.dirname($_SERVER['SCRIPT_NAME']));
		$_GET['serv_name'] = $sAppPath.(substr($sAppPath, -1)!='/'?'/':'');
	}
	
	public static function array_map_encapsulate($oData, $sChar)
	{
		if(is_array($oData))
		{
			$asChar = array_fill(1, count($oData), $sChar);
			return array_combine(array_keys($oData), array_map(array('self', 'array_map_encapsulate'), $oData, $asChar));
		}
		else
		{
			return $sChar.$oData.$sChar;
		}
	}
	
	public static function capitalizeWords($acText, $sCharList = '')
	{
		// Use ucwords if no delimiters are given
		if($sCharList=='') {
			return ucwords($acText);
		}
	   
		// Go through all characters
		$capitalizeNext = true;
		$max = strlen($acText);
		for ($i = 0; $i < $max; $i++)
		{
			if(strpos($sCharList, $acText[$i]) !== false)
			{
				$capitalizeNext = true;
			}
			elseif($capitalizeNext)
			{
				$capitalizeNext = false;
				$acText[$i] = strtoupper($acText[$i]);
			}
		}
	   
		return $acText;
	}
	
	public static function sendMail($sFrom, $sTo, $sReplyTo, $sSubject, $sMessage)
	{
		$sResult = '';
		if($sFrom!='' && $sTo!='' && $sReplyTo!='' && $sSubject!='' && $sMessage!='')
		{
			$sHtmlMessage = str_replace("\n", '<br />', $sMessage);
			$sPlainMessage = strip_tags(str_replace('<br />', "\n", $sHtmlMessage));
	
			$iBoundary = uniqid('HTMLEMAIL');
			$sHeaders = 'From: '.$sFrom."\r\n".
							'Reply-To: '.$sReplyTo."\r\n".
							'MIME-Version: 1.0'."\r\n".
							'Content-Type: multipart/alternative;'.
							'boundary = '.$iBoundary."\r\n\r\n".
							'MIME encoded Message'.
							'--'.$iBoundary."\r\n".
							'Content-Type: text/plain; charset=UTF-8'."\r\n".
							'Content-Transfer-Encoding: base64'."\r\n\r\n".
			chunk_split(base64_encode($sPlainMessage)).
							'--'.$iBoundary."\r\n".
							'Content-Type: text/html; charset=UTF-8'."\r\n".
							'Content-Transfer-Encoding: base64'."\r\n\r\n".
			chunk_split(base64_encode($sHtmlMessage));
				
			if(!mail($sTo, $sSubject, '', $sHeaders))
			{
				$sResult = 'Email: An unknown error occured';
			}
		}
		else
		{
			$sResult = 'Email: Some fields were empty';
		}
	}
	
	public static function createThumbnail($sInPath, $iMaxWidth, $iMaxHeight, $sOutPath='', $bDeleteIn=false)
	{
		$asResult = array('error'=>'');
				
		//Look up the extension to choose the image creator
		//TODO use MIME types
		$sInInfo = pathinfo($sInPath);
		$sInName = strtolower($sInInfo['basename']);
		$sImageExt = strtolower($sInInfo['extension']);
		$sImageExt = ($sImageExt=='jpg')?'jpeg':$sImageExt;
		
		//New Destination folder
		if($sOutPath=='') $sOutPath = $sInPath;
		elseif(substr($sOutPath, -1)=='/') $sOutPath .= $sInName;
		
		//New sizes
		if(in_array($sImageExt, Databap::$UPLOAD_IMG_EXTS))
		{
			list($iWidth, $iHeight) = getimagesize($sInPath);
			if($iWidth > $iMaxWidth || $iHeight > $iMaxHeight)
			{
				$dResizeDeltaWidth = $iWidth - $iMaxWidth;
				$dResizeDeltaHeight = $iHeight - $iMaxHeight;
				if($dResizeDeltaWidth > $dResizeDeltaHeight)
				{
					$iResizedWidth = $iMaxWidth;
					$iResizedHeight = ($iResizedWidth / $iWidth) * $iHeight;	
				}
				else
				{
					$iResizedHeight = $iMaxHeight;
					$iResizedWidth = ($iResizedHeight / $iHeight) * $iWidth;
				}
					
				//create image from source
				$oSource = call_user_func('imagecreatefrom'.$sImageExt, $sInPath);
				
				//Resize
				$oThumb = imagecreatetruecolor($iResizedWidth, $iResizedHeight);
				imagecopyresized($oThumb, $oSource, 0, 0, 0, 0, $iResizedWidth, $iResizedHeight, $iWidth, $iHeight);
				
				//Save
				if(file_exists($sOutPath)) unlink($sOutPath);
				if(!call_user_func_array('image'.$sImageExt, array($oThumb, $sOutPath)))
				{
					$asResult['error'] = 'Unable to create thumbnail : '.$sOutPath;
				}
			}
			elseif($sInPath != $sOutPath)
			{
				$iResizedWidth = $iWidth;
				$iResizedHeight = $iHeight;
				if(!copy($sInPath, $sOutPath)) $asResult['error'] = 'Copy failed from '.$sInPath.' to '.$sOutPath;
			}
			$asResult['width'] = $iResizedWidth;
			$asResult['height'] = $iResizedHeight;
			$asResult['out'] = $sOutPath;
		}
		else $asResult['error'] = 'Wrong file type';
		
		if($bDeleteIn && $asResult['error']=='' && $sInPath != $sOutPath) unlink($sInPath);
			
		return $asResult;
	}
}

/* Debug */

function pre($sText, $sMode='return', $bDie=false, $sTitle='Test')
{
	$sLog =	'<fieldset class="rounded">
					<legend class="rounded">'.$sTitle.'</legend>
					<pre>'.print_r($sText, true).'</pre>
				</fieldset>';
	switch($sMode)
	{
		case 'echo':
			echo $sLog;
			break;
		case 'return':
			if($bDie) echo $sLog;
			break;
		case 'log':
			file_put_contents('log.html', ($sTitle!=''?$sTitle." :\n":'').print_r($sText, true)."\n\n", FILE_APPEND);
			break;
		default:
			break;
	}
	
	if($bDie)
	{
		die('[die() called by the test function '.__FUNCTION__.'()]');
	}
	
	return $sLog;
}

function dlog($sText, $sTitle='Test')
{
	pre($sText, 'log', false, date('d/m/Y  H:m:i').' - '.$sTitle, false);
}

?>