PageRenderTime 45ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/iblock/lib/template/engine.php

https://gitlab.com/alexprowars/bitrix
PHP | 460 lines | 337 code | 9 blank | 114 comment | 14 complexity | 81afa4a7233428b23ba09b8b55b0a7f4 MD5 | raw file
  1. <?php
  2. /**
  3. * Bitrix Framework
  4. * @package bitrix
  5. * @subpackage iblock
  6. */
  7. /*
  8. \Bitrix\Main\Loader::includeModule('iblock');
  9. $arFields = array(
  10. "IBLOCK_ID" => 422,
  11. "NAME" => "Product name",
  12. "CODE" => "main code",
  13. "IBLOCK_SECTION_ID" => 12744,
  14. "PROPERTY_VALUES" => array(
  15. "CML2_ARTICLE" => "XX-SM6-XXXXXXXXX",
  16. ),
  17. );
  18. $element = new \Bitrix\Iblock\Template\Entity\Element(0);
  19. $element->setFields($arFields);
  20. echo "<pre>";//print_r($element);
  21. $arSkuCollection = array();
  22. for ($i = 0; $i < 5; $i++)
  23. {
  24. $arSkuCollection[$i] = array(
  25. "IBLOCK_ID" => 423,
  26. "NAME" => null,
  27. "CODE" => "code($i)",
  28. "PROPERTY_VALUES" => array(
  29. "CML2_LINK" => $element,
  30. "1377" => "0x0555555".$i,
  31. "1875" => 1045,
  32. ),
  33. );
  34. $sku = new \Bitrix\Iblock\Template\Entity\Element(0);
  35. $sku->setFields($arSkuCollection[$i]);//print_r($sku);
  36. $arSkuCollection[$i]["NAME"] = \Bitrix\Iblock\Template\Engine::process(
  37. $sku
  38. //,'{=this.property.cml2_link.name}: {=this.property.cml2_link.property.CML2_ARTICUL}, {=this.property.cml2_bar_code}'
  39. //,'Q{=this.code}Q'
  40. //,'Q{=this.property}Q'
  41. ,'Q{=this.property.1377}Q{=this.property.1875}Q'
  42. //,'Q{=this.property.CML2_LINK}Q'
  43. //,'Q{=this.property.CML2_LINK.name}Q'
  44. //,'Q{=this.property.CML2_LINK.code}Q'
  45. //,'Q{=this.property.CML2_LINK.property.CML2_ARTICLE}Q'
  46. //,'Q{=this.property.CML2_LINK.parent.property.str}Q'
  47. //,'Q{=this.catalog.price.base}Q'
  48. //,'Q{=this.catalog.weight}Q'
  49. //,'Q{=this.catalog.measure}Q'
  50. //,'Q{=this.catalog.store.1.name}Q'
  51. //,'Q{=catalog.store}Q'
  52. );
  53. }
  54. echo "<pre>",htmlspecialcharsEx(print_r($arSkuCollection,1)),"</pre>";
  55. */
  56. namespace Bitrix\Iblock\Template;
  57. /**
  58. * Class Engine
  59. * Provides interface for templates processing.
  60. * @package Bitrix\Iblock\Template
  61. */
  62. class Engine
  63. {
  64. /**
  65. * Takes an entity (Element, Section or Iblock) and processes the template.
  66. * <code>
  67. * if (\Bitrix\Main\Loader::includeModule('iblock'))
  68. * &#123;
  69. * $e = new \Bitrix\Iblock\Template\Entity\Element(6369);
  70. * echo "<pre>", print_r(\Bitrix\Iblock\Template\Engine::process($e, "Name: {=this.Name}. Code:{=this.code}"), 1), "</pre>";
  71. * &#125;
  72. * </code>
  73. *
  74. * @param Entity\Base $entity Context entity for template processing.
  75. * @param string $template Template to make substitutions in.
  76. *
  77. * @return string
  78. */
  79. public static function process(Entity\Base $entity, $template)
  80. {
  81. $rootNode = self::parseTemplateTree($template, new NodeRoot);
  82. return $rootNode->process($entity);
  83. }
  84. /**
  85. * Splits template by tokens and builds execution tree.
  86. *
  87. * @param string $template Source expression.
  88. * @param NodeRoot $parent Root node.
  89. *
  90. * @return NodeRoot
  91. */
  92. protected static function parseTemplateTree($template, NodeRoot $parent)
  93. {
  94. list($template, $modifiers) = Helper::splitTemplate($template);
  95. if ($modifiers != "")
  96. $parent->setModifiers($modifiers);
  97. $parsedTemplate = preg_split('/({=|})/', $template, -1, PREG_SPLIT_DELIM_CAPTURE);
  98. while (($token = array_shift($parsedTemplate)) !== null)
  99. {
  100. $node = null;
  101. if ($token === "{=")
  102. {
  103. $node = self::parseFormula($parsedTemplate);
  104. }
  105. elseif ($token !== "")
  106. {
  107. $node = new NodeText($token);
  108. }
  109. if ($node)
  110. $parent->addChild($node);
  111. }
  112. return $parent;
  113. }
  114. /**
  115. * Parses "{=" part of the template. Moves internal pointer right behind balanced "}"
  116. * after {= a field of the entity should follow
  117. * or a function call.
  118. *
  119. * @param array[]string &$parsedTemplate Template tokens.
  120. *
  121. * @return NodeEntityField|NodeFunction|null
  122. */
  123. protected static function parseFormula(array &$parsedTemplate)
  124. {
  125. $node = null;
  126. if (($token = array_shift($parsedTemplate)) !== null)
  127. {
  128. if (preg_match("/^([a-zA-Z0-9_]+\\.[a-zA-Z0-9_.]+)\\s*\$/", $token, $match))
  129. {
  130. $node = new NodeEntityField($match[1]);
  131. }
  132. elseif (preg_match("/^([a-zA-Z0-9_]+)(.*)\$/", $token, $match))
  133. {
  134. $node = new NodeFunction($match[1]);
  135. self::parseFunctionArguments($match[2], $parsedTemplate, $node);
  136. }
  137. }
  138. //Eat up to the formula end
  139. while (($token = array_shift($parsedTemplate)) !== null)
  140. {
  141. if ($token === "}")
  142. break;
  143. }
  144. return $node;
  145. }
  146. /**
  147. * Adds function arguments to a $function.
  148. * An formula may be evaluated as oa argument.
  149. * An number or
  150. * A string in double quotes.
  151. *
  152. * @param string $token Expression string.
  153. * @param array[]string &$parsedTemplate Template tokens.
  154. * @param NodeFunction $function Function object to which arguments will be added.
  155. *
  156. * @return void
  157. */
  158. protected static function parseFunctionArguments($token, array &$parsedTemplate, NodeFunction $function)
  159. {
  160. $token = ltrim($token, " \t\n\r");
  161. if ($token !== "")
  162. self::explodeFunctionArgument($token, $function);
  163. while (($token = array_shift($parsedTemplate)) !== null)
  164. {
  165. if ($token === "}")
  166. {
  167. array_unshift($parsedTemplate, $token);
  168. break;
  169. }
  170. elseif ($token === "{=")
  171. {
  172. $node = self::parseFormula($parsedTemplate);
  173. if ($node)
  174. $function->addParameter($node);
  175. }
  176. elseif ($token !== "")
  177. {
  178. self::explodeFunctionArgument($token, $function);
  179. }
  180. }
  181. }
  182. /**
  183. * Explodes a string into function arguments.
  184. * Numbers or strings.
  185. *
  186. * @param string $token Expression string.
  187. * @param NodeFunction $function Function object to which arguments will be added.
  188. *
  189. * @return void
  190. */
  191. protected static function explodeFunctionArgument($token, NodeFunction $function)
  192. {
  193. if (preg_match_all("/
  194. (
  195. [a-zA-Z0-9_]+\\.[a-zA-Z0-9_.]+
  196. |[0-9]+
  197. |\"[^\"]*\"
  198. )
  199. /x", $token, $wordList)
  200. )
  201. {
  202. foreach ($wordList[0] as $word)
  203. {
  204. if ($word !== "")
  205. {
  206. if (preg_match("/^([a-zA-Z0-9_]+\\.[a-zA-Z0-9_.]+)\\s*\$/", $word, $match))
  207. {
  208. $node = new NodeEntityField($match[1]);
  209. }
  210. else
  211. {
  212. $node = new NodeText(trim($word, '"'));
  213. }
  214. $function->addParameter($node);
  215. }
  216. }
  217. }
  218. }
  219. }
  220. /**
  221. * Class NodeBase
  222. * Base class for template parsed tree nodes.
  223. * @package Bitrix\Iblock\Template
  224. */
  225. abstract class NodeBase
  226. {
  227. /**
  228. * Converts internal contents of the node into external presentation.
  229. * It's a string or an array of strings.
  230. *
  231. * @param Entity\Base $entity Sets the context of processing.
  232. *
  233. * @return string
  234. */
  235. abstract public function process(Entity\Base $entity);
  236. }
  237. /**
  238. * Class NodeRoot
  239. * Present simple collection of child nodes
  240. *
  241. * @package Bitrix\Iblock\Template
  242. */
  243. class NodeRoot extends NodeBase
  244. {
  245. /** @var array[int]NodeBase */
  246. protected $children = array();
  247. protected $modifiers = array();
  248. /**
  249. * Appends a child to the children collection.
  250. *
  251. * @param NodeBase $child Object to be added as a child.
  252. *
  253. * @return void
  254. */
  255. public function addChild(NodeBase $child)
  256. {
  257. $this->children[] = $child;
  258. }
  259. /**
  260. * Sets modifiers to be used at the last stage of processing.
  261. *
  262. * @param string $modifiers String which contains modifiers.
  263. *
  264. * @return void
  265. */
  266. public function setModifiers($modifiers)
  267. {
  268. $this->modifiers = array();
  269. foreach(Helper::splitModifiers($modifiers) as $mod)
  270. {
  271. if ($mod == "l")
  272. $modifierFunction = Functions\Fabric::createInstance("lower");
  273. else
  274. $modifierFunction = Functions\Fabric::createInstance("translit", array(
  275. "replace_space" => mb_substr($mod, 1),
  276. ));
  277. $this->modifiers[] = $modifierFunction;
  278. }
  279. }
  280. /**
  281. * Calls process for each of it's children.
  282. * Returns concatenation of their results.
  283. *
  284. * @param Entity\Base $entity Sets the context of processing.
  285. *
  286. * @return string
  287. */
  288. public function process(Entity\Base $entity)
  289. {
  290. $content = "";
  291. /** @var NodeBase $child*/
  292. foreach ($this->children as $child)
  293. {
  294. $childContent = $child->process($entity);
  295. if (is_array($childContent))
  296. $content .= implode(" ", $childContent);
  297. else
  298. $content .= $childContent;
  299. }
  300. /** @var Functions\FunctionBase $modifier*/
  301. foreach ($this->modifiers as $modifier)
  302. {
  303. $node = new NodeText($content);
  304. $arguments = $modifier->onPrepareParameters($entity, array($node));
  305. $content = $modifier->calculate($arguments);
  306. }
  307. return $content;
  308. }
  309. }
  310. /**
  311. * Class NodeText
  312. * Plain text node
  313. *
  314. * @package Bitrix\Iblock\Template
  315. */
  316. class NodeText extends NodeBase
  317. {
  318. protected $content = "";
  319. /**
  320. * Sets text contents into $content.
  321. *
  322. * @param string $content A text to be saved.
  323. */
  324. function __construct($content = "")
  325. {
  326. $this->content = $content;
  327. }
  328. /**
  329. * Returns text contents.
  330. *
  331. * @param Entity\Base $entity Sets the context of processing.
  332. *
  333. * @return string
  334. */
  335. public function process(Entity\Base $entity)
  336. {
  337. return $this->content;
  338. }
  339. }
  340. /**
  341. * Class NodeEntityField
  342. * Represents an entity field in the formula.
  343. * For example: this.name
  344. *
  345. * @package Bitrix\Iblock\Template
  346. */
  347. class NodeEntityField extends NodeBase
  348. {
  349. protected $entityField = "";
  350. /**
  351. * Initializes the object.
  352. * $entityName and $entityField is case insensitive.
  353. *
  354. * @param string $entityField Field of the Entity.
  355. */
  356. function __construct($entityField = "")
  357. {
  358. $this->entityField = mb_strtolower($entityField);
  359. }
  360. /**
  361. * Calls "resolve" method of the $entity.
  362. * On success returns field value of the resolved.
  363. * Otherwise returns empty string.
  364. *
  365. * @param Entity\Base $entity Sets the context of processing.
  366. *
  367. * @return string
  368. */
  369. public function process(Entity\Base $entity)
  370. {
  371. $entityObject = $entity;
  372. $pathToField = explode(".", $this->entityField);
  373. for ($i = 0, $c = count($pathToField)-1; $i < $c; $i++)
  374. {
  375. $entityObject = $entityObject->resolve($pathToField[$i]);
  376. }
  377. if ($entityObject)
  378. return $entityObject->getField($pathToField[$c]);
  379. else
  380. return "";
  381. }
  382. }
  383. /**
  384. * Class NodeFunction
  385. * Represents a function call in the formula.
  386. *
  387. * @package Bitrix\Iblock\Template
  388. */
  389. class NodeFunction extends NodeBase
  390. {
  391. protected $functionName = "";
  392. protected $parameters = array();
  393. /**
  394. * Initialize function object.
  395. *
  396. * @param string $functionName Name of the function. Case insensitive.
  397. */
  398. public function __construct($functionName = "")
  399. {
  400. $this->functionName = mb_strtolower($functionName);
  401. }
  402. /**
  403. * Adds new parameters to the function call.
  404. *
  405. * @param NodeBase $parameter A new parameter to be added.
  406. *
  407. * @return void
  408. */
  409. public function addParameter(NodeBase $parameter)
  410. {
  411. $this->parameters[] = $parameter;
  412. }
  413. /**
  414. * Uses Functions\Fabric to get an instance of the function object by it's name.
  415. * On success calls onPrepareParameters, then calculate method.
  416. * Otherwise returns an empty string.
  417. *
  418. * @param Entity\Base $entity Sets the context of processing.
  419. *
  420. * @return string
  421. */
  422. public function process(Entity\Base $entity)
  423. {
  424. $functionObject = Functions\Fabric::createInstance($this->functionName);
  425. if ($functionObject instanceof Functions\FunctionBase)
  426. {
  427. $arguments = $functionObject->onPrepareParameters($entity, $this->parameters);
  428. return $functionObject->calculate($arguments);
  429. }
  430. else
  431. {
  432. return "";
  433. }
  434. }
  435. }