dbobjectsearch.class.php 34 KB

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