dbobjectsearch.class.php 33 KB

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