context.class.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832
  1. <?php
  2. /**
  3. * The context class file of zin of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
  6. * @author Hao Sun <sunhao@easycorp.ltd>
  7. * @package zin
  8. * @version $Id
  9. * @link https://www.zentao.net
  10. */
  11. namespace zin;
  12. use function zin\utils\flat;
  13. require_once dirname(__DIR__) . DS . 'utils' . DS . 'dataset.class.php';
  14. require_once dirname(__DIR__) . DS . 'utils' . DS . 'flat.func.php';
  15. require_once dirname(__DIR__) . DS . 'utils' . DS . 'deep.func.php';
  16. require_once __DIR__ . DS . 'helper.func.php';
  17. require_once __DIR__ . DS . 'render.class.php';
  18. require_once __DIR__ . DS . 'command.class.php';
  19. require_once __DIR__ . DS . 'js.class.php';
  20. class context extends \zin\utils\dataset
  21. {
  22. /**
  23. * @var string
  24. */
  25. public $name;
  26. /**
  27. * @var mixed[]
  28. */
  29. public $globalRenderList = array();
  30. /**
  31. * @var int
  32. */
  33. public $globalRenderLevel = 0;
  34. /**
  35. * @var mixed[]
  36. */
  37. public $data = array();
  38. /**
  39. * @var mixed[]
  40. */
  41. public $debugData = array();
  42. /**
  43. * @var \control|null
  44. */
  45. public $control;
  46. /**
  47. * @var bool
  48. */
  49. public $rendered = false;
  50. /**
  51. * @var bool
  52. */
  53. public $rawContentCalled = false;
  54. /**
  55. * @var bool
  56. */
  57. public $hookContentCalled = false;
  58. /**
  59. * @var mixed[]
  60. */
  61. public $beforeBuildNodeCallbacks = array();
  62. /**
  63. * @var mixed[]
  64. */
  65. public $onBuildNodeCallbacks = array();
  66. /**
  67. * @var mixed[]
  68. */
  69. public $onRenderNodeCallbacks = array();
  70. /**
  71. * @var mixed[]
  72. */
  73. public $onRenderCallbacks = array();
  74. /**
  75. * @var mixed[]
  76. */
  77. public $queries = array();
  78. /**
  79. * @var \zin\node|null
  80. */
  81. public $rootNode;
  82. /**
  83. * @var \zin\render|null
  84. */
  85. public $renderer;
  86. /**
  87. * @var mixed[]
  88. */
  89. public $pageJS = array();
  90. /**
  91. * @var mixed[]
  92. */
  93. public $pageCSS = array();
  94. /**
  95. * @var mixed[]
  96. */
  97. public $jsVars = array();
  98. /**
  99. * @var mixed[]
  100. */
  101. public $jsCalls = array();
  102. /**
  103. * @var mixed[]
  104. */
  105. public $wgRes = array();
  106. /**
  107. * @var mixed[]
  108. */
  109. public $eventBindings = array();
  110. /**
  111. * @var mixed[]
  112. */
  113. public $renderWgMap = array('page' => 'page', 'modal' => 'modalDialog', 'fragment' => 'fragment');
  114. /**
  115. * @var mixed[]
  116. */
  117. public $rawContentNames = array();
  118. /**
  119. * @param string $name
  120. */
  121. public function __construct($name)
  122. {
  123. parent::__construct();
  124. $this->name = $name;
  125. }
  126. public function __debugInfo()
  127. {
  128. return array_merge(array
  129. (
  130. 'name' => $this->name,
  131. 'globalRenderListLen' => count($this->globalRenderList),
  132. 'globalRenderList' => $this->globalRenderList,
  133. 'globalRenderLevel' => $this->globalRenderLevel,
  134. 'rendered' => $this->rendered,
  135. 'rawContentCalled' => $this->rawContentCalled,
  136. 'rawContentNames' => $this->rawContentNames
  137. ), $this->storedData);
  138. }
  139. /**
  140. * @param mixed $defaultValue
  141. * @return mixed
  142. * @param string $namePath
  143. */
  144. public function getData($namePath, $defaultValue = null)
  145. {
  146. return \zin\utils\deepGet($this->data, $namePath, $defaultValue);
  147. }
  148. /**
  149. * @param mixed $value
  150. * @param string $namePath
  151. */
  152. public function setData($namePath, $value)
  153. {
  154. \zin\utils\deepSet($this->data, $namePath, $value);
  155. }
  156. public function enableGlobalRender()
  157. {
  158. $this->globalRenderLevel--;
  159. }
  160. public function disableGlobalRender()
  161. {
  162. $this->globalRenderLevel++;
  163. }
  164. public function enabledGlobalRender()
  165. {
  166. return $this->globalRenderLevel < 1;
  167. }
  168. /**
  169. * @param \zin\node|\zin\iDirective $item
  170. */
  171. public function renderInGlobal($item)
  172. {
  173. if($this->globalRenderLevel > 0)
  174. {
  175. return false;
  176. }
  177. if($item instanceof node)
  178. {
  179. if($item->parent) return false;
  180. $type = $item->type();
  181. if($type === 'wg' || $type === 'node') return false;
  182. if(!isset($this->globalRenderList[$item->gid])) $this->globalRenderList[$item->gid] = $item;
  183. return true;
  184. }
  185. if(in_array($item, $this->globalRenderList)) return false;
  186. $this->globalRenderList[] = $item;
  187. return true;
  188. }
  189. /**
  190. * @param mixed $data
  191. */
  192. function skipRenderInGlobal($data)
  193. {
  194. if(is_array($data))
  195. {
  196. foreach($data as $item) skipRenderInGlobal($item);
  197. return;
  198. }
  199. if($data instanceof node || $data instanceof iDirective)
  200. {
  201. if(isset($data->gid)) unset($this->globalRenderList[$data->gid]);
  202. $data->notRenderInGlobal = true;
  203. }
  204. }
  205. /**
  206. * @param bool $clear
  207. */
  208. public function getGlobalRenderList($clear = true)
  209. {
  210. $globalItems = array();
  211. foreach($this->globalRenderList as $item)
  212. {
  213. if(is_object($item) && ((isset($item->parent) && $item->parent) || (isset($item->notRenderInGlobal) && $item->notRenderInGlobal)))
  214. {
  215. continue;
  216. }
  217. $globalItems[] = $item;
  218. }
  219. /* Clear globalRenderList. */
  220. if($clear) $this->globalRenderList = array();
  221. return $globalItems;
  222. }
  223. /**
  224. * @param string|mixed[] ...$files
  225. */
  226. public function addHookFiles(...$files)
  227. {
  228. $files = flat($files);
  229. return $this->mergeToList('hookFiles', array_filter(array_values($files)));
  230. }
  231. public function getHookFiles()
  232. {
  233. return $this->getList('hookFiles');
  234. }
  235. /**
  236. * @param string ...$files
  237. */
  238. public function addImports(...$files)
  239. {
  240. return $this->mergeToList('import', $files);
  241. }
  242. public function getImports()
  243. {
  244. return $this->getList('import');
  245. }
  246. /**
  247. * @param string|mixed[] $css
  248. * @param string|null $name
  249. */
  250. public function addCSS($css, $name = null)
  251. {
  252. if(is_array($css)) $css = implode("\n", $css);
  253. if($name)
  254. {
  255. if(isset($this->pageCSS[$name]))
  256. {
  257. if(isDebug()) triggerError("Page CSS name \"$name\" already exists.");
  258. return;
  259. }
  260. $this->pageCSS[$name] = $css;
  261. }
  262. else
  263. {
  264. $this->pageCSS[] = $css;
  265. }
  266. }
  267. public function getCSS()
  268. {
  269. $css = array();
  270. $wgRes = $this->wgRes;
  271. if($wgRes) foreach ($wgRes as $res) if($res['css']) $css[] = $res['css'];
  272. if($this->pageCSS) $css = array_merge($css, $this->pageCSS);
  273. return trim(implode("\n", $css));
  274. }
  275. /**
  276. * @param string|mixed[] $js
  277. * @param string|null $name
  278. */
  279. public function addJS($js, $name = null)
  280. {
  281. if(is_array($js)) $js = implode("\n", $js);
  282. if($name)
  283. {
  284. if(isset($this->pageJS[$name]))
  285. {
  286. if(isDebug()) triggerError("Page JS name \"$name\" already exists.");
  287. return;
  288. }
  289. $this->pageJS[$name] = $js;
  290. }
  291. else
  292. {
  293. $this->pageJS[] = $js;
  294. }
  295. }
  296. /**
  297. * @param mixed $value
  298. * @param string $name
  299. */
  300. public function addJSVar($name, $value)
  301. {
  302. $this->jsVars[$name] = $value;
  303. }
  304. public function addJSCall($func, $args)
  305. {
  306. $this->jsCalls[] = array($func, $args);
  307. }
  308. public function getJS()
  309. {
  310. $jsVars = $this->jsVars;
  311. $pageJS = $this->pageJS;
  312. $jsCalls = $this->jsCalls;
  313. $wgRes = $this->wgRes;
  314. $eventBindings = $this->eventBindings;
  315. $js = array();
  316. if($wgRes) foreach ($wgRes as $res) if($res['js']) $js[] = js::scope($res['js']);
  317. if($jsVars) foreach($jsVars as $name => $value) $js[] = js::defineVar($name, $value);
  318. if($pageJS) $js = array_merge($js, $pageJS);
  319. if($eventBindings) $js = array_merge($js, $eventBindings);
  320. if($jsCalls) foreach($jsCalls as $call) $js[] = js::defineJSCall($call[0], $call[1]);
  321. if(empty($js)) return '';
  322. $js = trim(implode("\n", $js));
  323. if(strpos($js, 'setTimeout') !== false) $js = 'function setTimeout(callback, time){return typeof window.registerTimer === "function" ? window.registerTimer(callback, time) : window.setTimeout(callback, time);}' . $js;
  324. if(strpos($js, 'setInterval') !== false) $js = 'function setInterval(callback, time){return typeof window.registerTimer === "function" ? window.registerTimer(callback, time, "interval") : window.setInterval(callback, time);}' . $js;
  325. $methods = array('onPageUnmount', 'beforePageUpdate', 'afterPageUpdate', 'onPageRender');
  326. foreach($methods as $method)
  327. {
  328. if(strpos($js, $method) === false) continue;
  329. $js .= "if(typeof $method === 'function') window.$method = $method;";
  330. }
  331. return $js;
  332. }
  333. /**
  334. * @param string $wgClass
  335. */
  336. public function addWgRes($wgClass)
  337. {
  338. if(isset($this->wgRes[$wgClass])) return;
  339. $res = array();
  340. $res['css'] = $wgClass::getPageCSS();
  341. $res['js'] = $wgClass::getPageJS();
  342. $this->wgRes[$wgClass] = $res;
  343. }
  344. /**
  345. * @param mixed ...$values
  346. * @param string $name
  347. */
  348. public function addDebugData($name, ...$values)
  349. {
  350. $e = new \Exception();
  351. $trace = $e->getTraceAsString();
  352. $trace = str_replace($this->control->app->basePath, '', $trace);
  353. $stack = explode("\n", $trace);
  354. if(str_contains($stack[0], 'lib/zin/core/context.func.php')) array_shift($stack);
  355. $finalName = $name;
  356. if(empty($finalName))
  357. {
  358. $statement = $stack[0];
  359. if(str_contains($stack[0], '): zin\d('))
  360. {
  361. $statement = explode('): zin\d(', $statement)[1];
  362. if(str_contains($statement, ',')) $finalName = explode(',', $statement)[0];
  363. else $finalName = explode(')', $statement)[0];
  364. }
  365. else
  366. {
  367. $finalName = 'dump';
  368. }
  369. }
  370. $isJson = !str_starts_with($name, '$');
  371. $data = $values;
  372. if($isJson)
  373. {
  374. $data = json_encode($values);
  375. if($data === false) $isJson = false;
  376. else $data = jsRaw($data);
  377. }
  378. if(!$isJson) $data = array_map(function($value) {return var_export($value, true);}, $values);
  379. $this->debugData[] = array('name' => $finalName, 'data' => $data, 'type' => $isJson ? 'json' : 'var', 'trace' => $stack);
  380. }
  381. public function getDebugData()
  382. {
  383. global $app;
  384. $zinDebug = null;
  385. if(isDebug() && (!isAjaxRequest() || isAjaxRequest('zin')))
  386. {
  387. $zinDebug = data('zinDebug');
  388. if(is_array($zinDebug))
  389. {
  390. $zinDebug['basePath'] = $app->getBasePath();
  391. $zinDebug['debug'] = $this->debugData;
  392. if(isset($app->zinErrors)) $zinDebug['errors'] = $app->zinErrors;
  393. }
  394. }
  395. return $zinDebug;
  396. }
  397. public function getRawContent()
  398. {
  399. $rawContent = ob_get_contents();
  400. if(!is_string($rawContent)) $rawContent = '';
  401. if(!empty(ob_get_status(true))) ob_end_clean();
  402. return $rawContent;
  403. }
  404. /**
  405. * @param \zin\query $query
  406. */
  407. public function addQuery($query)
  408. {
  409. $this->queries[] = $query;
  410. }
  411. /**
  412. * Include hooks files.
  413. */
  414. public function includeHooks()
  415. {
  416. $hookFiles = $this->getHookFiles();
  417. ob_start();
  418. foreach($hookFiles as $hookFile)
  419. {
  420. if(!empty($hookFile) && file_exists($hookFile)) include $hookFile;
  421. }
  422. $hookCode = ob_get_clean();
  423. ob_end_flush();
  424. if($hookCode) return $hookCode;
  425. return '';
  426. }
  427. /**
  428. * @param null|object|string|mixed[] $selectors
  429. * @return string|mixed[]|object
  430. * @param \zin\node $node
  431. * @param string $renderType
  432. * @param bool $renderInner
  433. */
  434. public function render($node, $selectors = null, $renderType = 'html', $renderInner = false)
  435. {
  436. $this->disableGlobalRender();
  437. $renderer = new render($node, $selectors, $renderType, $renderInner);
  438. $this->rendered = true;
  439. $this->renderer = $renderer;
  440. $this->rootNode = $node;
  441. $hookCode = $this->includeHooks();
  442. $rawContent = $this->getRawContent();
  443. $node->prebuild(true);
  444. $this->applyQueries($node);
  445. $result = $renderer->render();
  446. if(is_object($result)) // renderType = json
  447. {
  448. $zinDebug = $this->getDebugData();
  449. if($zinDebug && isset($result->zinDebug)) $result->zinDebug = $zinDebug;
  450. $result = json_encode($result, JSON_PARTIAL_OUTPUT_ON_ERROR);
  451. }
  452. else
  453. {
  454. $js = $this->getJS();
  455. $css = $this->getCSS();
  456. $replace = array('<!-- {{HOOK_CONTENT}} -->' => $hookCode, '/*{{ZIN_PAGE_CSS}}*/' => $css, '/*{{ZIN_PAGE_JS}}*/' => $js);
  457. /* 如果存在 rawContentNames,则将 rawContent 中的内容块替换为对应名称的内容块。 */
  458. /* If rawContentNames exists, replace the content block in rawContent with the content block of the corresponding name. */
  459. if($this->rawContentNames)
  460. {
  461. $rawContentMap = parseRawContent($rawContent);
  462. $replace['<!-- {{RAW_CONTENT}} -->'] = $rawContentMap['GLOBAL'];
  463. $replace['<!-- {{RAW_CONTENT:ALL}} -->'] = $rawContent;
  464. foreach ($this->rawContentNames as $name => $_)
  465. {
  466. if(!isset($rawContentMap[$name]))
  467. {
  468. if(isDebug()) triggerError("The content of rawContent(\"$name\") not found.");
  469. continue;
  470. }
  471. $replace["<!-- {{RAW_CONTENT:{$name}}} -->"] = $rawContentMap[$name];
  472. }
  473. }
  474. else
  475. {
  476. $replace['<!-- {{RAW_CONTENT}} -->'] = $rawContent;
  477. }
  478. $zinDebug = $this->getDebugData();
  479. if(is_array($result)) // renderType = list
  480. {
  481. foreach($result as $index => $item)
  482. {
  483. if($item->name === 'zinDebug' && $zinDebug)
  484. {
  485. $result[$index] = array('zinDebug:<BEGIN>', $zinDebug);
  486. continue;
  487. }
  488. if($item->name === 'hookCode')
  489. {
  490. $item->data = $hookCode;
  491. continue;
  492. }
  493. if(!isset($item->type) || $item->type !== 'html') continue;
  494. $item->data = str_replace(array_keys($replace), array_values($replace), $item->data);
  495. }
  496. $result = json_encode($result, JSON_PARTIAL_OUTPUT_ON_ERROR);
  497. }
  498. else // renderType = html
  499. {
  500. if($zinDebug) $js .= js::defineVar('window.zinDebug', $zinDebug);
  501. $result = str_replace(array_keys($replace), array_values($replace), $result);
  502. }
  503. }
  504. $data = new stdClass();
  505. $data->type = $renderType;
  506. $data->output = $result;
  507. foreach($this->onRenderCallbacks as $callback)
  508. {
  509. if($callback instanceof \Closure) $callback($data, $node);
  510. else call_user_func($callback, $data, $node);
  511. }
  512. $this->enabledGlobalRender();
  513. return $data->output;
  514. }
  515. /**
  516. * @param \zin\node $rootNode
  517. */
  518. protected function applyQueries($rootNode)
  519. {
  520. if(!$this->queries) return;
  521. foreach($this->queries as $query)
  522. {
  523. $this->applyQuery($rootNode, $query);
  524. }
  525. }
  526. /**
  527. * @param \zin\node $rootNode
  528. * @param \zin\query $query
  529. */
  530. protected function applyQuery($rootNode, $query)
  531. {
  532. $nodes = $query->isRoot() ? array($rootNode) : findInNode($query->selectors, $rootNode, false, false, false);
  533. if(!$nodes) return;
  534. $queryNodes = $nodes;
  535. foreach($query->commands as $command)
  536. {
  537. list($method, $args) = $command;
  538. $result = call_user_func("\zin\command::{$method}", $queryNodes, ...$args);
  539. if(is_array($result)) $queryNodes = $result;
  540. if(empty($queryNodes)) break;
  541. prebuild($queryNodes);
  542. }
  543. }
  544. /**
  545. * @param \zin\node $node
  546. */
  547. public function handleBeforeBuildNode($node)
  548. {
  549. foreach($this->beforeBuildNodeCallbacks as $callback)
  550. {
  551. if($callback instanceof \Closure) $callback($node);
  552. else call_user_func($callback, $node);
  553. }
  554. }
  555. /**
  556. * @param \zin\stdClass $data
  557. * @param \zin\node $node
  558. */
  559. public function handleBuildNode(&$data, $node)
  560. {
  561. foreach($this->onBuildNodeCallbacks as $callback)
  562. {
  563. if($callback instanceof \Closure) $callback($data, $node);
  564. else call_user_func($callback, $data, $node);
  565. }
  566. if($node instanceof wg)
  567. {
  568. $class = get_class($node);
  569. if(!isset($this->wgRes[$class]))
  570. {
  571. $res = array();
  572. $res['css'] = $class::getPageCSS();
  573. $res['js'] = $class::getPageJS();
  574. $this->wgRes[$class] = $res;
  575. }
  576. }
  577. if($this->renderer) $this->renderer->handleBuildNode($data, $node);
  578. $eventBinding = $node->buildEvents();
  579. if($eventBinding) $this->eventBindings[] = $eventBinding;
  580. }
  581. /**
  582. * @param \zin\stdClass $data
  583. * @param \zin\node $node
  584. */
  585. public function handleRenderNode(&$data, $node)
  586. {
  587. foreach($this->onRenderNodeCallbacks as $callback)
  588. {
  589. if($callback instanceof \Closure) $callback($data, $node);
  590. else call_user_func($callback, $data, $node);
  591. }
  592. }
  593. /**
  594. * @param callable|\Closure $callback
  595. */
  596. public function onBuildNode($callback)
  597. {
  598. $this->onBuildNodeCallbacks[] = $callback;
  599. }
  600. /**
  601. * @param callable|\Closure $callback
  602. */
  603. public function onRenderNode($callback)
  604. {
  605. $this->onRenderNodeCallbacks[] = $callback;
  606. }
  607. /**
  608. * @param callable|\Closure $callback
  609. */
  610. public function onBeforeBuildNode($callback)
  611. {
  612. $this->beforeBuildNodeCallbacks[] = $callback;
  613. }
  614. /**
  615. * @param callable|\Closure $callback
  616. */
  617. public function onRender($callback)
  618. {
  619. $this->onRenderCallbacks[] = $callback;
  620. }
  621. /**
  622. * @param string|mixed[] $mapOrName
  623. * @param string|null $wgName
  624. */
  625. public function setRenderWgMap($mapOrName, $wgName = null)
  626. {
  627. if(is_array($mapOrName))
  628. {
  629. $this->renderWgMap = array_merge($this->renderWgMap, $mapOrName);
  630. }
  631. else
  632. {
  633. $this->renderWgMap[$mapOrName] = $wgName;
  634. }
  635. }
  636. public function getRenderWgName()
  637. {
  638. if(isset($this->renderWgMap['all'])) return $this->renderWgMap['all'];
  639. if(isAjaxRequest('modal')) return $this->renderWgMap['modal'];
  640. if(isAjaxRequest() && !isAjaxRequest('zin')) return $this->renderWgMap['fragment'];
  641. return 'page';
  642. }
  643. /**
  644. * @var mixed[]
  645. */
  646. public static $stack = array();
  647. /**
  648. * @param string ...$code
  649. */
  650. public static function js(...$code)
  651. {
  652. static::current()->addJS(flat($code));
  653. }
  654. /**
  655. * @param mixed ...$args
  656. * @param string $func
  657. */
  658. public static function jsCall($func, ...$args)
  659. {
  660. static::current()->addJSCall($func, $args);
  661. }
  662. public static function jsVar($name, $value)
  663. {
  664. static::current()->addJSVar($name, $value);
  665. }
  666. /**
  667. * @param string ...$code
  668. */
  669. public static function css(...$code)
  670. {
  671. static::current()->addCSS(flat($code));
  672. }
  673. /**
  674. * @param string ...$files
  675. */
  676. public static function import(...$files)
  677. {
  678. static::current()->addImports(...$files);
  679. }
  680. /**
  681. * Get current context.
  682. *
  683. * @access public
  684. * @return context
  685. */
  686. public static function current()
  687. {
  688. if(empty(static::$stack))
  689. {
  690. $context = new context('default');
  691. static::$stack['default'] = $context;
  692. return $context;
  693. }
  694. return end(static::$stack);
  695. }
  696. /**
  697. * Create context.
  698. *
  699. * @access public
  700. * @param string $name Context name.
  701. * @return context
  702. */
  703. public static function create($name)
  704. {
  705. if(isset(static::$stack[$name]))
  706. {
  707. triggerError("Context name \"$name\" already exists.");
  708. }
  709. $context = new context($name);
  710. static::$stack[$name] = $context;
  711. return $context;
  712. }
  713. /**
  714. * Pop last context.
  715. *
  716. * @access public
  717. * @return ?context
  718. */
  719. public static function pop()
  720. {
  721. return array_pop(static::$stack);
  722. }
  723. }