dbobjectsearch.class.php 33 KB

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