PageRenderTime 24ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/App/Web.php

http://github.com/atk4/atk4
PHP | 655 lines | 468 code | 48 blank | 139 comment | 30 complexity | 975ecbfad215fc9ca3b82fc42ee5ebfa MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception
  1. <?php
  2. /**
  3. * App_Web extends an APP of CommandLine applications with knowledge of HTML
  4. * templates, understanding of pages and routing.
  5. */
  6. class App_Web extends App_CLI
  7. {
  8. /**
  9. * Cleaned up name of the currently requested page.
  10. */
  11. public $page = null;
  12. /**
  13. * Root page where URL will send when ('/') is encountered.
  14. *
  15. * @todo: make this work properly
  16. */
  17. public $index_page = 'index';
  18. /**
  19. * Recorded time when execution has started.
  20. */
  21. public $start_time = null;
  22. /**
  23. * Skin for web application templates.
  24. */
  25. public $skin;
  26. /**
  27. * Set a title of your application, will appear in <title> tag.
  28. */
  29. public $title;
  30. /**
  31. * Authentication object
  32. *
  33. * @see Auth_Basic::init()
  34. * @var Auth_Basic
  35. */
  36. public $auth;
  37. /**
  38. * jQuery object. Initializes only if you add jQuery in your app.
  39. *
  40. * @see jQuery::init()
  41. * @var jQuery
  42. */
  43. public $jquery;
  44. /**
  45. * jQuery UI object. Initializes only if you add jQuery UI in your app.
  46. *
  47. * @see jUI::init()
  48. * @var jUI
  49. */
  50. public $jui;
  51. /** @var App_Web */
  52. public $app;
  53. /** @var array For internal use */
  54. protected $included;
  55. /** @var array For internal use */
  56. protected $rendered;
  57. // {{{ Start-up
  58. public function __construct($realm = null, $skin = 'default', $options = array())
  59. {
  60. $this->start_time = time() + preg_replace('/ .*/', '', microtime());
  61. $this->skin = $skin;
  62. try {
  63. parent::__construct($realm, $options);
  64. } catch (Exception $e) {
  65. // This exception is used to abort initialisation of the objects,
  66. // but when normal rendering is still required
  67. if ($e instanceof Exception_Stop) {
  68. return;
  69. }
  70. $this->caughtException($e);
  71. }
  72. }
  73. /**
  74. * Executed before init, this method will initialize PageManager and
  75. * pathfinder.
  76. */
  77. public function _beforeInit()
  78. {
  79. $this->pm = $this->add($this->pagemanager_class, $this->pagemanager_options);
  80. /** @type Controller_PageManager $this->pm */
  81. $this->pm->parseRequestedURL();
  82. parent::_beforeInit();
  83. }
  84. /**
  85. * Redefine this function instead of default constructor.
  86. */
  87. public function init()
  88. {
  89. $this->getLogger();
  90. // Verify Licensing
  91. //$this->licenseCheck('atk4');
  92. // send headers, no caching
  93. $this->sendHeaders();
  94. $this->cleanMagicQuotes();
  95. parent::init();
  96. // in addition to default initialization, set up logger and template
  97. $this->initializeTemplate();
  98. if (get_class($this) == 'App_Web') {
  99. $this->setConfig(array('url_postfix' => '.php', 'url_prefix' => ''));
  100. }
  101. }
  102. /**
  103. * Magic Quotes were a design error. Let's strip them if they are enabled.
  104. */
  105. public function cleanMagicQuotes()
  106. {
  107. if (!function_exists('stripslashes_array')) {
  108. function stripslashes_array(&$array, $iterations = 0)
  109. {
  110. if ($iterations < 3) {
  111. foreach ($array as $key => $value) {
  112. if (is_array($value)) {
  113. stripslashes_array($array[$key], $iterations + 1);
  114. } else {
  115. $array[$key] = stripslashes($array[$key]);
  116. }
  117. }
  118. }
  119. }
  120. }
  121. if (get_magic_quotes_gpc()) {
  122. stripslashes_array($_GET);
  123. stripslashes_array($_POST);
  124. stripslashes_array($_COOKIE);
  125. }
  126. }
  127. /**
  128. * Sends default headers. Re-define to send your own headers.
  129. */
  130. public function sendHeaders()
  131. {
  132. header('Content-Type: text/html; charset=utf-8');
  133. header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past
  134. header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT'); // always modified
  135. header('Cache-Control: no-store, no-cache, must-revalidate'); // HTTP/1.1
  136. header('Cache-Control: post-check=0, pre-check=0', false);
  137. header('Pragma: no-cache'); // HTTP/1.0
  138. }
  139. /**
  140. * Call this method if you want to see execution time on the bottom of your pages.
  141. */
  142. public function showExecutionTime()
  143. {
  144. $this->addHook('post-render-output', array($this, '_showExecutionTime'));
  145. $this->addHook('post-js-execute', array($this, '_showExecutionTimeJS'));
  146. }
  147. /** @ignore */
  148. public function _showExecutionTime()
  149. {
  150. echo 'Took '.(time() + microtime() - $this->start_time).'s';
  151. }
  152. /** @ignore */
  153. public function _showExecutionTimeJS()
  154. {
  155. echo "\n\n/* Took ".number_format(time() + microtime() - $this->start_time, 5).'s */';
  156. }
  157. // }}}
  158. // {{{ Obsolete
  159. /**
  160. * This method is called when exception was caught in the application.
  161. *
  162. * @param Exception $e
  163. */
  164. public function caughtException($e)
  165. {
  166. $this->hook('caught-exception', array($e));
  167. throw $e;
  168. /* unreachable code
  169. echo '<span style="color:red">Problem with your request.</span>';
  170. echo "<p>Please use 'Logger' class for more sophisticated output<br>\$app-&gt;add('Logger');</p>";
  171. exit;
  172. */
  173. }
  174. /**
  175. * @todo Description
  176. *
  177. * @param string $msg
  178. * @param int $shift
  179. *
  180. * @return bool|void
  181. */
  182. public function outputWarning($msg, $shift = 0)
  183. {
  184. if ($this->hook('output-warning', array($msg, $shift))) {
  185. return true;
  186. }
  187. echo '<span style="color:red">', $msg, '</span>';
  188. }
  189. /**
  190. * @todo Description
  191. *
  192. * @param string $msg
  193. * @param int $shift
  194. *
  195. * @return bool|void
  196. */
  197. public function outputDebug($msg, $shift = 0)
  198. {
  199. if ($this->hook('output-debug', array($msg, $shift))) {
  200. return true;
  201. }
  202. echo '<span style="color:blue">', $msg, '</font><br />';
  203. }
  204. // }}}
  205. // {{{ Sessions
  206. /**
  207. * Initializes existing or new session.
  208. *
  209. * Attempts to re-initialize session. If session is not found,
  210. * new one will be created, unless $create is set to false. Avoiding
  211. * session creation and placing cookies is to enhance user privacy.
  212. * Call to memorize() / recall() will automatically create session
  213. *
  214. * @param bool $create
  215. */
  216. public $_is_session_initialized = false;
  217. public function initializeSession($create = true)
  218. {
  219. if ($this->_is_session_initialized || session_id()) {
  220. return;
  221. }
  222. // Change settings if defined in settings file
  223. $params = session_get_cookie_params();
  224. $params['httponly'] = true; // true by default
  225. foreach ($params as $key => $default) {
  226. $params[$key] = $this->app->getConfig('session/'.$key, $default);
  227. }
  228. if ($create === false && !isset($_COOKIE[$this->name])) {
  229. return;
  230. }
  231. $this->_is_session_initialized = true;
  232. session_set_cookie_params(
  233. $params['lifetime'],
  234. $params['path'],
  235. $params['domain'],
  236. $params['secure'],
  237. $params['httponly']
  238. );
  239. session_name($this->name);
  240. session_start();
  241. }
  242. /**
  243. * Completely destroy existing session.
  244. */
  245. public function destroySession()
  246. {
  247. if ($this->_is_session_initialized) {
  248. $_SESSION = array();
  249. if (isset($_COOKIE[$this->name])) {
  250. setcookie($this->name/*session_name()*/, '', time() - 42000, '/');
  251. }
  252. session_destroy();
  253. $this->_is_session_initialized = false;
  254. }
  255. }
  256. // }}}
  257. // {{{ Sticky GET Argument implementation. Register stickyGET to have it appended to all generated URLs
  258. public $sticky_get_arguments = array();
  259. /**
  260. * Make current get argument with specified name automatically appended to all generated URLs.
  261. *
  262. * @param string $name
  263. *
  264. * @return string
  265. */
  266. public function stickyGet($name)
  267. {
  268. $this->sticky_get_arguments[$name] = @$_GET[$name];
  269. return $_GET[$name];
  270. }
  271. /**
  272. * Remove sticky GET which was set by stickyGET.
  273. *
  274. * @param string $name
  275. */
  276. public function stickyForget($name)
  277. {
  278. unset($this->sticky_get_arguments[$name]);
  279. }
  280. /** @ignore - used by URL class */
  281. public function getStickyArguments()
  282. {
  283. return $this->sticky_get_arguments;
  284. }
  285. /**
  286. * @todo Description
  287. *
  288. * @param string $name
  289. *
  290. * @return string
  291. */
  292. public function get($name)
  293. {
  294. return $_GET[$name];
  295. }
  296. /**
  297. * @todo Description
  298. *
  299. * @param string $file
  300. * @param string $ext
  301. * @param string $locate
  302. *
  303. * @return $this
  304. */
  305. public function addStylesheet($file, $ext = '.css', $locate = 'css')
  306. {
  307. //$file = $this->app->locateURL('css', $file . $ext);
  308. if (@$this->included[$locate.'-'.$file.$ext]++) {
  309. return;
  310. }
  311. if (strpos($file, 'http') !== 0 && $file[0] != '/') {
  312. $url = $this->locateURL($locate, $file.$ext);
  313. } else {
  314. $url = $file;
  315. }
  316. $this->template->appendHTML(
  317. 'js_include',
  318. '<link type="text/css" href="'.$url.'" rel="stylesheet" />'."\n"
  319. );
  320. return $this;
  321. }
  322. // }}}
  323. // {{{ Very Important Methods
  324. /**
  325. * Call this method from your index file. It is the main method of Agile Toolkit.
  326. */
  327. public function main()
  328. {
  329. try {
  330. // Initialize page and all elements from here
  331. $this->initLayout();
  332. } catch (Exception $e) {
  333. if (!($e instanceof Exception_Stop)) {
  334. return $this->caughtException($e);
  335. }
  336. //$this->caughtException($e);
  337. }
  338. try {
  339. $this->hook('post-init');
  340. $this->hook('afterInit');
  341. $this->hook('pre-exec');
  342. $this->hook('beforeExec');
  343. if (isset($_GET['submit']) && $_POST) {
  344. $this->hook('submitted');
  345. }
  346. $this->hook('post-submit');
  347. $this->hook('afterSubmit');
  348. $this->execute();
  349. } catch (Exception $e) {
  350. $this->caughtException($e);
  351. }
  352. $this->hook('saveDelayedModels');
  353. }
  354. /**
  355. * Main execution loop.
  356. */
  357. public function execute()
  358. {
  359. $this->rendered['sub-elements'] = array();
  360. try {
  361. $this->hook('pre-render');
  362. $this->hook('beforeRender');
  363. $this->recursiveRender();
  364. if (isset($_GET['cut_object'])) {
  365. throw new BaseException("Unable to cut object with name='".$_GET['cut_object']."'. ".
  366. "It wasn't initialized");
  367. }
  368. if (isset($_GET['cut_region'])) {
  369. // @todo Imants: This looks something obsolete. At least property cut_region_result is never defined.
  370. if (!$this->cut_region_result) {
  371. throw new BaseException("Unable to cut region with name='".$_GET['cut_region']."'");
  372. }
  373. echo $this->cut_region_result;
  374. return;
  375. }
  376. } catch (Exception $e) {
  377. if ($e instanceof Exception_Stop) {
  378. $this->hook('cut-output');
  379. if (isset($e->result)) {
  380. echo $e->result;
  381. }
  382. $this->hook('post-render-output');
  383. return;
  384. }
  385. throw $e;
  386. }
  387. }
  388. /**
  389. * Renders all objects inside applications and echo all output to the browser.
  390. */
  391. public function render()
  392. {
  393. $this->hook('pre-js-collection');
  394. if (isset($this->app->jquery) && $this->app->jquery) {
  395. $this->app->jquery->getJS($this);
  396. }
  397. if (!($this->template)) {
  398. throw new BaseException('You should specify template for APP object');
  399. }
  400. $this->hook('pre-render-output');
  401. if (headers_sent($file, $line)) {
  402. echo "<br />Direct output (echo or print) detected on $file:$line. <a target='_blank' "
  403. ."href='http://agiletoolkit.org/error/direct_output'>Use \$this->add('Text') instead</a>.<br />";
  404. }
  405. echo $this->template->render();
  406. $this->hook('post-render-output');
  407. }
  408. // }}}
  409. // {{{ Miscelanious Functions
  410. /**
  411. * Render only specified object or object with specified name.
  412. *
  413. * @param mixed $object
  414. *
  415. * @return $this
  416. */
  417. public function cut($object)
  418. {
  419. $_GET['cut_object'] = is_object($object) ? $object->name : $object;
  420. return $this;
  421. }
  422. /**
  423. * Perform instant redirect to another page.
  424. *
  425. * @param string $page
  426. * @param array $args
  427. */
  428. public function redirect($page = null, $args = array())
  429. {
  430. /*
  431. * Redirect to specified page. $args are $_GET arguments.
  432. * Use this function instead of issuing header("Location") stuff
  433. */
  434. $url = $this->url($page, $args);
  435. if ($this->app->isAjaxOutput()) {
  436. if ($_GET['cut_page']) {
  437. echo '<script>'.$this->app->js()->redirect($url).'</script>Redirecting page...';
  438. exit;
  439. } else {
  440. $this->app->js()->redirect($url)->execute();
  441. }
  442. }
  443. header('Location: '.$url);
  444. exit;
  445. }
  446. /**
  447. * Called on all templates in the system, populates some system-wide tags.
  448. *
  449. * @param Template $t
  450. */
  451. public function setTags($t)
  452. {
  453. // Determine Location to atk_public
  454. if ($this->app->pathfinder && $this->app->pathfinder->atk_public) {
  455. $q = $this->app->pathfinder->atk_public->getURL();
  456. } else {
  457. $q = 'http://www.agiletoolkit.org/';
  458. }
  459. $t->trySet('atk_path', $q.'/');
  460. $t->trySet('base_path', $q = $this->app->pm->base_path);
  461. // We are using new capability of SMlite to process tags individually
  462. try {
  463. $t->eachTag($tag = 'template', array($this, '_locateTemplate'));
  464. $t->eachTag($tag = 'public', array($this, '_locatePublic'));
  465. $t->eachTag($tag = 'js', array($this, '_locateJS'));
  466. $t->eachTag($tag = 'css', array($this, '_locateCSS'));
  467. $t->eachTag($tag = 'page', array($this, 'getBaseURL'));
  468. } catch (BaseException $e) {
  469. throw $e
  470. ->addMoreInfo('processing_tag', $tag)
  471. ->addMoreInfo('template', $t->template_file)
  472. ;
  473. }
  474. $this->hook('set-tags', array($t));
  475. }
  476. /**
  477. * Returns true if browser is going to EVAL output.
  478. *
  479. * @todo rename into isJSOutput();
  480. *
  481. * @return bool
  482. */
  483. public function isAjaxOutput()
  484. {
  485. return isset($_POST['ajax_submit']) || ($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest');
  486. }
  487. /** @private - NO PRIVATE !!! */
  488. public function _locateTemplate($path)
  489. {
  490. return $this->locateURL('public', $path);
  491. }
  492. public function _locatePublic($path)
  493. {
  494. return $this->locateURL('public', $path);
  495. }
  496. public function _locateJS($path)
  497. {
  498. return $this->locateURL('js', $path);
  499. }
  500. public function _locateCSS($path)
  501. {
  502. return $this->locateURL('css', $path);
  503. }
  504. public function _locatePage($path)
  505. {
  506. return $this->url($path);
  507. }
  508. /**
  509. * Only show $object in the final rendering.
  510. *
  511. * @deprecated 4.4
  512. */
  513. public function renderOnly($object)
  514. {
  515. return $this->cut($object);
  516. }
  517. // }}}
  518. // {{{ Layout implementation
  519. protected $layout_initialized = false;
  520. /**
  521. * Implements Layouts.
  522. * Layout is region in shared template which may be replaced by object.
  523. */
  524. public function initLayout()
  525. {
  526. if ($this->layout_initialized) {
  527. throw $this->exception('Please do not call initLayout() directly from init()', 'Obsolete');
  528. }
  529. $this->layout_initialized = true;
  530. }
  531. // TODO: layouts need to be simplified and obsolete, because we have have other layouts now.
  532. // doc/layouts
  533. //
  534. /**
  535. * Register new layout, which, if has method and tag in the template, will be rendered.
  536. *
  537. * @param string $name
  538. *
  539. * @return $this
  540. */
  541. public function addLayout($name)
  542. {
  543. if (!$this->template) {
  544. return;
  545. }
  546. // TODO: change to functionExists()
  547. if (method_exists($this, $lfunc = 'layout_'.$name)) {
  548. if ($this->template->is_set($name)) {
  549. $this->$lfunc();
  550. }
  551. }
  552. return $this;
  553. }
  554. /**
  555. * Default handling of Content page. To be replaced by App_Frontend
  556. * This function initializes content. Content is page-dependant.
  557. */
  558. public function layout_Content()
  559. {
  560. $page = str_replace('/', '_', $this->page);
  561. if (method_exists($this, $pagefunc = 'page_'.$page)) {
  562. $p = $this->add('Page', $this->page, 'Content');
  563. $this->$pagefunc($p);
  564. } else {
  565. $this->app->locate('page', str_replace('_', '/', $this->page).'.php');
  566. $this->add('page_'.$page, $page, 'Content');
  567. //throw new BaseException("No such page: ".$this->page);
  568. }
  569. }
  570. /**
  571. * Default template for the application. Redefine to add your own rules.
  572. *
  573. * @return array|string
  574. */
  575. public function defaultTemplate()
  576. {
  577. return array('html');
  578. }
  579. // }}}
  580. }