dbobjectsearch.class.php 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004
  1. <?php
  2. /**
  3. * Define filters for a given class of objects (formerly named "filter")
  4. *
  5. * @package iTopORM
  6. * @author Romain Quetiez <romainquetiez@yahoo.fr>
  7. * @author Denis Flaven <denisflave@free.fr>
  8. * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
  9. * @link www.itop.com
  10. * @since 1.0
  11. * @version 1.1.1.1 $
  12. */
  13. /**
  14. * Sibusql - value set start
  15. * @package iTopORM
  16. * @info zis is private
  17. */
  18. define('VS_START', '{');
  19. /**
  20. * Sibusql - value set end
  21. * @package iTopORM
  22. */
  23. define('VS_END', '}');
  24. define('SIBUSQLPARAMREGEXP', "/\\$\\[(.*)\\:(.*)\\:(.*)\\]/U");
  25. define('SIBUSQLTHISREGEXP', "/this\\.(.*)/U");
  26. /**
  27. * Define filters for a given class of objects (formerly named "filter")
  28. *
  29. * @package iTopORM
  30. * @author Romain Quetiez <romainquetiez@yahoo.fr>
  31. * @author Denis Flaven <denisflave@free.fr>
  32. * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
  33. * @link www.itop.com
  34. * @mytagrom youpi
  35. * @since 1.0
  36. * @version 1.1.1.1 $
  37. */
  38. class DBObjectSearch
  39. {
  40. private $m_sClass;
  41. private $m_sClassAlias;
  42. private $m_aClasses; // queried classes (alias => class name)
  43. private $m_oSearchCondition;
  44. private $m_aParams;
  45. private $m_aFullText;
  46. private $m_aPointingTo;
  47. private $m_aReferencedBy;
  48. private $m_aRelatedTo;
  49. public function __construct($sClass, $sClassAlias = '')
  50. {
  51. if (empty($sClassAlias)) $sClassAlias = $sClass;
  52. assert('is_string($sClass)');
  53. assert('MetaModel::IsValidClass($sClass)'); // #@# could do better than an assert, or at least give the caller's reference
  54. // => idee d'un assert avec call stack (autre utilisation = echec sur query SQL)
  55. if (empty($sClassAlias)) $sClassAlias = $sClass;
  56. $this->m_sClass = $sClass;
  57. $this->m_sClassAlias = $sClassAlias;
  58. $this->m_aClasses = array($sClassAlias => $sClass);
  59. $this->m_oSearchCondition = new TrueExpression;
  60. $this->m_aParams = array();
  61. $this->m_aFullText = array();
  62. $this->m_aPointingTo = array();
  63. $this->m_aReferencedBy = array();
  64. $this->m_aRelatedTo = array();
  65. }
  66. public function IsAny()
  67. {
  68. // #@# todo - if (!$this->m_oSearchCondition->IsTrue()) return false;
  69. if (count($this->m_aFullText) > 0) return false;
  70. if (count($this->m_aPointingTo) > 0) return false;
  71. if (count($this->m_aReferencedBy) > 0) return false;
  72. if (count($this->m_aRelatedTo) > 0) return false;
  73. return true;
  74. }
  75. public function Describe()
  76. {
  77. // To replace __Describe
  78. }
  79. public function DescribeConditionPointTo($sExtKeyAttCode)
  80. {
  81. if (!isset($this->m_aPointingTo[$sExtKeyAttCode])) return "";
  82. $oFilter = $this->m_aPointingTo[$sExtKeyAttCode];
  83. if ($oFilter->IsAny()) return "";
  84. $oAtt = MetaModel::GetAttributeDef($this->GetClass(), $sExtKeyAttCode);
  85. return $oAtt->GetLabel()." having ({$oFilter->DescribeConditions()})";
  86. }
  87. public function DescribeConditionRefBy($sForeignClass, $sForeignExtKeyAttCode)
  88. {
  89. if (!isset($this->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode])) return "";
  90. $oFilter = $this->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode];
  91. if ($oFilter->IsAny()) return "";
  92. $oAtt = MetaModel::GetAttributeDef($sForeignClass, $sForeignExtKeyAttCode);
  93. return "being ".$oAtt->GetLabel()." for ".$sForeignClass."s in ({$oFilter->DescribeConditions()})";
  94. }
  95. public function DescribeConditionRelTo($aRelInfo)
  96. {
  97. $oFilter = $aRelInfo['flt'];
  98. $sRelCode = $aRelInfo['relcode'];
  99. $iMaxDepth = $aRelInfo['maxdepth'];
  100. return "related ($sRelCode... peut mieux faire !, $iMaxDepth dig depth) to a {$oFilter->GetClass()} ({$oFilter->DescribeConditions()})";
  101. }
  102. public function DescribeConditions()
  103. {
  104. $aConditions = array();
  105. $aCondFT = array();
  106. foreach($this->m_aFullText as $sFullText)
  107. {
  108. $aCondFT[] = " contain word(s) '$sFullText'";
  109. }
  110. if (count($aCondFT) > 0)
  111. {
  112. $aConditions[] = "which ".implode(" and ", $aCondFT);
  113. }
  114. // #@# todo - review textual description of the JOIN and search condition (is that still feasible?)
  115. $aConditions[] = $this->RenderCondition();
  116. $aCondPoint = array();
  117. foreach($this->m_aPointingTo as $sExtKeyAttCode=>$oFilter)
  118. {
  119. if ($oFilter->IsAny()) continue;
  120. $aCondPoint[] = $this->DescribeConditionPointTo($sExtKeyAttCode);
  121. }
  122. if (count($aCondPoint) > 0)
  123. {
  124. $aConditions[] = implode(" and ", $aCondPoint);
  125. }
  126. $aCondReferred= array();
  127. foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences)
  128. {
  129. foreach($aReferences as $sForeignExtKeyAttCode=>$oForeignFilter)
  130. {
  131. if ($oForeignFilter->IsAny()) continue;
  132. $aCondReferred[] = $this->DescribeConditionRefBy($sForeignClass, $sForeignExtKeyAttCode);
  133. }
  134. }
  135. foreach ($this->m_aRelatedTo as $aRelInfo)
  136. {
  137. $aCondReferred[] = $this->DescribeConditionRelTo($aRelInfo);
  138. }
  139. if (count($aCondReferred) > 0)
  140. {
  141. $aConditions[] = implode(" and ", $aCondReferred);
  142. }
  143. return implode(" and ", $aConditions);
  144. }
  145. public function __DescribeHTML()
  146. {
  147. $sConditionDesc = $this->DescribeConditions();
  148. if (!empty($sConditionDesc))
  149. {
  150. return "Objects of class '$this->m_sClass', $sConditionDesc";
  151. }
  152. return "Any object of class '$this->m_sClass'";
  153. }
  154. protected function TransferConditionExpression($oFilter, $aTranslation)
  155. {
  156. $oTranslated = $oFilter->GetCriteria()->Translate($aTranslation, false);
  157. $this->AddConditionExpression($oTranslated);
  158. // #@# what about collisions in parameter names ???
  159. $this->m_aParams = array_merge($this->m_aParams, $oFilter->m_aParams);
  160. }
  161. public function ResetCondition()
  162. {
  163. $this->m_oSearchCondition = new TrueExpression();
  164. // ? is that usefull/enough, do I need to rebuild the list after the subqueries ?
  165. // $this->m_aClasses = array($this->m_sClassAlias => $this->m_sClass);
  166. }
  167. public function AddConditionExpression($oExpression)
  168. {
  169. $this->m_oSearchCondition = $this->m_oSearchCondition->LogAnd($oExpression);
  170. }
  171. public function AddCondition($sFilterCode, $value, $sOpCode = null)
  172. {
  173. // #@# backward compatibility for pkey/id
  174. if (strtolower(trim($sFilterCode)) == 'pkey') $sFilterCode = 'id';
  175. // #@# todo - obsolete smoothly, first send exceptions
  176. // throw new CoreException('SibusQL has been obsoleted, please update your queries', array('sibusql'=>$sQuery, 'oql'=>$oFilter->ToOQL()));
  177. MyHelpers::CheckKeyInArray('filter code', $sFilterCode, MetaModel::GetClassFilterDefs($this->m_sClass));
  178. $oFilterDef = MetaModel::GetClassFilterDef($this->m_sClass, $sFilterCode);
  179. if (empty($sOpCode))
  180. {
  181. $sOpCode = $oFilterDef->GetLooseOperator();
  182. }
  183. MyHelpers::CheckKeyInArray('operator', $sOpCode, $oFilterDef->GetOperators());
  184. // Preserve backward compatibility - quick n'dirty way to change that API semantic
  185. //
  186. $oField = new FieldExpression($sFilterCode, $this->m_sClassAlias);
  187. switch($sOpCode)
  188. {
  189. case 'SameDay':
  190. case 'SameMonth':
  191. case 'SameYear':
  192. case 'Today':
  193. case '>|':
  194. case '<|':
  195. case '=|':
  196. throw new CoreException('Deprecated operator, please consider using OQL (SQL) expressions like "(TO_DAYS(NOW()) - TO_DAYS(x)) AS AgeDays"', array('operator' => $sOpCode));
  197. break;
  198. case "IN":
  199. if (!is_array($value)) $value = array($value);
  200. $sListExpr = '('.implode(', ', CMDBSource::Quote($value)).')';
  201. $sOQLCondition = $oField->Render()." IN $sListExpr";
  202. break;
  203. case "NOTIN":
  204. if (!is_array($value)) $value = array($value);
  205. $sListExpr = '('.implode(', ', CMDBSource::Quote($value)).')';
  206. $sOQLCondition = $oField->Render()." NOT IN $sListExpr";
  207. break;
  208. case 'Contains':
  209. $this->m_aParams[$sFilterCode] = "%$value%";
  210. $sOperator = 'LIKE';
  211. break;
  212. case 'Begins with':
  213. $this->m_aParams[$sFilterCode] = "$value%";
  214. $sOperator = 'LIKE';
  215. break;
  216. case 'Finishes with':
  217. $this->m_aParams[$sFilterCode] = "%$value";
  218. $sOperator = 'LIKE';
  219. break;
  220. default:
  221. $this->m_aParams[$sFilterCode] = $value;
  222. $sOperator = $sOpCode;
  223. }
  224. switch($sOpCode)
  225. {
  226. case "IN":
  227. case "NOTIN":
  228. $oNewCondition = Expression::FromOQL($sOQLCondition);
  229. break;
  230. case 'Contains':
  231. case 'Begins with':
  232. case 'Finishes with':
  233. default:
  234. $oRightExpr = new VariableExpression($sFilterCode);
  235. $oNewCondition = new BinaryExpression($oField, $sOperator, $oRightExpr);
  236. }
  237. $this->AddConditionExpression($oNewCondition);
  238. }
  239. public function AddCondition_FullText($sFullText)
  240. {
  241. $this->m_aFullText[] = $sFullText;
  242. }
  243. protected function AddToNameSpace(&$aClassAliases, &$aAliasTranslation)
  244. {
  245. $sOrigAlias = $this->m_sClassAlias;
  246. if (array_key_exists($sOrigAlias, $aClassAliases))
  247. {
  248. $this->m_sClassAlias = MetaModel::GenerateUniqueAlias($aClassAliases, $sOrigAlias, $this->m_sClass);
  249. // Translate the condition expression with the new alias
  250. $aAliasTranslation[$sOrigAlias]['*'] = $this->m_sClassAlias;
  251. }
  252. foreach($this->m_aPointingTo as $sExtKeyAttCode=>$oFilter)
  253. {
  254. $oFilter->AddToNameSpace($aClassAliases, $aAliasTranslation);
  255. }
  256. foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences)
  257. {
  258. foreach($aReferences as $sForeignExtKeyAttCode=>$oForeignFilter)
  259. {
  260. $oForeignFilter->AddToNameSpace($aClassAliases, $aAliasTranslation);
  261. }
  262. }
  263. }
  264. public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode)
  265. {
  266. $aAliasTranslation = array();
  267. $res = $this->AddCondition_PointingTo_InNameSpace($oFilter, $sExtKeyAttCode, $this->m_aClasses, $aAliasTranslation);
  268. $this->TransferConditionExpression($oFilter, $aAliasTranslation);
  269. return $res;
  270. }
  271. protected function AddCondition_PointingTo_InNameSpace(DBObjectSearch $oFilter, $sExtKeyAttCode, &$aClassAliases, &$aAliasTranslation)
  272. {
  273. if (!MetaModel::IsValidKeyAttCode($this->GetClass(), $sExtKeyAttCode))
  274. {
  275. throw new CoreWarning("The attribute code '$sExtKeyAttCode' is not an external key of the class '{$this->GetClass()}' - the condition will be ignored");
  276. }
  277. $oAttExtKey = MetaModel::GetAttributeDef($this->GetClass(), $sExtKeyAttCode);
  278. if(!MetaModel::IsSameFamilyBranch($oFilter->GetClass(), $oAttExtKey->GetTargetClass()))
  279. {
  280. throw new CoreException("The specified filter (pointing to {$oFilter->GetClass()}) is not compatible with the key '{$this->GetClass()}::$sExtKeyAttCode', which is pointing to {$oAttExtKey->GetTargetClass()}");
  281. }
  282. if (array_key_exists($sExtKeyAttCode, $this->m_aPointingTo))
  283. {
  284. $this->m_aPointingTo[$sExtKeyAttCode]->MergeWith_InNamespace($oFilter, $aClassAliases, $aAliasTranslation);
  285. }
  286. else
  287. {
  288. $oFilter->AddToNamespace($aClassAliases, $aAliasTranslation);
  289. // #@# The condition expression found in that filter should not be used - could be another kind of structure like a join spec tree !!!!
  290. // $oNewFilter = clone $oFilter;
  291. // $oNewFilter->ResetCondition();
  292. $this->m_aPointingTo[$sExtKeyAttCode] = $oFilter;
  293. }
  294. }
  295. public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode)
  296. {
  297. $aAliasTranslation = array();
  298. $res = $this->AddCondition_ReferencedBy_InNameSpace($oFilter, $sForeignExtKeyAttCode, $this->m_aClasses, $aAliasTranslation);
  299. $this->TransferConditionExpression($oFilter, $aAliasTranslation);
  300. return $res;
  301. }
  302. protected function AddCondition_ReferencedBy_InNameSpace(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, &$aClassAliases, &$aAliasTranslation)
  303. {
  304. $sForeignClass = $oFilter->GetClass();
  305. $sForeignClassAlias = $oFilter->GetClassAlias();
  306. if (!MetaModel::IsValidKeyAttCode($sForeignClass, $sForeignExtKeyAttCode))
  307. {
  308. throw new CoreException("The attribute code '$sForeignExtKeyAttCode' is not an external key of the class '{$sForeignClass}' - the condition will be ignored");
  309. }
  310. $oAttExtKey = MetaModel::GetAttributeDef($sForeignClass, $sForeignExtKeyAttCode);
  311. if(!MetaModel::IsSameFamilyBranch($this->GetClass(), $oAttExtKey->GetTargetClass()))
  312. {
  313. throw new CoreException("The specified filter (objects referencing an object of class {$this->GetClass()}) is not compatible with the key '{$sForeignClass}::$sForeignExtKeyAttCode', which is pointing to {$oAttExtKey->GetTargetClass()}");
  314. }
  315. if (array_key_exists($sForeignClass, $this->m_aReferencedBy) && array_key_exists($sForeignExtKeyAttCode, $this->m_aReferencedBy[$sForeignClass]))
  316. {
  317. $this->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode]->MergeWith_InNamespace($oFilter, $aClassAliases, $aAliasTranslation);
  318. }
  319. else
  320. {
  321. $oFilter->AddToNamespace($aClassAliases, $aAliasTranslation);
  322. // #@# The condition expression found in that filter should not be used - could be another kind of structure like a join spec tree !!!!
  323. //$oNewFilter = clone $oFilter;
  324. //$oNewFilter->ResetCondition();
  325. $this->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode]= $oFilter;
  326. }
  327. }
  328. public function AddCondition_LinkedTo(DBObjectSearch $oLinkFilter, $sExtKeyAttCodeToMe, $sExtKeyAttCodeTarget, DBObjectSearch $oFilterTarget)
  329. {
  330. $oLinkFilterFinal = clone $oLinkFilter;
  331. $oLinkFilterFinal->AddCondition_PointingTo($sExtKeyAttCodeToMe);
  332. $this->AddCondition_ReferencedBy($oLinkFilterFinal, $sExtKeyAttCodeToMe);
  333. }
  334. public function AddCondition_RelatedTo(DBObjectSearch $oFilter, $sRelCode, $iMaxDepth)
  335. {
  336. MyHelpers::CheckValueInArray('relation code', $sRelCode, MetaModel::EnumRelations());
  337. $this->m_aRelatedTo[] = array('flt'=>$oFilter, 'relcode'=>$sRelCode, 'maxdepth'=>$iMaxDepth);
  338. }
  339. public function MergeWith($oFilter)
  340. {
  341. $aAliasTranslation = array();
  342. $res = $this->MergeWith_InNamespace($oFilter, $this->m_aClasses, $aAliasTranslation);
  343. $this->TransferConditionExpression($oFilter, $aAliasTranslation);
  344. return $res;
  345. }
  346. protected function MergeWith_InNamespace($oFilter, &$aClassAliases, &$aAliasTranslation)
  347. {
  348. if ($this->GetClass() != $oFilter->GetClass())
  349. {
  350. throw new CoreException("Attempting to merge a filter of class '{$this->GetClass()}' with a filter of class '{$oFilter->GetClass()}'");
  351. }
  352. // Translate search condition into our aliasing scheme
  353. $aAliasTranslation[$oFilter->GetClassAlias()]['*'] = $this->GetClassAlias();
  354. $this->m_aFullText = array_merge($this->m_aFullText, $oFilter->m_aFullText);
  355. $this->m_aRelatedTo = array_merge($this->m_aRelatedTo, $oFilter->m_aRelatedTo);
  356. foreach($oFilter->m_aPointingTo as $sExtKeyAttCode=>$oExtFilter)
  357. {
  358. $this->AddCondition_PointingTo_InNamespace($oExtFilter, $sExtKeyAttCode, $aClassAliases, $aAliasTranslation);
  359. }
  360. foreach($oFilter->m_aReferencedBy as $sForeignClass => $aReferences)
  361. {
  362. foreach($aReferences as $sForeignExtKeyAttCode => $oForeignFilter)
  363. {
  364. $this->AddCondition_ReferencedBy_InNamespace($oForeignFilter, $sForeignExtKeyAttCode, $aClassAliases, $aAliasTranslation);
  365. }
  366. }
  367. }
  368. public function GetClassName($sAlias) {return $this->m_aClasses[$sAlias];}
  369. public function GetClasses() {return $this->m_aClasses;}
  370. public function GetClass() {return $this->m_sClass;}
  371. public function GetClassAlias() {return $this->m_sClassAlias;}
  372. public function GetCriteria() {return $this->m_oSearchCondition;}
  373. public function GetCriteria_FullText() {return $this->m_aFullText;}
  374. public function GetCriteria_PointingTo($sKeyAttCode = "")
  375. {
  376. if (empty($sKeyAttCode))
  377. {
  378. return $this->m_aPointingTo;
  379. }
  380. if (!array_key_exists($sKeyAttCode, $this->m_aPointingTo)) return null;
  381. return $this->m_aPointingTo[$sKeyAttCode];
  382. }
  383. public function GetCriteria_ReferencedBy($sRemoteClass = "", $sForeignExtKeyAttCode = "")
  384. {
  385. if (empty($sRemoteClass))
  386. {
  387. return $this->m_aReferencedBy;
  388. }
  389. if (!array_key_exists($sRemoteClass, $this->m_aReferencedBy)) return null;
  390. if (empty($sForeignExtKeyAttCode))
  391. {
  392. return $this->m_aReferencedBy[$sRemoteClass];
  393. }
  394. if (!array_key_exists($sForeignExtKeyAttCode, $this->m_aReferencedBy[$sRemoteClass])) return null;
  395. return $this->m_aReferencedBy[$sRemoteClass][$sForeignExtKeyAttCode];
  396. }
  397. public function GetCriteria_RelatedTo()
  398. {
  399. return $this->m_aRelatedTo;
  400. }
  401. public function GetInternalParams()
  402. {
  403. return $this->m_aParams;
  404. }
  405. public function RenderCondition()
  406. {
  407. return $this->m_oSearchCondition->Render($this->m_aParams);
  408. }
  409. public function serialize()
  410. {
  411. // Efficient but resulting in long strings:
  412. // -> return (base64_encode(serialize($this)));
  413. $sValue = $this->GetClass()."\n";
  414. $sValue .= $this->GetClassAlias()."\n";
  415. foreach($this->m_aClasses as $sClassAlias => $sClass)
  416. {
  417. // A stands for "Aliases"
  418. $sValue .= "A:$sClassAlias:$sClass\n";
  419. }
  420. foreach($this->m_aFullText as $sFullText)
  421. {
  422. // F stands for "Full text"
  423. $sValue .= "F:".$sFullText."\n";
  424. }
  425. $sValue .= "C:".$this->m_oSearchCondition->serialize()."\n";
  426. foreach($this->m_aPointingTo as $sExtKey=>$oFilter)
  427. {
  428. // P stands for "Pointing to"
  429. $sValue .= "P:".$sExtKey.":".$oFilter->serialize()."\n";
  430. }
  431. foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences)
  432. {
  433. foreach($aReferences as $sForeignExtKeyAttCode=>$oForeignFilter)
  434. {
  435. // R stands for "Referenced by"
  436. $sValue .= "R:".$sForeignExtKeyAttCode.":".$oForeignFilter->serialize()."\n";
  437. }
  438. }
  439. foreach($this->m_aRelatedTo as $aRelatedTo)
  440. {
  441. $oFilter = $aRelatedTo['flt'];
  442. $sRelCode = $aRelatedTo['relcode'];
  443. $iMaxDepth = $aRelatedTo['maxdepth'];
  444. $sValue .= "T:".$oFilter->serialize().":$sRelCode:$iMaxDepth";
  445. }
  446. return base64_encode($sValue);
  447. }
  448. static public function unserialize($sValue)
  449. {
  450. // See comment above...
  451. // -> return (unserialize(base64_decode($sValue)));
  452. $sClearText = base64_decode($sValue);
  453. $aValues = split("\n", $sClearText);
  454. $i = 0;
  455. $sClass = $aValues[$i++];
  456. $sClassAlias = $aValues[$i++];
  457. $oFilter = new DBObjectSearch($sClass, $sClassAlias);
  458. while($i < count($aValues) && !empty($aValues[$i]))
  459. {
  460. $aCondition = split(":", $aValues[$i++]);
  461. switch ($aCondition[0])
  462. {
  463. case "A":
  464. $oFilter->m_aClasses[$aCondition[1]] = $aCondition[2];
  465. break;
  466. case "F":
  467. $oFilter->AddCondition_FullText($aCondition[1]);
  468. break;
  469. case "C":
  470. $oFilter->m_oSearchCondition = Expression::unserialize($aCondition[1]);
  471. break;
  472. case "P":
  473. //$oAtt = DBObject::GetAttributeDef($sClass, $aCondition[1]);
  474. //$sRemoteClass = $oAtt->GetTargetClass();
  475. $oSubFilter = self::unserialize($aCondition[2]);
  476. $sExtKeyAttCode = $aCondition[1];
  477. $oFilter->AddCondition_PointingTo($oSubFilter, $sExtKeyAttCode);
  478. break;
  479. case "R":
  480. $oRemoteFilter = self::unserialize($aCondition[2]);
  481. $sExtKeyAttCodeToMe = $aCondition[1];
  482. $oFilter->AddCondition_ReferencedBy($oRemoteFilter, $sExtKeyAttCodeToMe);
  483. break;
  484. case "T":
  485. $oSubFilter = self::unserialize($aCondition[1]);
  486. $sRelCode = $aCondition[2];
  487. $iMaxDepth = $aCondition[3];
  488. $oFilter->AddCondition_RelatedTo($oSubFilter, $sRelCode, $iMaxDepth);
  489. default:
  490. throw new CoreException("invalid filter definition (cannot unserialize the data, clear text = '$sClearText')");
  491. }
  492. }
  493. return $oFilter;
  494. }
  495. // SImple BUt Structured Query Languag - SubuSQL
  496. //
  497. static private function Value2Expression($value)
  498. {
  499. $sRet = $value;
  500. if (is_array($value))
  501. {
  502. $sRet = VS_START.implode(', ', $value).VS_END;
  503. }
  504. else if (!is_numeric($value))
  505. {
  506. $sRet = "'".addslashes($value)."'";
  507. }
  508. return $sRet;
  509. }
  510. static private function Expression2Value($sExpr)
  511. {
  512. $retValue = $sExpr;
  513. if ((substr($sExpr, 0, 1) == "'") && (substr($sExpr, -1, 1) == "'"))
  514. {
  515. $sNoQuotes = substr($sExpr, 1, -1);
  516. return stripslashes($sNoQuotes);
  517. }
  518. if ((substr($sExpr, 0, 1) == VS_START) && (substr($sExpr, -1, 1) == VS_END))
  519. {
  520. $sNoBracket = substr($sExpr, 1, -1);
  521. $aRetValue = array();
  522. foreach (explode(",", $sNoBracket) as $sItem)
  523. {
  524. $aRetValue[] = self::Expression2Value(trim($sItem));
  525. }
  526. return $aRetValue;
  527. }
  528. return $retValue;
  529. }
  530. public function ToOQL(&$aParams = null)
  531. {
  532. $bRetrofitParams = (!is_null($aParams));
  533. $sRes = "SELECT ".$this->GetClass().' AS '.$this->GetClassAlias();
  534. $sRes .= $this->ToOQL_Joins();
  535. $sRes .= " WHERE ".$this->m_oSearchCondition->Render($aParams, $bRetrofitParams);
  536. return $sRes;
  537. }
  538. protected function ToOQL_Joins()
  539. {
  540. $sRes = '';
  541. foreach($this->m_aPointingTo as $sExtKey=>$oFilter)
  542. {
  543. $sRes .= ' JOIN '.$oFilter->GetClass().' AS '.$oFilter->GetClassAlias().' ON '.$this->GetClassAlias().'.'.$sExtKey.' = '.$oFilter->GetClassAlias().'.id';
  544. $sRes .= $oFilter->ToOQL_Joins();
  545. }
  546. foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences)
  547. {
  548. foreach($aReferences as $sForeignExtKeyAttCode=>$oForeignFilter)
  549. {
  550. $sRes .= ' JOIN '.$oForeignFilter->GetClass().' AS '.$oForeignFilter->GetClassAlias().' ON '.$oForeignFilter->GetClassAlias().'.'.$sForeignExtKeyAttCode.' = '.$this->GetClassAlias().'.id';
  551. $sRes .= $oForeignFilter->ToOQL_Joins();
  552. }
  553. }
  554. return $sRes;
  555. }
  556. public function ToSibusQL()
  557. {
  558. return "NONONO";
  559. }
  560. static private function privProcessParams($sQuery, array $aParams, $oDbObject)
  561. {
  562. $iPlaceHoldersCount = preg_match_all(SIBUSQLPARAMREGEXP, $sQuery, $aMatches, PREG_SET_ORDER);
  563. if ($iPlaceHoldersCount > 0)
  564. {
  565. foreach($aMatches as $aMatch)
  566. {
  567. $sStringToSearch = $aMatch[0];
  568. $sParameterName = $aMatch[1];
  569. $sDefaultValue = $aMatch[2];
  570. $sDescription = $aMatch[3];
  571. $sValue = $sDefaultValue;
  572. if (array_key_exists($sParameterName, $aParams))
  573. {
  574. $sValue = $aParams[$sParameterName];
  575. unset($aParams[$sParameterName]);
  576. }
  577. else if (is_object($oDbObject))
  578. {
  579. if (strpos($sParameterName, "this.") === 0)
  580. {
  581. $sAttCode = substr($sParameterName, strlen("this."));
  582. if ($sAttCode == 'id')
  583. {
  584. $sValue = $oDbObject->GetKey();
  585. }
  586. else if ($sAttCode == 'class')
  587. {
  588. $sValue = get_class($oDbObject);
  589. }
  590. else if (MetaModel::IsValidAttCode(get_class($oDbObject), $sAttCode))
  591. {
  592. $sValue = $oDbObject->Get($sAttCode);
  593. }
  594. }
  595. }
  596. $sQuery = str_replace($sStringToSearch, $sValue, $sQuery);
  597. }
  598. }
  599. if (count($aParams) > 0)
  600. {
  601. // throw new CoreException("Unused parameter(s) for this SibusQL expression: (".implode(', ', array_keys($aParams)).")");
  602. }
  603. return $sQuery;
  604. }
  605. static public function ListSibusQLParams($sQuery)
  606. {
  607. $aRet = array();
  608. $iPlaceHoldersCount = preg_match_all(SIBUSQLPARAMREGEXP, $sQuery, $aMatches, PREG_SET_ORDER);
  609. if ($iPlaceHoldersCount > 0)
  610. {
  611. foreach($aMatches as $aMatch)
  612. {
  613. $sStringToSearch = $aMatch[0];
  614. $sParameterName = $aMatch[1];
  615. $sDefaultValue = $aMatch[2];
  616. $sDescription = $aMatch[3];
  617. $aRet[$sParameterName]["description"] = $sDescription;
  618. $aRet[$sParameterName]["default"] = $sDefaultValue;
  619. }
  620. }
  621. return $aRet;
  622. }
  623. protected function OQLExpressionToCondition($sQuery, $oExpression, $aClassAliases)
  624. {
  625. if ($oExpression instanceof BinaryOqlExpression)
  626. {
  627. $sOperator = $oExpression->GetOperator();
  628. $oLeft = $this->OQLExpressionToCondition($sQuery, $oExpression->GetLeftExpr(), $aClassAliases);
  629. $oRight = $this->OQLExpressionToCondition($sQuery, $oExpression->GetRightExpr(), $aClassAliases);
  630. return new BinaryExpression($oLeft, $sOperator, $oRight);
  631. }
  632. elseif ($oExpression instanceof FieldOqlExpression)
  633. {
  634. $sClassAlias = $oExpression->GetParent();
  635. $sFltCode = $oExpression->GetName();
  636. if (empty($sClassAlias))
  637. {
  638. // Try to find an alias
  639. // Build an array of field => array of aliases
  640. $aFieldClasses = array();
  641. foreach($aClassAliases as $sAlias => $sReal)
  642. {
  643. foreach(MetaModel::GetFiltersList($sReal) as $sAnFltCode)
  644. {
  645. $aFieldClasses[$sAnFltCode][] = $sAlias;
  646. }
  647. }
  648. if (!array_key_exists($sFltCode, $aFieldClasses))
  649. {
  650. throw new OqlNormalizeException('Unknown filter code', $sQuery, $oExpression->GetNameDetails(), array_keys($aFieldClasses));
  651. }
  652. if (count($aFieldClasses[$sFltCode]) > 1)
  653. {
  654. throw new OqlNormalizeException('Ambiguous filter code', $sQuery, $oExpression->GetNameDetails());
  655. }
  656. $sClassAlias = $aFieldClasses[$sFltCode][0];
  657. }
  658. else
  659. {
  660. if (!array_key_exists($sClassAlias, $aClassAliases))
  661. {
  662. throw new OqlNormalizeException('Unknown class [alias]', $sQuery, $oExpression->GetParentDetails(), array_keys($aClassAliases));
  663. }
  664. $sClass = $aClassAliases[$sClassAlias];
  665. if (!MetaModel::IsValidFilterCode($sClass, $sFltCode))
  666. {
  667. throw new OqlNormalizeException('Unknown filter code', $sQuery, $oExpression->GetNameDetails(), MetaModel::GetFiltersList($sClass));
  668. }
  669. }
  670. return new FieldExpression($sFltCode, $sClassAlias);
  671. }
  672. elseif ($oExpression instanceof VariableOqlExpression)
  673. {
  674. return new VariableExpression($oExpression->GetName());
  675. }
  676. elseif ($oExpression instanceof TrueOqlExpression)
  677. {
  678. return new TrueExpression;
  679. }
  680. elseif ($oExpression instanceof ScalarOqlExpression)
  681. {
  682. return new ScalarExpression($oExpression->GetValue());
  683. }
  684. elseif ($oExpression instanceof ListOqlExpression)
  685. {
  686. return new ListExpression($oExpression->GetItems());
  687. }
  688. elseif ($oExpression instanceof FunctionOqlExpression)
  689. {
  690. return new FunctionExpression($oExpression->GetVerb(), $oExpression->GetArgs());
  691. }
  692. else
  693. {
  694. throw new CoreException('Unknown expression type', array('class'=>get_class($oExpression), 'query'=>$sQuery));
  695. }
  696. }
  697. static protected $m_aOQLQueries = array();
  698. static public function FromOQL($sQuery)
  699. {
  700. if (empty($sQuery)) return null;
  701. // Query caching
  702. $bOQLCacheEnabled = true;
  703. if ($bOQLCacheEnabled && array_key_exists($sQuery, self::$m_aOQLQueries))
  704. {
  705. // hit!
  706. return clone self::$m_aOQLQueries[$sQuery];
  707. }
  708. $oOql = new OqlInterpreter($sQuery);
  709. $oOqlQuery = $oOql->ParseObjectQuery();
  710. $sClass = $oOqlQuery->GetClass();
  711. $sClassAlias = $oOqlQuery->GetClassAlias();
  712. if (!MetaModel::IsValidClass($sClass))
  713. {
  714. throw new OqlNormalizeException('Unknown class', $sQuery, $oOqlQuery->GetClassDetails(), MetaModel::GetClasses());
  715. }
  716. $oResultFilter = new DBObjectSearch($sClass, $sClassAlias);
  717. $aAliases = array($sClassAlias => $sClass);
  718. // Maintain an array of filters, because the flat list is in fact referring to a tree
  719. // And this will be an easy way to dispatch the conditions
  720. // $oResultFilter will be referenced by the other filters, or the other way around...
  721. $aJoinItems = array($sClassAlias => $oResultFilter);
  722. $aJoinSpecs = $oOqlQuery->GetJoins();
  723. if (is_array($aJoinSpecs))
  724. {
  725. foreach ($aJoinSpecs as $oJoinSpec)
  726. {
  727. $sJoinClass = $oJoinSpec->GetClass();
  728. $sJoinClassAlias = $oJoinSpec->GetClassAlias();
  729. if (!MetaModel::IsValidClass($sJoinClass))
  730. {
  731. throw new OqlNormalizeException('Unknown class', $sQuery, $oJoinSpec->GetClassDetails(), MetaModel::GetClasses());
  732. }
  733. if (array_key_exists($sJoinClassAlias, $aAliases))
  734. {
  735. if ($sJoinClassAlias != $sJoinClass)
  736. {
  737. throw new OqlNormalizeException('Duplicate class alias', $sQuery, $oJoinSpec->GetClassAliasDetails());
  738. }
  739. else
  740. {
  741. throw new OqlNormalizeException('Duplicate class name', $sQuery, $oJoinSpec->GetClassDetails());
  742. }
  743. }
  744. // Assumption: ext key on the left only !!!
  745. // normalization should take care of this
  746. $oLeftField = $oJoinSpec->GetLeftField();
  747. $sFromClass = $oLeftField->GetParent();
  748. $sExtKeyAttCode = $oLeftField->GetName();
  749. $oRightField = $oJoinSpec->GetRightField();
  750. $sToClass = $oRightField->GetParent();
  751. $sPKeyDescriptor = $oRightField->GetName();
  752. if ($sPKeyDescriptor != 'id')
  753. {
  754. throw new OqlNormalizeException('Wrong format for Join clause (right hand), expecting an id', $sQuery, $oRightField->GetNameDetails(), array('id'));
  755. }
  756. $aAliases[$sJoinClassAlias] = $sJoinClass;
  757. $aJoinItems[$sJoinClassAlias] = new DBObjectSearch($sJoinClass, $sJoinClassAlias);
  758. if (!array_key_exists($sFromClass, $aJoinItems))
  759. {
  760. throw new OqlNormalizeException('Unknown class in join condition (left expression)', $sQuery, $oLeftField->GetParentDetails(), array_keys($aJoinItems));
  761. }
  762. if (!array_key_exists($sToClass, $aJoinItems))
  763. {
  764. throw new OqlNormalizeException('Unknown class in join condition (right expression)', $sQuery, $oRightField->GetParentDetails(), array_keys($aJoinItems));
  765. }
  766. $aExtKeys = array_keys(MetaModel::GetExternalKeys($aAliases[$sFromClass]));
  767. if (!in_array($sExtKeyAttCode, $aExtKeys))
  768. {
  769. throw new OqlNormalizeException('Unknown external key in join condition (left expression)', $sQuery, $oLeftField->GetNameDetails(), $aExtKeys);
  770. }
  771. if ($sFromClass == $sJoinClassAlias)
  772. {
  773. $aJoinItems[$sToClass]->AddCondition_ReferencedBy($aJoinItems[$sFromClass], $sExtKeyAttCode);
  774. }
  775. else
  776. {
  777. $aJoinItems[$sFromClass]->AddCondition_PointingTo($aJoinItems[$sToClass], $sExtKeyAttCode);
  778. }
  779. }
  780. }
  781. $oConditionTree = $oOqlQuery->GetCondition();
  782. if ($oConditionTree instanceof Expression)
  783. {
  784. $oResultFilter->m_oSearchCondition = $oResultFilter->OQLExpressionToCondition($sQuery, $oConditionTree, $aAliases);
  785. }
  786. if ($bOQLCacheEnabled)
  787. {
  788. self::$m_aOQLQueries[$sQuery] = clone $oResultFilter;
  789. }
  790. return $oResultFilter;
  791. }
  792. static public function FromSibusQL($sQuery, array $aParams = array(), $oObject = null)
  793. {
  794. if (empty($sQuery)) return null;
  795. $sQuery = self::privProcessParams($sQuery, $aParams, $oObject);
  796. if (preg_match('@^\\s*SELECT@', $sQuery))
  797. {
  798. return self::FromOQL($sQuery, $aParams, $oObject);
  799. }
  800. $iSepPos = strpos($sQuery, ":");
  801. if ($iSepPos === false)
  802. {
  803. // Only the class was specified -> all rows are required
  804. $sClass = trim($sQuery);
  805. $oFilter = new DBObjectSearch($sClass);
  806. }
  807. else
  808. {
  809. $sClass = trim(substr($sQuery, 0, $iSepPos));
  810. $sConds = trim(substr($sQuery, $iSepPos + 1));
  811. $aValues = split(" AND ", $sConds);
  812. $oFilter = new DBObjectSearch($sClass);
  813. foreach ($aValues as $sCond)
  814. {
  815. $sCond = trim($sCond);
  816. if (strpos($sCond, "* HAS ") === 0)
  817. {
  818. $sValue = self::Expression2Value(substr($sCond, strlen("* HAS ")));
  819. $oFilter->AddCondition_FullText($sValue);
  820. }
  821. else if (preg_match("@^(\S+) IN \\((.+)\\)$@", $sCond, $aMatches))
  822. {
  823. $sExtKeyAttCode = $aMatches[1];
  824. $sFilterExp = $aMatches[2];
  825. $oSubFilter = self::FromSibuSQL($sFilterExp);
  826. $oFilter->AddCondition_PointingTo($oSubFilter, $sExtKeyAttCode);
  827. }
  828. else if (strpos($sCond, "PKEY IS ") === 0)
  829. {
  830. if (preg_match("@^PKEY IS (\S+) IN \\((.+)\\)$@", $sCond, $aMatches))
  831. {
  832. $sExtKeyAttCodeToMe = $aMatches[1];
  833. $sFilterExp = $aMatches[2];
  834. $oRemoteFilter = self::FromSibuSQL($sFilterExp);
  835. $oFilter->AddCondition_ReferencedBy($oRemoteFilter, $sExtKeyAttCodeToMe);
  836. }
  837. }
  838. else if (strpos($sCond, "RELATED") === 0)
  839. {
  840. if (preg_match("@^RELATED\s*\\((.+)\\)\s*TO\s*\\((.+)\\)@", trim($sCond), $aMatches))
  841. {
  842. $aRelation = explode(',', trim($aMatches[1]));
  843. $sRelCode = trim($aRelation[0]);
  844. $iMaxDepth = intval(trim($aRelation[1]));
  845. $sFilterExp = trim($aMatches[2]);
  846. $oSubFilter = self::FromSibuSQL($sFilterExp);
  847. $oFilter->AddCondition_RelatedTo($oSubFilter, $sRelCode, $iMaxDepth);
  848. }
  849. }
  850. else
  851. {
  852. $sOperandExpr = "'.*'|\d+|-\d+|".VS_START.".+".VS_END;
  853. if (preg_match("@^(\S+)\s+(.*)\s+($sOperandExpr)$@", $sCond, $aMatches))
  854. {
  855. $sFltCode = trim($aMatches[1]);
  856. $sOpCode = trim($aMatches[2]);
  857. $value = self::Expression2Value($aMatches[3]);
  858. $oFilter->AddCondition($sFltCode, $value, $sOpCode);
  859. }
  860. else
  861. {
  862. throw new CoreException("Wrong format for filter definition: '$sQuery'");
  863. }
  864. }
  865. }
  866. }
  867. // #@# todo - obsolete smoothly, first give the OQL version !
  868. // throw new CoreException('SibusQL has been obsoleted, please update your queries', array('sibusql'=>$sQuery, 'oql'=>$oFilter->ToOQL()));
  869. return $oFilter;
  870. }
  871. // Sexy display of a SibuSQL expression
  872. static public function SibuSQLAsHtml($sQuery)
  873. {
  874. $sQuery = htmlentities($sQuery);
  875. $aParams = self::ListSibusQLParams($sQuery);
  876. $aParamValues = array();
  877. foreach ($aParams as $sParamName => $aParamInfo)
  878. {
  879. $sDescription = $aParamInfo["description"];
  880. $sDefaultValue = $aParamInfo["default"];
  881. $aParamValues[$sParamName] = "<span style=\"background-color:#aaa;\" title\"$sDescription (default to '$sDefaultValue')\">$sParamName</span>";
  882. }
  883. $sQuery = self::privProcessParams($sQuery, $aParamValues, null);
  884. return $sQuery;
  885. }
  886. public function toxpath()
  887. {
  888. // #@# a voir...
  889. }
  890. static public function fromxpath()
  891. {
  892. // #@# a voir...
  893. }
  894. }
  895. ?>