PageRenderTime 47ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/pagesystem/Page.php

https://github.com/jcorbinredtree/framework
PHP | 565 lines | 271 code | 45 blank | 249 comment | 38 complexity | 5b053bb739869c42468ce9343735f627 MD5 | raw file
Possible License(s): LGPL-2.1, MPL-2.0-no-copyleft-exception
  1. <?php
  2. /**
  3. * Page definition
  4. *
  5. * PHP version 5
  6. *
  7. * LICENSE: The contents of this file are subject to the Mozilla Public License Version 1.1
  8. * (the "License"); you may not use this file except in compliance with the
  9. * License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
  10. *
  11. * Software distributed under the License is distributed on an "AS IS" basis,
  12. * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
  13. * the specific language governing rights and limitations under the License.
  14. *
  15. * The Original Code is Red Tree Systems Code.
  16. *
  17. * The Initial Developer of the Original Code is Red Tree Systems, LLC. All Rights Reserved.
  18. *
  19. * @author Red Tree Systems, LLC <support@redtreesystems.com>
  20. * @copyright 2009 Red Tree Systems, LLC
  21. * @license MPL 1.1
  22. * @version 2.0
  23. * @link http://framework.redtreesystems.com
  24. */
  25. require_once 'lib/util/CallbackManager.php';
  26. require_once dirname(__FILE__).'/PageHeaders.php';
  27. /**
  28. * Describes a page in a site (amazing)
  29. *
  30. * Basic page, manages things such as page "data" which is named arbitrary
  31. * mixed values and "buffers", which is a primitive form of page content on the
  32. * way out to the client.
  33. *
  34. * @package Site
  35. */
  36. class Page extends CallbackManager
  37. {
  38. /**
  39. * Outgoing HTTP Header interface
  40. *
  41. * @var PageHeaders
  42. */
  43. public $headers;
  44. /**
  45. * The mime type of the page
  46. *
  47. * @var string
  48. */
  49. protected $type;
  50. /**
  51. * The template resource sting to use to render this page
  52. *
  53. * The default implementation defaults to "page/type" where the "type"
  54. * portion is the $type property with all '/'s replaced with '_'s.
  55. *
  56. * This can be null if a subclass does not wish to use a template-based
  57. * approach
  58. *
  59. * @var string
  60. * @see onRender
  61. */
  62. protected $template;
  63. /**
  64. * Named page content buffers such as 'head', 'top', 'left', etc.
  65. */
  66. private $buffers;
  67. /**
  68. * The buffer currently being rendered
  69. */
  70. private $renderingBuffer = null;
  71. /**
  72. * Arbitrary page data by group name such as 'breadCrumbs' or
  73. * 'topNavigation'.
  74. *
  75. * The distinction between buffers and data is that buffers are clearly
  76. * defined as equivilent to string data and rendered at the end of the
  77. * request; however data can be absolutely anything.
  78. */
  79. private $data;
  80. /**
  81. * @var Site
  82. */
  83. protected $site;
  84. /**
  85. * Constructor
  86. *
  87. * Creates a new Page.
  88. *
  89. * @param site Site
  90. * @param type string the type of the page, defaults to 'text/plain'
  91. * @param template the template resource string used to render this page
  92. * if set to the false value, then the $template property will not be set,
  93. * either way the resulting template value will then be appended to the
  94. * PageSystem module prefix
  95. * @see $template
  96. */
  97. public function __construct(Site $site, $type='text/plain', $template=null)
  98. {
  99. $this->site = $site;
  100. $this->buffers = array();
  101. $this->data = array();
  102. $this->type = $type;
  103. if (isset($template)) {
  104. if ($template === false) {
  105. $this->template = null;
  106. } else {
  107. $this->template = $template;
  108. }
  109. } else {
  110. $this->template = sprintf('page/%s',
  111. preg_replace('/\//', '_', $this->type)
  112. );
  113. }
  114. $this->template =
  115. $site->modules->getModulePrefix('PageSystem').
  116. '/'.
  117. $this->template;
  118. $this->headers = new PageHeaders();
  119. $this->headers->setContentType($this->type);
  120. }
  121. public function getSite()
  122. {
  123. return $this->site;
  124. }
  125. /**
  126. * Gets the mime type of the page
  127. *
  128. * @return string
  129. */
  130. public function getType()
  131. {
  132. return $this->type;
  133. }
  134. /**
  135. * Tests whether the given mime type is compatible with this page
  136. *
  137. * @param t string
  138. * @return boolean
  139. */
  140. public function compatibleType($t)
  141. {
  142. return $t == $this->type;
  143. }
  144. /**
  145. * Adds an item to the named buffer
  146. *
  147. * @param name the name of the buffer, e.g. head, top, left, etc
  148. * @param content mixed a renderable item
  149. *
  150. * @return void
  151. *
  152. * @see renderBufferedItem
  153. */
  154. public function addToBuffer($name, &$content)
  155. {
  156. if (isset($this->renderingBuffer) && $this->renderingBuffer == $name) {
  157. throw new RuntimeException(
  158. "Cannot add to page buffer $name while rendering it"
  159. );
  160. }
  161. if ($content === null) {
  162. return;
  163. }
  164. if (! array_key_exists($name, $this->buffers)) {
  165. $this->buffers[$name] = array();
  166. }
  167. array_push($this->buffers[$name], $content);
  168. }
  169. /**
  170. * Tests whether the given buffer has any content
  171. *
  172. * @param name the name of the buffer, e.g. head, top, left, etc
  173. * @return boolean
  174. */
  175. public function hasBuffer($name)
  176. {
  177. assert(is_string($name));
  178. if (array_key_exists($name, $this->buffers)) {
  179. return count($this->buffers[$name]) != 0;
  180. } else {
  181. return false;
  182. }
  183. }
  184. /**
  185. * Renders and returns the named buffer
  186. *
  187. * @param name the name of the buffer, e.g. head, top, left, etc
  188. * @param asArray boolean if true, an array is returned containing each
  189. * item from the buffer rendered, otherwise the concatenation of this array
  190. * is returned.
  191. * @param flush boolean, if true call clearBuffer right before returning
  192. *
  193. * @return string or array if name exists, null otherwise
  194. *
  195. * @see renderBufferedItem
  196. */
  197. public function getBuffer($name, $asArray=false, $flush=false)
  198. {
  199. if (isset($this->renderingBuffer)) {
  200. throw new RuntimeException(
  201. 'Page->getBuffer called recursively '.
  202. "$name inside of $this->renderingBuffer"
  203. );
  204. }
  205. if (! array_key_exists($name, $this->buffers)) {
  206. return null;
  207. }
  208. $this->renderingBuffer = $name;
  209. if ($asArray) {
  210. $ret = array();
  211. foreach ($this->buffers[$name] as &$item) {
  212. array_push($ret,
  213. $this->renderBufferedItem($item)
  214. );
  215. }
  216. } else {
  217. $ret = $this->renderBufferedItem(
  218. $this->buffers[$name], array($name)
  219. );
  220. }
  221. $this->renderingBuffer = null;
  222. if ($flush) {
  223. $this->clearBuffer($name);
  224. }
  225. return $ret;
  226. }
  227. /**
  228. * Emptys the named buffer.
  229. * Doesn't return anything, call getBuffer first if you want that
  230. *
  231. * @param name string
  232. * @return void
  233. */
  234. public function clearBuffer($name)
  235. {
  236. if (array_key_exists($name, $this->buffers)) {
  237. $this->buffers[$name] = array();
  238. }
  239. }
  240. /**
  241. * Calls processBuffer on each defined buffer
  242. *
  243. * @return void
  244. */
  245. public function processBuffers()
  246. {
  247. foreach (array_keys($this->buffers) as $name) {
  248. $this->processBuffer($name);
  249. }
  250. }
  251. /**
  252. * Processes the named buffer
  253. *
  254. * This turns each item in the buffer into its string representation and
  255. * throws away the old non-string data. The buffer still remains as an array
  256. * of items, but each item will now be just a string.
  257. *
  258. * @param name string the buffer
  259. *
  260. * @return void
  261. */
  262. public function processBuffer($name)
  263. {
  264. if (! array_key_exists($name, $this->buffers)) {
  265. throw new InvalidArgumentException('Invaild buffer name');
  266. }
  267. $this->buffers[$name] = $this->getBuffer($name, true);
  268. }
  269. /**
  270. * Renders a buffered item
  271. *
  272. * @param mixed the item, can be any of:
  273. * - a string
  274. * - an object derived from BufferedObject, call getBuffer and concatenate
  275. * - an object that implements __tostring, convert to string and concatenate
  276. * - any other object, a string "[Objcet of type CLASS]"
  277. * - an array, each array element will be passed through renderBufferedItem
  278. * and concatenated.
  279. * - a callable, call the callable, passing it args as arguments and then
  280. * pass its return through renderBUfferedItem.
  281. *
  282. * @return string
  283. */
  284. private function renderBufferedItem(&$item, $args=array())
  285. {
  286. try {
  287. if (is_array($item)) {
  288. $r = '';
  289. foreach ($item as &$i) {
  290. $r .= $this->renderBufferedItem($i, $args);
  291. }
  292. return $r;
  293. } elseif (is_object($item)) {
  294. if ($item instanceof BufferedObject) {
  295. return $item->getBuffer();
  296. } elseif ($item instanceof PHPSTLTemplate) {
  297. return $this->renderTemplate($item);
  298. } elseif (method_exists($item, '__tostring')) {
  299. return (string) $item;
  300. } else {
  301. return "[Object of type ".get_class($item)."]";
  302. }
  303. } elseif (is_callable($item)) {
  304. ob_start();
  305. $ret = call_user_func_array($item, $args);
  306. $l = ob_get_length();
  307. $mess = '';
  308. if ($l !== false) {
  309. if ($l > 0) {
  310. $mess = ob_get_contents();
  311. }
  312. ob_end_clean();
  313. }
  314. if (! is_string($ret) || is_callable($ret)) {
  315. $ret = $this->renderBufferedItem($ret, $args);
  316. }
  317. return $ret.$mess;
  318. } else {
  319. return $item;
  320. }
  321. } catch (Exception $e) {
  322. return $this->handleBufferedItemException(
  323. $this->renderingBuffer, $item, $e
  324. );
  325. };
  326. }
  327. /**
  328. * Called by renderBufferedItem if processing an item rasises an exception.
  329. *
  330. * Subclasses should return a string to represent the exception in the page,
  331. * or if the page wants to be intolerant, a subclass can opt to simply throw
  332. * the exception.
  333. *
  334. * The default implementation renders the exception as text only.
  335. *
  336. * @param buffer string the buffer the item belongs to
  337. * @param item mixed the item that failed
  338. * @param Exception e raised by the item
  339. * @return string
  340. */
  341. protected function handleBufferedItemException($buffer, $item, Exception $e)
  342. {
  343. $mess = "Error while processing buffer '$buffer' item:\n";
  344. foreach (explode("\n", (string) $e) as $line) {
  345. $mess .= " $line\n";
  346. }
  347. return $mess;
  348. }
  349. /**
  350. * Sets a data item
  351. *
  352. * This implements a singular data item, see addData if the item should
  353. * be an array.
  354. *
  355. * @param name string
  356. * @param value string if null, unsets the item
  357. * @return void
  358. */
  359. public function setData($name, $value)
  360. {
  361. if (isset($value)) {
  362. $this->data[$name] = $value;
  363. } elseif (array_key_exists($name, $this->data)) {
  364. unset($this->data[$name]);
  365. }
  366. }
  367. /**
  368. * Sets multiple data items on one go
  369. *
  370. * @param data array
  371. * @return void
  372. */
  373. public function setDataArray($data)
  374. {
  375. foreach ($data as $n => &$v) {
  376. $this->setData($n, $v);
  377. }
  378. }
  379. /**
  380. * Adds a data item to the page
  381. * Similar in spirit to addToBuffer but with less semantics
  382. *
  383. * @param name string
  384. * @param item mixed
  385. * @return void
  386. */
  387. public function addData($name, &$item)
  388. {
  389. if ($item === null) {
  390. return;
  391. }
  392. if (! array_key_exists($name, $this->data)) {
  393. $this->data[$name] = array();
  394. }
  395. array_push($this->data[$name], $item);
  396. }
  397. /**
  398. * Tests whether the named data item exists
  399. *
  400. * @param name string
  401. * @return boolean true if a call to getData($name) would return non-null
  402. */
  403. public function hasData($name)
  404. {
  405. return array_key_exists($name, $this->data);
  406. }
  407. /**
  408. * Returns the named data item
  409. *
  410. * @param name string
  411. * @return mixed
  412. * @see addData, setData
  413. */
  414. public function getData($name, $default=null)
  415. {
  416. if (
  417. array_key_exists($name, $this->data) &&
  418. isset($this->data[$name])
  419. ) {
  420. return $this->data[$name];
  421. } else {
  422. return $default;
  423. }
  424. }
  425. /**
  426. * Clears the named data array
  427. *
  428. * @param name string
  429. * @return void
  430. */
  431. public function clearData($name)
  432. {
  433. if (array_key_exists($name, $this->data)) {
  434. unset($this->data[$name]);
  435. }
  436. }
  437. public function addWarning($warning)
  438. {
  439. $this->addData('warnings', $warning);
  440. }
  441. public function addNotice($notice)
  442. {
  443. $this->addData('notices', $notice);
  444. }
  445. public function getWarnings()
  446. {
  447. return $this->getData('warnings', array());
  448. }
  449. public function getNotices()
  450. {
  451. return $this->getData('notices', array());
  452. }
  453. public function clearWarnings()
  454. {
  455. $this->cleartData('warnings');
  456. }
  457. public function clearNotices()
  458. {
  459. $this->cleartData('notices');
  460. }
  461. /**
  462. * Renders this page
  463. *
  464. * All buffers are finalized using processBuffers directly before calling
  465. * onRender
  466. *
  467. * dispatches two callbacks: prerender and postrender
  468. *
  469. * @param Site $site
  470. * @see processBuffers
  471. * @return void
  472. */
  473. final public function render()
  474. {
  475. $this->dispatchCallback('prerender', $this);
  476. $this->processBuffers();
  477. $output = $this->onRender();
  478. $this->headers->send();
  479. print $output;
  480. $this->dispatchCallback('postrender', $this);
  481. }
  482. /**
  483. * Generates page output
  484. *
  485. * The default implementation attempts to load the $template property
  486. * through the TemplateSystem and render through it, or if the $template
  487. * property is not set, simply returns the 'content' buffer.
  488. *
  489. * @return string
  490. * @see $template, renderTemplate
  491. */
  492. protected function onRender()
  493. {
  494. if (isset($this->template)) {
  495. $tsys = $this->site->modules->get('TemplateSystem');
  496. return $this->renderTemplate($tsys->load($this->template));
  497. } else {
  498. return $this->getBuffer('content');
  499. }
  500. }
  501. /**
  502. * Renders a template with special argument semantics
  503. *
  504. * The template is rendered with arguments set from the $data field merged
  505. * with array('page' => $this)
  506. *
  507. * @return array
  508. */
  509. protected function renderTemplate(PHPSTLTemplate &$template)
  510. {
  511. $prefix = $this->site->modules->getModulePrefix('PageSystem');
  512. return $template->render(array_merge(
  513. $this->data, array(
  514. 'pageSystemPrefix' => $prefix,
  515. 'page' => $this
  516. )
  517. ));
  518. }
  519. }
  520. ?>