PageRenderTime 150ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/konstrukt/konstrukt.inc.php

http://github.com/troelskn/konstrukt
PHP | 1721 lines | 1049 code | 45 blank | 627 comment | 95 complexity | 6d296f8457251b44b34a87212d85231a MD5 | raw file
  1. <?php
  2. require_once 'konstrukt/adapter.inc.php';
  3. require_once 'konstrukt/charset.inc.php';
  4. require_once 'konstrukt/response.inc.php';
  5. require_once 'konstrukt/logging.inc.php';
  6. require_once 'konstrukt/template.inc.php';
  7. /**
  8. * A factory for creating components on-the-fly.
  9. * A ComponentCreator can create new instances of k_Component.
  10. * If you use a DI container, you should write an adapter that implements this interface
  11. */
  12. interface k_ComponentCreator {
  13. /**
  14. * Sets the debugger to use.
  15. * @param k_DebugListener
  16. * @return void
  17. */
  18. function setDebugger(k_DebugListener $debugger);
  19. /**
  20. * Creates a new instance of the requested class.
  21. * @param string
  22. * @param k_Context
  23. * @param string
  24. * @return k_Component
  25. */
  26. function create($class_name, k_Context $context, $namespace = "");
  27. }
  28. /**
  29. * A simple implementation, which doesn't handle any dependencies.
  30. * You can use this, for very simple applications or if you prefer a global
  31. * mechanism for dependencies (Such as Singleton).
  32. */
  33. class k_DefaultComponentCreator implements k_ComponentCreator {
  34. /** @var k_DebugListener */
  35. protected $debugger;
  36. /** @var k_Document */
  37. protected $document;
  38. /** @var array */
  39. protected $aliases = array();
  40. /**
  41. * @param k_Document
  42. * @return void
  43. */
  44. function __construct($document = null) {
  45. $this->document = $document ? $document : new k_Document();
  46. $this->setDebugger(new k_MultiDebugListener());
  47. }
  48. /**
  49. * Sets the debugger to use.
  50. * @param k_DebugListener
  51. * @return void
  52. */
  53. function setDebugger(k_DebugListener $debugger) {
  54. $this->debugger = $debugger;
  55. }
  56. /**
  57. * When trying to instantiate $class_name, instead use $implementing_class
  58. */
  59. function setImplementation($class_name, $implementing_class) {
  60. $this->aliases[strtolower($class_name)] = $implementing_class;
  61. }
  62. /**
  63. * Creates a new instance of the requested class.
  64. * @param string
  65. * @param k_Context
  66. * @param string
  67. * @return k_Component
  68. */
  69. function create($class_name, k_Context $context, $namespace = "") {
  70. $component = $this->instantiate(isset($this->aliases[strtolower($class_name)]) ? $this->aliases[strtolower($class_name)] : $class_name);
  71. $component->setContext($context);
  72. $component->setUrlState(new k_UrlState($context, $namespace));
  73. $component->setDocument($this->document);
  74. $component->setComponentCreator($this);
  75. $component->setDebugger($this->debugger);
  76. return $component;
  77. }
  78. /**
  79. * @param string
  80. * @return k_Component
  81. */
  82. protected function instantiate($class_name) {
  83. return new $class_name();
  84. }
  85. }
  86. /**
  87. * A debuglistener is an object that can receive various events and send
  88. * them to some place for further inspection.
  89. */
  90. interface k_DebugListener {
  91. /**
  92. * @param k_Context
  93. * @return void
  94. */
  95. function logRequestStart(k_Context $context);
  96. /**
  97. * @param Exception
  98. * @return void
  99. */
  100. function logException(Exception $ex);
  101. /**
  102. * @param k_Component
  103. * @param string
  104. * @param string
  105. * @return void
  106. */
  107. function logDispatch($component, $name, $next);
  108. /**
  109. * @param mixed
  110. * @return void
  111. */
  112. function log($mixed);
  113. /**
  114. * Allows the debugger to change the k_Response before output.
  115. * This is used to inject a debugbar into the response. It is perhaps not the most elegant solution, but it works.
  116. * @param k_Response
  117. * @return k_Response
  118. */
  119. function decorate(k_Response $response);
  120. }
  121. /**
  122. * Used internally.
  123. *
  124. * Returns the first stack frame, which isn't a method on a k_DebugListener.
  125. */
  126. function k_debuglistener_get_stackframe() {
  127. $stacktrace = debug_backtrace();
  128. array_shift($stacktrace);
  129. foreach ($stacktrace as $frame) {
  130. if (!isset($frame['class'])) break;
  131. $reflection = new ReflectionClass($frame['class']);
  132. if (!$reflection->implementsInterface('k_DebugListener')) break;
  133. }
  134. return $frame;
  135. }
  136. /**
  137. * A dummy implementation of k_DebugListener, which does nothing.
  138. */
  139. class k_VoidDebugListener implements k_DebugListener {
  140. function logRequestStart(k_Context $context) {}
  141. function logException(Exception $ex) {}
  142. function logDispatch($component, $name, $next) {}
  143. function log($mixed) {}
  144. function decorate(k_Response $response) {
  145. return $response;
  146. }
  147. }
  148. /**
  149. * Decorator that allows multiple k_DebugListener objects to receive the same events.
  150. */
  151. class k_MultiDebugListener implements k_DebugListener {
  152. protected $listeners = array();
  153. function add(k_DebugListener $listener) {
  154. $this->listeners[] = $listener;
  155. }
  156. function logRequestStart(k_Context $context) {
  157. foreach ($this->listeners as $listener) {
  158. $listener->logRequestStart($context);
  159. }
  160. }
  161. function logException(Exception $ex) {
  162. foreach ($this->listeners as $listener) {
  163. $listener->logException($ex);
  164. }
  165. }
  166. /**
  167. * @param k_Component
  168. * @param string
  169. * @param string
  170. * @return void
  171. */
  172. function logDispatch($component, $name, $next) {
  173. foreach ($this->listeners as $listener) {
  174. $listener->logDispatch($component, $name, $next);
  175. }
  176. }
  177. function log($mixed) {
  178. foreach ($this->listeners as $listener) {
  179. $listener->log($mixed);
  180. }
  181. }
  182. /**
  183. * @param k_Response
  184. * @return k_Response
  185. */
  186. function decorate(k_Response $response) {
  187. foreach ($this->listeners as $listener) {
  188. $response = $listener->decorate($response);
  189. }
  190. return $response;
  191. }
  192. }
  193. /**
  194. * For registry-style component wiring.
  195. * You can use this for legacy applications, that are tied to k_Registry.
  196. * usage:
  197. *
  198. * $registry = new k_Registry();
  199. * k_run('Root', new k_HttpRequest(), new k_RegistryComponentCreator($registry))->out();
  200. */
  201. class k_RegistryComponentCreator extends k_DefaultComponentCreator {
  202. protected $registry;
  203. function __construct($registry) {
  204. parent::__construct();
  205. $this->registry = $registry;
  206. }
  207. protected function instantiate($class_name) {
  208. return new $class_name($this->registry);
  209. }
  210. }
  211. /**
  212. * Adapter for using a dependency injection container for creating components.
  213. * Works with any di-container, that provides a method `create` for instantiating a container.
  214. * Tested with:
  215. * https://phemto.svn.sourceforge.net/svnroot/phemto/trunk
  216. * http://github.com/troelskn/bucket/tree/master
  217. */
  218. class k_InjectorAdapter extends k_DefaultComponentCreator {
  219. /** @var Object */
  220. protected $injector;
  221. /**
  222. * @param Object
  223. * @return void
  224. */
  225. function __construct($injector, $document = null) {
  226. if (!method_exists($injector, 'create')) {
  227. throw new Exception("Injector must provide a method `create`");
  228. }
  229. parent::__construct($document);
  230. $this->injector = $injector;
  231. }
  232. /**
  233. * @param string
  234. * @return k_Component
  235. */
  236. protected function instantiate($class_name) {
  237. return $this->injector->create($class_name);
  238. }
  239. }
  240. /**
  241. * For BC. Use k_InjectorAdapter instead.
  242. *
  243. * @deprecatd
  244. */
  245. class k_PhemtoAdapter extends k_InjectorAdapter {}
  246. /**
  247. * Representation of the applications' user
  248. */
  249. interface k_Identity {
  250. /**
  251. * Return the username
  252. */
  253. function user();
  254. /**
  255. * Return true if the user is anonymous (not authenticated)
  256. */
  257. function anonymous();
  258. }
  259. /**
  260. * A factory for recognising and loading the users identity
  261. */
  262. interface k_IdentityLoader {
  263. /**
  264. * Using the context as input, the identityloader should find and return a userobject.
  265. * @return k_Identity
  266. */
  267. function load(k_Context $context);
  268. }
  269. /**
  270. * A default implementation, which always returns k_Anonymous
  271. */
  272. class k_DefaultIdentityLoader implements k_IdentityLoader {
  273. function load(k_Context $context) {
  274. return new k_Anonymous();
  275. }
  276. }
  277. /**
  278. * Baseclass for basic http authentication.
  279. */
  280. class k_BasicHttpIdentityLoader implements k_IdentityLoader {
  281. function load(k_Context $context) {
  282. if (preg_match('/^Basic (.*)$/', $context->header('authorization'), $mm)) {
  283. list($username, $password) = explode(':', base64_decode($mm[1]), 2);
  284. $user = $this->selectUser($username, $password);
  285. if ($user) {
  286. return $user;
  287. }
  288. }
  289. return new k_Anonymous();
  290. }
  291. /**
  292. * This should be overridden in subclass to select user from eg. a database or similar.
  293. */
  294. function selectUser($username, $password) {
  295. return new k_AuthenticatedUser($username);
  296. }
  297. }
  298. /**
  299. * Default implementation of k_Identity ... doesn't do much
  300. */
  301. class k_Anonymous implements k_Identity {
  302. function user() {
  303. return "";
  304. }
  305. function anonymous() {
  306. return true;
  307. }
  308. }
  309. /**
  310. * Basic implementation of k_Identity, used for authenticated users.
  311. */
  312. class k_AuthenticatedUser implements k_Identity {
  313. protected $user;
  314. function __construct($user) {
  315. $this->user = $user;
  316. }
  317. function user() {
  318. return $this->user;
  319. }
  320. function anonymous() {
  321. return false;
  322. }
  323. }
  324. /**
  325. * Representation of the applications' current language
  326. */
  327. interface k_Language {
  328. /**
  329. * Return the language name in English
  330. */
  331. function name();
  332. /**
  333. * Return the two letter language language iso code (639-1)
  334. */
  335. function isoCode();
  336. }
  337. /**
  338. * A factory for recognising and loading the language
  339. */
  340. interface k_LanguageLoader {
  341. /**
  342. * Using the context as input, the languageloader should find and return a language object.
  343. * @return k_Language
  344. */
  345. function load(k_Context $context);
  346. }
  347. /**
  348. * A factory for recognising and loading the language
  349. */
  350. interface k_TranslatorLoader {
  351. /**
  352. * Using the context as input, the translatorloader should find and return a translator object.
  353. * @return k_Translator
  354. */
  355. function load(k_Context $context);
  356. }
  357. interface k_Translator {
  358. /**
  359. * @param string $phrase The phrase to translate
  360. * @param k_Language $language The language to translate the phrase to
  361. */
  362. function translate($phrase, k_Language $language = null);
  363. }
  364. /**
  365. * A context provides a full encapsulation of global variables, for any sub-components
  366. * The most important direct implementations are k_HttpRequest and k_Component
  367. */
  368. interface k_Context {
  369. function query($key = null, $default = null);
  370. function body($key = null, $default = null);
  371. function header($key = null, $default = null);
  372. function cookie($key = null, $default = null);
  373. function session($key = null, $default = null);
  374. function file($key = null, $default = null);
  375. function rawHttpRequestBody();
  376. function requestUri();
  377. function method();
  378. function serverName();
  379. function remoteAddr();
  380. function identity();
  381. function language();
  382. function translator();
  383. function t($phrase, k_Language $language = null);
  384. function url($path = "", $params = array());
  385. function subspace();
  386. function negotiateContentType($candidates = array());
  387. }
  388. /**
  389. * Usually the top-level context ... provides access to the http-protocol
  390. */
  391. class k_HttpRequest implements k_Context {
  392. /** @var string */
  393. protected $href_base;
  394. /** @var string */
  395. protected $subspace;
  396. /** @var array */
  397. protected $query;
  398. /** @var array */
  399. protected $body;
  400. /** @var string */
  401. protected $rawHttpRequestBody;
  402. /** @var string */
  403. protected $request_uri;
  404. /** @var array */
  405. protected $server;
  406. /** @var array */
  407. protected $files;
  408. /** @var array */
  409. protected $headers;
  410. /** @var k_adapter_CookieAccess */
  411. protected $cookie_access;
  412. /** @var k_adapter_SessionAccess */
  413. protected $session_access;
  414. /** @var k_IdentityLoader */
  415. protected $identity_loader;
  416. /** @var k_Identity */
  417. protected $identity;
  418. /** @var k_LanguageLoader */
  419. protected $language_loader;
  420. /** @var k_Language */
  421. protected $language;
  422. /** @var k_TranslatorLoader */
  423. protected $translator_loader;
  424. /** @var k_Translator */
  425. protected $translator;
  426. /** @var k_ContentTypeNegotiator */
  427. protected $content_type_negotiator;
  428. /**
  429. * @param string
  430. * @param string
  431. * @param k_IdentityLoader
  432. * @param k_LanguageLoader
  433. * @param k_TranslatorLoader
  434. * @param k_adapter_GlobalsAccess
  435. * @param k_adapter_CookieAccess
  436. * @param k_adapter_SessionAccess
  437. */
  438. function __construct($href_base = null, $request_uri = null, k_IdentityLoader $identity_loader = null, k_LanguageLoader $language_loader = null, k_TranslatorLoader $translator_loader = null, k_adapter_GlobalsAccess $superglobals = null, k_adapter_CookieAccess $cookie_access = null, k_adapter_SessionAccess $session_access = null, k_adapter_UploadedFileAccess $file_access = null) {
  439. if (preg_match('~/$~', $href_base)) {
  440. throw new Exception("href_base may _not_ have trailing slash");
  441. }
  442. if (preg_match('~^\w+://\w+\.~', $href_base)) {
  443. throw new Exception("href_base may _not_ include hostname");
  444. }
  445. if (!$superglobals) {
  446. $superglobals = new k_adapter_SafeGlobalsAccess(new k_charset_Utf8CharsetStrategy());
  447. }
  448. if (!$file_access) {
  449. $file_access = new k_adapter_DefaultUploadedFileAccess();
  450. }
  451. $this->query = $superglobals->query();
  452. $this->body = $superglobals->body();
  453. $this->rawHttpRequestBody = $superglobals->rawHttpRequestBody();
  454. $this->server = $superglobals->server();
  455. $this->files = array();
  456. foreach ($superglobals->files() as $key => $file_info) {
  457. if (array_key_exists('name', $file_info)) {
  458. $this->files[$key] = new k_adapter_UploadedFile($file_info, $key, $file_access);
  459. } else {
  460. $this->files[$key] = array();
  461. foreach ($file_info as $file_info_struct) {
  462. $this->files[$key][] = new k_adapter_UploadedFile($file_info_struct, $key, $file_access);
  463. }
  464. }
  465. }
  466. $this->headers = $this->lowerKeys($superglobals->headers());
  467. $this->cookie_access = $cookie_access ? $cookie_access : new k_adapter_DefaultCookieAccess($this->server['SERVER_NAME'], $superglobals->cookie());
  468. $this->session_access = $session_access ? $session_access : new k_adapter_DefaultSessionAccess($this->cookie_access);
  469. $this->identity_loader = $identity_loader ? $identity_loader : new k_DefaultIdentityLoader();
  470. $this->language_loader = $language_loader;
  471. $this->translator_loader = $translator_loader;
  472. $this->href_base = $href_base === null ? preg_replace('~(.*)/.*~', '$1', $this->server['SCRIPT_NAME']) : $href_base;
  473. $this->request_uri = $request_uri === null ? $this->server['REQUEST_URI'] : $request_uri;
  474. $this->subspace =
  475. preg_replace( // remove root
  476. '~^' . preg_quote($this->href_base, '~') . '~', '',
  477. preg_replace( // remove trailing query-string
  478. '~([?]{1}.*)$~', '',
  479. $this->request_uri));
  480. $this->content_type_negotiator = new k_ContentTypeNegotiator($this->header('accept'));
  481. }
  482. /**
  483. * @param array
  484. * @return array
  485. */
  486. protected function lowerKeys($input) {
  487. $output = array();
  488. foreach ($input as $key => $value) {
  489. $output[strtolower($key)] = $value;
  490. }
  491. return $output;
  492. }
  493. /**
  494. * @param string
  495. * @param string
  496. * @return string
  497. */
  498. function query($key = null, $default = null) {
  499. return $key
  500. ? isset($this->query[$key])
  501. ? $this->query[$key]
  502. : $default
  503. : $this->query;
  504. }
  505. /**
  506. * @param string
  507. * @param string
  508. * @return string
  509. */
  510. function body($key = null, $default = null) {
  511. return $key
  512. ? isset($this->body[$key])
  513. ? $this->body[$key]
  514. : $default
  515. : $this->body;
  516. }
  517. /**
  518. * Returns the http-request body as-is. Note that you have to manually handle charset-issues for this.
  519. * @return string
  520. */
  521. function rawHttpRequestBody() {
  522. return $this->rawHttpRequestBody;
  523. }
  524. /**
  525. * @param string
  526. * @param mixed The default value to return, if the value doesn't exist
  527. * @return string
  528. */
  529. function header($key = null, $default = null) {
  530. $key = strtolower($key);
  531. return $key
  532. ? isset($this->headers[$key])
  533. ? $this->headers[$key]
  534. : $default
  535. : $this->headers;
  536. }
  537. function cookie($key = null, $default = null) {
  538. return $key ? $this->cookie_access->get($key, $default) : $this->cookie_access;
  539. }
  540. /**
  541. * @param string
  542. * @param mixed The default value to return, if the value doesn't exist
  543. * @return string
  544. */
  545. function session($key = null, $default = null) {
  546. return $key ? $this->session_access->get($key, $default) : $this->session_access;
  547. }
  548. function file($key = null, $default = null) {
  549. return $key
  550. ? isset($this->files[$key])
  551. ? $this->files[$key]
  552. : $default
  553. : $this->files;
  554. }
  555. /**
  556. * Gives back the HTTP-method
  557. * Allows a body-parameter `_method` to override, if the real HTTP-method is POST.
  558. * Eg. to emulate a PUT request, you can POST with:
  559. * <input type="hidden" name="_method" value="put" />
  560. * @return string
  561. */
  562. function method() {
  563. $real_http_method = strtolower($this->server['REQUEST_METHOD']);
  564. return $real_http_method === 'post' ? $this->body('_method', 'post') : $real_http_method;
  565. }
  566. function requestUri() {
  567. return $this->request_uri;
  568. }
  569. /**
  570. * Gives back the server name
  571. * @return string
  572. */
  573. function serverName() {
  574. return $this->server['SERVER_NAME'];
  575. }
  576. function remoteAddr() {
  577. return $this->server['REMOTE_ADDR'];
  578. }
  579. /**
  580. * @return k_Identity
  581. */
  582. function identity() {
  583. if (!isset($this->identity)) {
  584. $this->identity = $this->identity_loader->load($this);
  585. }
  586. return $this->identity;
  587. }
  588. /**
  589. * @return k_Language
  590. */
  591. function language() {
  592. if (!isset($this->language)) {
  593. if(!isset($this->language_loader)) {
  594. throw new ErrorException('Trying to load a language when no language loader is provided');
  595. }
  596. $this->language = $this->language_loader->load($this);
  597. }
  598. return $this->language;
  599. }
  600. /**
  601. * @return k_Translator
  602. */
  603. function translator() {
  604. if (!isset($this->translator)) {
  605. if(!isset($this->translator_loader)) {
  606. throw new ErrorException('Trying to load a translator when no translator loader is provided');
  607. }
  608. $this->translator = $this->translator_loader->load($this);
  609. }
  610. return $this->translator;
  611. }
  612. function t($phrase, k_Language $language = null) {
  613. $language = $language ? $language : $this->language();
  614. return $this->translator()->translate($phrase, $language);
  615. }
  616. /**
  617. * Generates a URL relative to this component
  618. * @param mixed
  619. * @param array
  620. * @return string
  621. */
  622. function url($path = "", $params = array()) {
  623. if (is_array($path)) {
  624. $path = implode('/', array_map('rawurlencode', $path));
  625. }
  626. $stack = array();
  627. foreach (explode('/', $this->href_base . $path) as $name) {
  628. if ($name == '..' && count($stack) > 0) {
  629. array_pop($stack);
  630. } else {
  631. $stack[] = $name;
  632. }
  633. }
  634. $normalised_path = implode('/', $stack);
  635. $assoc = array();
  636. $indexed = array();
  637. foreach ($params as $key => $value) {
  638. if (is_int($key)) {
  639. $indexed[] = rawurlencode($value);
  640. } else {
  641. $assoc[$key] = $value;
  642. }
  643. }
  644. $querystring = "";
  645. if (count($indexed) > 0) {
  646. $querystring = implode('&', $indexed);
  647. }
  648. $assoc_string = http_build_query($assoc);
  649. if ($assoc_string) {
  650. if ($querystring) {
  651. $querystring .= '&';
  652. }
  653. $querystring .= $assoc_string;
  654. }
  655. return
  656. ($normalised_path === $this->href_base ? ($normalised_path . '/') : $normalised_path)
  657. . ($querystring ? ('?' . $querystring) : '');
  658. }
  659. /**
  660. * @return string
  661. */
  662. function subspace() {
  663. return $this->subspace;
  664. }
  665. /**
  666. * @param array
  667. * @return string
  668. */
  669. function negotiateContentType($candidates = array(), $user_override = null) {
  670. return $this->content_type_negotiator->match($candidates, $user_override);
  671. }
  672. }
  673. /**
  674. * Encapsulates logic for comparing against the Accept HTTP-header
  675. */
  676. class k_ContentTypeNegotiator {
  677. /** @var array */
  678. protected $types;
  679. /**
  680. * @param string
  681. * @return oid
  682. */
  683. function __construct($accept = "") {
  684. $this->types = $this->parse($accept);
  685. usort($this->types, array($this, '_sortByQ'));
  686. }
  687. /**
  688. * @param string
  689. * @return array
  690. */
  691. function parse($input) {
  692. $types = array();
  693. $position = 0;
  694. foreach (explode(",", $input) as $tuple) {
  695. if ($tuple) {
  696. $type = $this->parseType($tuple);
  697. $type[4] = $position++;
  698. $types[] = $type;
  699. }
  700. }
  701. return $types;
  702. }
  703. /**
  704. * @param string
  705. * @return array
  706. */
  707. function parseType($tuple) {
  708. $tuple = trim($tuple);
  709. if (preg_match('~^(.*)/([^; ]*)(\s*;\s*q\s*=\s*([0-9.]+))?$~', $tuple, $mm)) {
  710. return array($mm[1] . '/' . $mm[2], $mm[1], $mm[2], isset($mm[4]) ? $mm[4] : 1);
  711. }
  712. return array($tuple, $tuple, '*', 1);
  713. }
  714. /**
  715. * @param array
  716. * @param array
  717. * @return integer
  718. */
  719. function _sortByQ($a, $b) {
  720. if ($a[3] === $b[3]) {
  721. // fallback to sort by position
  722. if ($a[4] === $b[4]) {
  723. return 0;
  724. }
  725. return ($a[4] > $b[4]) ? 1 : -1;
  726. }
  727. return ($a[3] > $b[3]) ? -1 : 1;
  728. }
  729. /**
  730. * @param array
  731. * @param array
  732. * @return boolean
  733. */
  734. function compare($a, $b) {
  735. return ($a[1] === $b[1] || $a[1] === '*' || $b[1] === '*') && ($a[2] === $b[2] || $a[2] === '*' || $b[2] === '*');
  736. }
  737. /**
  738. * @param array
  739. * @return string
  740. */
  741. function match($candidates = array(), $user_override = null) {
  742. if ($user_override) {
  743. $types = $this->parse($user_override);
  744. } else {
  745. $types = $this->types;
  746. }
  747. if (count($types) == 0 && count($candidates) > 0) {
  748. return $candidates[0];
  749. }
  750. foreach ($types as $type) {
  751. foreach ($candidates as $candidate) {
  752. $candidate_type = $this->parseType($candidate);
  753. if ($this->compare($candidate_type, $type)) {
  754. return $candidate_type[0];
  755. }
  756. }
  757. }
  758. }
  759. }
  760. /**
  761. * The document is a container for properties of the HTML-document.
  762. * The default implementation (this) is rather limited. If you have more specific needs,
  763. * you can add you own subclass and use that instead. In that case, you should follow the
  764. * same convention of explicit getters/setters, rather than using public properties etc.
  765. */
  766. class k_Document {
  767. /** @var string */
  768. protected $title = "No Title";
  769. /** @var array */
  770. protected $scripts = array();
  771. /** @var array */
  772. protected $styles = array();
  773. /** @var array */
  774. protected $onload = array();
  775. function title() {
  776. return $this->title;
  777. }
  778. function setTitle($title) {
  779. return $this->title = $title;
  780. }
  781. function scripts() {
  782. return $this->scripts;
  783. }
  784. function addScript($script) {
  785. return $this->scripts[] = $script;
  786. }
  787. function styles() {
  788. return $this->styles;
  789. }
  790. function addStyle($style) {
  791. return $this->styles[] = $style;
  792. }
  793. function onload() {
  794. return $this->onload;
  795. }
  796. function addOnload($onload) {
  797. return $this->onload[] = $onload;
  798. }
  799. function __set($name, $value) {
  800. throw new Exception("Setting of undefined property not allowed");
  801. }
  802. }
  803. /**
  804. * Exception is raised when trying to change an immutable property.
  805. */
  806. class k_PropertyImmutableException extends Exception {
  807. /** @var string */
  808. protected $message = 'Tried to change immutable property after initial assignment';
  809. }
  810. /**
  811. * Exception is raised when an input content-type has an unsupported charset.
  812. */
  813. class k_UnsupportedContentTypeCharsetException extends Exception {
  814. /** @var string */
  815. protected $message = 'Received an unsupported chaset in content-type';
  816. }
  817. /**
  818. * A component is the baseclass for all userland components
  819. * Each component should be completely isolated from its surrounding, only
  820. * depending on its parent context
  821. */
  822. abstract class k_Component implements k_Context {
  823. /** @var k_Context */
  824. protected $context;
  825. /** @var k_UrlState */
  826. protected $url_state;
  827. /**
  828. * UrlState, will be initialised with these values, upon creation.
  829. * @var array
  830. */
  831. protected $url_init = array();
  832. /** @var k_ComponentCreator */
  833. protected $component_creator;
  834. /** @var k_Document */
  835. protected $document;
  836. /** @var k_DebugListener */
  837. protected $debugger;
  838. /**
  839. * Log something to the debugger.
  840. */
  841. protected function debug($mixed) {
  842. $this->debugger->log($mixed);
  843. }
  844. /**
  845. * @param k_Context
  846. * @return void
  847. */
  848. function setContext(k_Context $context) {
  849. if ($this->context !== null) {
  850. throw new k_PropertyImmutableException();
  851. }
  852. $this->context = $context;
  853. }
  854. /**
  855. * @param k_UrlState
  856. * @return void
  857. */
  858. function setUrlState(k_UrlState $url_state) {
  859. if ($this->url_state !== null) {
  860. throw new k_PropertyImmutableException();
  861. }
  862. $this->url_state = $url_state;
  863. foreach ($this->url_init as $key => $value) {
  864. $this->url_state->init($key, $value);
  865. }
  866. }
  867. /**
  868. * @param k_ComponentCreator
  869. * @return void
  870. */
  871. function setComponentCreator(k_ComponentCreator $component_creator) {
  872. if ($this->component_creator !== null) {
  873. throw new k_PropertyImmutableException();
  874. }
  875. $this->component_creator = $component_creator;
  876. }
  877. /**
  878. * @param k_Document
  879. * @return void
  880. */
  881. function setDocument(k_Document $document) {
  882. if ($this->document !== null) {
  883. throw new k_PropertyImmutableException();
  884. }
  885. $this->document = $document;
  886. }
  887. /**
  888. * @param k_DebugListener
  889. * @return void
  890. */
  891. function setDebugger(k_DebugListener $debugger) {
  892. $this->debugger = $debugger;
  893. }
  894. /**
  895. * @param string
  896. * @param mixed The default value to return, if the value doesn't exist
  897. * @return string
  898. */
  899. function query($key = null, $default = null) {
  900. return $this->url_state->get($key, $default);
  901. }
  902. /**
  903. * @param string
  904. * @param string
  905. * @return string
  906. */
  907. function body($key = null, $default = null) {
  908. return $this->context->body($key, $default);
  909. }
  910. function rawHttpRequestBody() {
  911. return $this->context->rawHttpRequestBody();
  912. }
  913. function header($key = null, $default = null) {
  914. return $this->context->header($key, $default);
  915. }
  916. function cookie($key = null, $default = null) {
  917. return $this->context->cookie($key, $default);
  918. }
  919. /**
  920. * @param string
  921. * @param mixed The default value to return, if the value doesn't exist
  922. * @return string
  923. */
  924. function session($key = null, $default = null) {
  925. return $this->context->session($key, $default);
  926. }
  927. function file($key = null, $default = null) {
  928. return $this->context->file($key, $default);
  929. }
  930. /**
  931. * @return string
  932. */
  933. function method() {
  934. return $this->context->method();
  935. }
  936. function requestUri() {
  937. return $this->context->requestUri();
  938. }
  939. /**
  940. * @return string
  941. */
  942. function serverName() {
  943. return $this->context->serverName();
  944. }
  945. function remoteAddr() {
  946. return $this->context->remoteAddr();
  947. }
  948. function identity() {
  949. return $this->context->identity();
  950. }
  951. function language() {
  952. return $this->context->language();
  953. }
  954. function translator() {
  955. return $this->context->translator();
  956. }
  957. function t($phrase, k_Language $language = null) {
  958. return $this->context->t($phrase, $language);
  959. }
  960. /*
  961. * @return k_Document
  962. */
  963. function document() {
  964. return $this->document;
  965. }
  966. /**
  967. * @param mixed
  968. * @param array
  969. * @return string
  970. */
  971. function url($path = "", $params = array()) {
  972. if (is_array($path)) {
  973. $path = implode('/', array_map('rawurlencode', $path));
  974. }
  975. return $this->context->url(
  976. $path
  977. ? (substr($path, 0, 1) === '/'
  978. ? $path
  979. : ($path === '.'
  980. ? $this->name()
  981. : (preg_match('~^\.([^/.]+)~', $path, $mm)
  982. ? ($this->name() . '.' . $mm[1])
  983. : $this->segment() . '/' . $path)))
  984. : $this->segment(),
  985. $this->url_state->merge($params));
  986. }
  987. /**
  988. * @return string
  989. */
  990. function subspace() {
  991. return preg_replace('~^[^/]*[/]{0,1}~', '', $this->context->subspace());
  992. }
  993. function negotiateContentType($candidates = array(), $user_override = null) {
  994. return $this->context->negotiateContentType($candidates, $user_override);
  995. }
  996. protected function contentTypeShortName() {
  997. $value = null;
  998. $parameters = array();
  999. foreach (explode(";", $this->header('content-type')) as $tuple) {
  1000. if (preg_match('/^([^=]+)=(.+)$/', $tuple, $reg)) {
  1001. $parameters[strtolower(trim($reg[1]))] = $reg[2];
  1002. } elseif ($value !== null) {
  1003. throw new Exception("HTTP parse error. Header line has multiple values");
  1004. } else {
  1005. $value = $tuple;
  1006. }
  1007. }
  1008. $content_type = isset($value) ? $value : '';
  1009. $charset = isset($parameters['charset']) ? strtolower($parameters['charset']) : 'utf-8';
  1010. if ($charset !== 'utf-8') {
  1011. // This could probably be dealt with a bit more elegantly, but that would require us to re-implement PHP's native input-parsing.
  1012. // For now, if you get this error, you should just fix the problem by requiring your clients to speak utf-8.
  1013. throw new k_UnsupportedContentTypeCharsetException();
  1014. }
  1015. return isset($GLOBALS['konstrukt_content_types'][$content_type]) ? $GLOBALS['konstrukt_content_types'][$content_type] : null;
  1016. }
  1017. /**
  1018. * The full path segment for this components representation.
  1019. * @return string
  1020. */
  1021. protected function segment() {
  1022. if (preg_match('~^([^/]+)[/]{0,1}~', $this->context->subspace(), $mm)) {
  1023. return $mm[1];
  1024. }
  1025. // special case for top-level + subtype
  1026. if (preg_match('~^/(\.[^/]+)[/]{0,1}~', $this->context->subspace(), $mm)) {
  1027. return $mm[1];
  1028. }
  1029. }
  1030. /**
  1031. * The name part of the uri segment.
  1032. * @return string
  1033. */
  1034. protected function name() {
  1035. if ($segment = $this->segment()) {
  1036. return preg_replace('/\..*$/', '', $segment);
  1037. }
  1038. }
  1039. /**
  1040. * @return string
  1041. */
  1042. protected function subtype() {
  1043. if (preg_match('/\.(.+)$/', $this->segment(), $mm)) {
  1044. return $mm[1];
  1045. }
  1046. }
  1047. /**
  1048. * @return string
  1049. */
  1050. protected function next() {
  1051. if (preg_match('~^[^/.]+~', $this->subspace(), $mm)) {
  1052. return $mm[0];
  1053. }
  1054. }
  1055. /**
  1056. * @param string
  1057. * @param string A namespace for querystring parameters
  1058. * @return string
  1059. */
  1060. protected function forward($class_name, $namespace = "") {
  1061. if (is_array($class_name)) {
  1062. $namespace = $class_name[1];
  1063. $class_name = $class_name[0];
  1064. }
  1065. $next = $this->createComponent($class_name, $namespace);
  1066. return $next->dispatch();
  1067. }
  1068. /**
  1069. * @param string
  1070. * @param string A namespace for querystring parameters
  1071. * @return k_Component
  1072. */
  1073. protected function createComponent($class_name, $namespace = '') {
  1074. return $this->component_creator->create($class_name, $this, $namespace);
  1075. }
  1076. /**
  1077. * @return string
  1078. */
  1079. function dispatch() {
  1080. $this->debugger->logDispatch($this, $this->name(), $this->next());
  1081. $next = $this->next();
  1082. if ($next) {
  1083. $class_name = $this->map($next);
  1084. if (!$class_name) {
  1085. throw new k_PageNotFound();
  1086. }
  1087. return $this->wrap($this->forward($class_name));
  1088. }
  1089. return $this->execute();
  1090. }
  1091. protected function map($name) {}
  1092. /**
  1093. * @return string
  1094. */
  1095. function execute() {
  1096. $method = $this->method();
  1097. if (!in_array($method, array('head','get','post','put','delete'))) {
  1098. throw new k_MethodNotAllowed();
  1099. }
  1100. return $this->{$method}();
  1101. }
  1102. function HEAD() {
  1103. throw new k_NotImplemented();
  1104. }
  1105. function GET() {
  1106. return $this->render();
  1107. }
  1108. function POST() {
  1109. $postfix = $this->contentTypeShortName();
  1110. if ($postfix) {
  1111. if (method_exists($this, 'post' . $postfix)) {
  1112. return $this->{'post' . $postfix}();
  1113. }
  1114. }
  1115. foreach (get_class_methods($this) as $m) {
  1116. if (preg_match('~^post.+~', $m)) {
  1117. // There is at least one acceptable handler for post
  1118. throw new k_NotAcceptable();
  1119. }
  1120. }
  1121. throw new k_NotImplemented();
  1122. }
  1123. function PUT() {
  1124. $postfix = $this->contentTypeShortName();
  1125. if ($postfix) {
  1126. if (method_exists($this, 'put' . $postfix)) {
  1127. return $this->{'put' . $postfix}();
  1128. }
  1129. }
  1130. foreach (get_class_methods($this) as $m) {
  1131. if (preg_match('~^put.+~', $m)) {
  1132. // There is at least one acceptable handler for put
  1133. throw new k_NotAcceptable();
  1134. }
  1135. }
  1136. throw new k_NotImplemented();
  1137. }
  1138. function DELETE() {
  1139. throw new k_NotImplemented();
  1140. }
  1141. /**
  1142. * Renders the components view.
  1143. * This method delegates control to an appropriate handler, based on content-type negotiation.
  1144. * The typical handler would be `renderHtml`. If no handler is present, an exception is raised.
  1145. */
  1146. function render() {
  1147. $accept = array();
  1148. foreach ($GLOBALS['konstrukt_content_types'] as $type => $name) {
  1149. $handler = 'render' . $name;
  1150. if (method_exists($this, $handler)) {
  1151. $accept[$type] = $handler;
  1152. $accept[$name] = $handler;
  1153. }
  1154. }
  1155. $content_type = $this->negotiateContentType(array_keys($accept), $this->subtype());
  1156. if (isset($accept[$content_type])) {
  1157. // subview dispatch
  1158. $subview = $this->subview();
  1159. $subhandler = $accept[$content_type] . $subview;
  1160. if ($subview && method_exists($this, $subhandler)) {
  1161. return k_coerce_to_response(
  1162. $this->{$subhandler}(),
  1163. k_content_type_to_response_type($content_type));
  1164. }
  1165. return k_coerce_to_response(
  1166. $this->{$accept[$content_type]}(),
  1167. k_content_type_to_response_type($content_type));
  1168. }
  1169. if (count($accept) > 0) {
  1170. throw new k_NotAcceptable();
  1171. }
  1172. throw new k_NotImplemented();
  1173. }
  1174. function wrap($content) {
  1175. $typed = k_coerce_to_response($content);
  1176. $response_type = ($typed instanceof k_HttpResponse) ? 'http' : k_content_type_to_response_type($typed->contentType());
  1177. $handler = 'wrap' . $response_type;
  1178. // is there a wrapper for this content-type ?
  1179. if (method_exists($this, $handler)) {
  1180. $wrapped = $this->{$handler}($typed->toContentType($typed->internalType()));
  1181. // if the wrapper returns a k_Response, we just pass it through
  1182. if ($wrapped instanceof k_Response) {
  1183. return $wrapped;
  1184. }
  1185. // if the content is a typed response, we clone its status + headers
  1186. if ($content instanceof k_Response) {
  1187. $wrapped = k_coerce_to_response($wrapped, $response_type);
  1188. $wrapped->setStatus($typed->status());
  1189. foreach ($typed->headers() as $key => $value) {
  1190. $wrapped->setHeader($key, $value);
  1191. }
  1192. $wrapped->setCharset($typed->charset());
  1193. }
  1194. return $wrapped;
  1195. }
  1196. // no wrapper ? do nothing.
  1197. return $content;
  1198. }
  1199. /**
  1200. * Returns the subview component of the URL, if any.
  1201. * @return string
  1202. */
  1203. function subview() {
  1204. if (preg_match('~^.*\?([^=&]+)~i', $this->requestUri(), $mm)) {
  1205. return urldecode($mm[1]);
  1206. }
  1207. }
  1208. }
  1209. /**
  1210. * Used for persisting state over the query-string, and for namespacing query-string parameters
  1211. */
  1212. class k_UrlState {
  1213. /** @var k_Context */
  1214. protected $context;
  1215. /** @var string */
  1216. protected $namespace;
  1217. /** @var array */
  1218. protected $state = array();
  1219. /** @var array */
  1220. protected $default_values = array();
  1221. /**
  1222. * @param k_Context
  1223. * @param string
  1224. * @return void
  1225. */
  1226. function __construct(k_Context $context, $namespace = "") {
  1227. $this->context = $context;
  1228. $this->namespace = $namespace;
  1229. }
  1230. /**
  1231. * @param string
  1232. * @param string
  1233. * @return void
  1234. */
  1235. function init($key, $default = "") {
  1236. $this->state[$this->namespace . $key] = $this->context->query($this->namespace . $key, (string) $default);
  1237. $this->default_values[$this->namespace . $key] = (string) $default;
  1238. }
  1239. function has($key) {
  1240. return isset($this->state[$this->namespace . $key]);
  1241. }
  1242. /**
  1243. * @param string
  1244. * @param mixed The default value to return, if the value doesn't exist
  1245. * @return string
  1246. */
  1247. function get($key, $default = null) {
  1248. return isset($this->state[$this->namespace . $key]) ? $this->state[$this->namespace . $key] : $this->context->query($this->namespace . $key, $default);
  1249. }
  1250. /**
  1251. * @param string
  1252. * @param string
  1253. * @return void
  1254. */
  1255. function set($key, $value) {
  1256. $this->state[$this->namespace . $key] = (string) $value;
  1257. }
  1258. /**
  1259. * @param array
  1260. * @return array
  1261. */
  1262. function merge($params = array()) {
  1263. $result = $this->state;
  1264. foreach ($params as $key => $value) {
  1265. $result[$this->namespace . $key] = $value;
  1266. }
  1267. // filter off default values, since they are implied
  1268. foreach ($result as $ns_key => $value) {
  1269. if (isset($this->default_values[$ns_key]) && $value == $this->default_values[$ns_key]) {
  1270. $result[$ns_key] = null;
  1271. }
  1272. }
  1273. return $result;
  1274. }
  1275. }
  1276. /**
  1277. * A metaresponse represents an abstract event in the application, which needs alternate handling.
  1278. * This would typically be an error-condition.
  1279. * In the simplest invocation, a metaresponse maps directly to a component, which renders a generic error.
  1280. */
  1281. abstract class k_MetaResponse extends Exception {
  1282. abstract function componentName();
  1283. }
  1284. /**
  1285. * Raise this if the user must be authorised to access the requested resource.
  1286. */
  1287. class k_NotAuthorized extends k_MetaResponse {
  1288. /** @var string */
  1289. protected $message = 'You must be authorised to access this resource';
  1290. function componentName() {
  1291. return 'k_DefaultNotAuthorizedComponent';
  1292. }
  1293. }
  1294. /**
  1295. * Raise this if the user doesn't have access to the requested resource.
  1296. */
  1297. class k_Forbidden extends k_MetaResponse {
  1298. /** @var string */
  1299. protected $message = 'The requested page is forbidden';
  1300. function componentName() {
  1301. return 'k_DefaultForbiddenComponent';
  1302. }
  1303. }
  1304. /**
  1305. * Raise this if the requested resource couldn't be found.
  1306. */
  1307. class k_PageNotFound extends k_MetaResponse {
  1308. /** @var string */
  1309. protected $message = 'The requested page was not found';
  1310. function componentName() {
  1311. return 'k_DefaultPageNotFoundComponent';
  1312. }
  1313. }
  1314. /**
  1315. * Raise this if resource doesn't support the requested HTTP method.
  1316. * @see k_NotImplemented
  1317. */
  1318. class k_MethodNotAllowed extends k_MetaResponse {
  1319. /** @var string */
  1320. protected $message = 'The request HTTP method is not supported by the handling component';
  1321. function componentName() {
  1322. return 'k_DefaultMethodNotAllowedComponent';
  1323. }
  1324. }
  1325. /**
  1326. * Raise this if the request isn't yet implemented.
  1327. * This is roughly the HTTP equivalent to a "todo"
  1328. */
  1329. class k_NotImplemented extends k_MetaResponse {
  1330. /** @var string */
  1331. protected $message = 'The server does not support the functionality required to fulfill the request';
  1332. function componentName() {
  1333. return 'k_DefaultNotImplementedComponent';
  1334. }
  1335. }
  1336. /**
  1337. * Raise this if the request can't be processed due to details of the request (Such as the Content-Type or other headers)
  1338. */
  1339. class k_NotAcceptable extends k_MetaResponse {
  1340. /** @var string */
  1341. protected $message = 'The resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request';
  1342. function componentName() {
  1343. return 'k_DefaultNotNotAcceptableComponent';
  1344. }
  1345. }
  1346. /**
  1347. * @see k_NotAuthorized
  1348. */
  1349. class k_DefaultNotAuthorizedComponent extends k_Component {
  1350. function dispatch() {
  1351. $response = $this->render();
  1352. $response->setStatus(401);
  1353. $response->setHeader('WWW-Authenticate', 'Basic realm="Restricted"');
  1354. return $response;
  1355. }
  1356. function render() {
  1357. return new k_HtmlResponse('<html><body><h1>HTTP 401 - Not Authorized</h1></body></html>');
  1358. }
  1359. }
  1360. /**
  1361. * @see k_Forbidden
  1362. */
  1363. class k_DefaultForbiddenComponent extends k_Component {
  1364. function dispatch() {
  1365. $response = $this->render();
  1366. $response->setStatus(403);
  1367. return $response;
  1368. }
  1369. function render() {
  1370. return new k_HtmlResponse('<html><body><h1>HTTP 403 - Forbidden</h1></body></html>');
  1371. }
  1372. }
  1373. /**
  1374. * @see k_PageNotFound
  1375. */
  1376. class k_DefaultPageNotFoundComponent extends k_Component {
  1377. function dispatch() {
  1378. $response = $this->render();
  1379. $response->setStatus(404);
  1380. return $response;
  1381. }
  1382. function render() {
  1383. return new k_HtmlResponse('<html><body><h1>HTTP 404 - Page Not Found</h1></body></html>');
  1384. }
  1385. }
  1386. /**
  1387. * @see k_MethodNotAllowed
  1388. */
  1389. class k_DefaultMethodNotAllowedComponent extends k_Component {
  1390. function dispatch() {
  1391. $response = $this->render();
  1392. $response->setStatus(405);
  1393. return $response;
  1394. }
  1395. function render() {
  1396. return new k_HtmlResponse('<html><body><h1>HTTP 405 - Method Not Allowed</h1></body></html>');
  1397. }
  1398. }
  1399. /**
  1400. * @see k_NotAcceptable
  1401. */
  1402. class k_DefaultNotNotAcceptableComponent extends k_Component {
  1403. function dispatch() {
  1404. $response = $this->render();
  1405. $response->setStatus(406);
  1406. return $response;
  1407. }
  1408. function render() {
  1409. return new k_HtmlResponse('<html><body><h1>HTTP 406 - Not Acceptable</h1></body></html>');
  1410. }
  1411. }
  1412. /**
  1413. * @see k_NotImplemented
  1414. */
  1415. class k_DefaultNotImplementedComponent extends k_Component {
  1416. function dispatch() {
  1417. $response = $this->render();
  1418. $response->setStatus(501);
  1419. return $response;
  1420. }
  1421. function render() {
  1422. return new k_HtmlResponse('<html><body><h1>HTTP 501 - Not Implemented</h1></body></html>');
  1423. }
  1424. }
  1425. /**
  1426. * Creates an application bootstrap.
  1427. * @return k_Bootstrap
  1428. */
  1429. function k() {
  1430. return new k_Bootstrap();
  1431. }
  1432. /**
  1433. * Application bootstrap.
  1434. */
  1435. class k_Bootstrap {
  1436. /** @var k_HttpRequest */
  1437. protected $http_request;
  1438. /** @var k_ComponentCreator */
  1439. protected $components;
  1440. /** @var k_charset_CharsetStrategy */
  1441. protected $charset_strategy;
  1442. /** @var boolean */
  1443. protected $is_debug = false;
  1444. /** @var string */
  1445. protected $log_filename = null;
  1446. /** @var string */
  1447. protected $href_base = null;
  1448. /** @var k_IdentityLoader */
  1449. protected $identity_loader;
  1450. /** @var k_LanguageLoader */
  1451. protected $language_loader;
  1452. /** @var k_TranslatorLoader */
  1453. protected $translator_loader;
  1454. /** @var k_adapter_GlobalsAccess */
  1455. protected $globals_access;
  1456. /**
  1457. * Serves a http request, given a root component name.
  1458. * @param $root_class_name string The classname of an instance of k_Component
  1459. * @return k_Response
  1460. */
  1461. function run($root_class_name) {
  1462. $debugger = new k_MultiDebugListener();
  1463. $this->components()->setDebugger($debugger);
  1464. if ($this->is_debug) {
  1465. $debugger->add(new k_logging_WebDebugger());
  1466. }
  1467. if ($this->log_filename) {
  1468. $debugger->add(new k_logging_LogDebugger($this->log_filename));
  1469. }
  1470. try {
  1471. $debugger->logRequestStart($this->context());
  1472. return $debugger->decorate($this->dispatchRoot($root_class_name));
  1473. } catch (Exception $ex) {
  1474. $debugger->logException($ex);
  1475. throw $ex;
  1476. }
  1477. }
  1478. protected function dispatchRoot($root_class_name) {
  1479. try {
  1480. $class_name = $root_class_name;
  1481. while (true) {
  1482. try {
  1483. $root = $this->components()->create($class_name, $this->context());
  1484. $response = $root->dispatch();
  1485. if (!($response instanceof k_Response)) {
  1486. $response = new k_HtmlResponse($response);
  1487. }
  1488. $response->setCharset($this->charsetStrategy()->responseCharset());
  1489. return $response;
  1490. } catch (k_MetaResponse $ex) {
  1491. $class_name = $ex->componentName();
  1492. }
  1493. }
  1494. } catch (k_Response $ex) {
  1495. return $ex;
  1496. }
  1497. }
  1498. /**
  1499. * Sets the context to use. Usually, this is an instance of k_HttpRequest.
  1500. * @param k_Context
  1501. * @return k_Bootstrap
  1502. */
  1503. function setContext(k_Context $http_request) {
  1504. $this->http_request = $http_request;
  1505. return $this;
  1506. }
  1507. /**
  1508. * Sets the componentcreator to use.
  1509. * @param k_ComponentCreator
  1510. * @return k_Bootstrap
  1511. */
  1512. function setComponentCreator(k_ComponentCreator $components) {
  1513. $this->components = $components;
  1514. return $this;
  1515. }
  1516. /**
  1517. * Set the charsetstrategy.
  1518. * @param k_charset_CharsetStrategy
  1519. * @return k_Bootstrap
  1520. */
  1521. function setCharsetStrategy(k_charset_CharsetStrategy $charset_strategy) {
  1522. $this->charset_strategy = $charset_strategy;
  1523. return $this;
  1524. }
  1525. /**
  1526. * Set the identity loader.
  1527. * @param k_IdentityLoader
  1528. * @return k_Bootstrap
  1529. */
  1530. function setIdentityLoader(k_IdentityLoader $identity_loader) {
  1531. $this->identity_loader = $identity_loader;
  1532. return $this;
  1533. }
  1534. /**
  1535. * Set the language loader.
  1536. * @param k_LanguageLoader
  1537. * @return k_Bootstrap
  1538. */
  1539. function setLanguageLoader(k_LanguageLoader $language_loader) {
  1540. $this->language_loader = $language_loader;
  1541. return $this;
  1542. }
  1543. /**
  1544. * Set the translator loader.
  1545. * @param k_TranslatorLoader
  1546. * @return k_Bootstrap
  1547. */
  1548. function setTranslatorLoader(k_TranslatorLoader $translator_loader) {
  1549. $this->translator_loader = $translator_loader;
  1550. return $this;
  1551. }
  1552. /**
  1553. * Enable/disable the in-browser debug-bar.
  1554. * @param boolean
  1555. * @return k_Bootstrap
  1556. */
  1557. function setDebug($is_debug = true) {
  1558. $this->is_debug = !! $is_debug;
  1559. return $this;
  1560. }
  1561. /**
  1562. * Specifies a filename to log debug information to.
  1563. * @param string
  1564. * @return k_Bootstrap
  1565. */
  1566. function setLog($filename) {
  1567. $this->log_filename = $filename;
  1568. return $this;
  1569. }
  1570. /**
  1571. * Sets the base href, if the application isn't mounted at the web root.
  1572. * @param string
  1573. * @return k_Bootstrap
  1574. */
  1575. function setHrefBase($href_base) {
  1576. $this->href_base = $href_base;
  1577. return $this;
  1578. }
  1579. /**
  1580. * @return k_Context
  1581. */
  1582. protected function context() {
  1583. if (!isset($this->http_request)) {
  1584. $this->http_request = new k_HttpRequest($this->href_base, null, $this->identityLoader(), $this->languageLoader(), $this->translatorLoader(), $this->globalsAccess());
  1585. }
  1586. return $this->http_request;
  1587. }
  1588. /**
  1589. * @return k_ComponentCreator
  1590. */
  1591. protected function components() {
  1592. if (!isset($this->components)) {
  1593. $this->components = new k_DefaultComponentCreator();
  1594. }
  1595. return $this->components;
  1596. }
  1597. /**
  1598. * @return k_charset_CharsetStrategy
  1599. */
  1600. protected function charsetStrategy() {
  1601. if (!isset($this->charset_strategy)) {
  1602. $this->charset_strategy = new k_charset_Utf8CharsetStrategy();
  1603. }
  1604. return $this->charset_strategy;
  1605. }
  1606. /**
  1607. * @return k_IdentityLoader
  1608. */
  1609. protected function identityLoader() {
  1610. if (!isset($this->identity_loader)) {
  1611. $this->identity_loader = new k_DefaultIdentityLoader();
  1612. }
  1613. return $this->identity_loader;
  1614. }
  1615. /**
  1616. * @return mixed k_LanguageLoader or null
  1617. */
  1618. protected function languageLoader() {
  1619. return $this->language_loader;
  1620. }
  1621. /**
  1622. * @return mixed k_TranslatorLoader or null
  1623. */
  1624. protected function translatorLoader() {
  1625. return $this->translator_loader;
  1626. }
  1627. /**
  1628. * @return k_adapter_GlobalsAccess
  1629. */
  1630. protected function globalsAccess() {
  1631. if (!isset($this->globals_access)) {
  1632. $this->globals_access = new k_adapter_SafeGlobalsAccess($this->charsetStrategy());
  1633. }
  1634. return $this->globals_access;
  1635. }
  1636. }
  1637. /**
  1638. * Resolves a filename according to the includepath.
  1639. * Returns on the first match or false if no match is found.
  1640. */
  1641. function k_search_include_path($filename) {
  1642. if (is_file($filename)) {
  1643. return $filename;
  1644. }
  1645. foreach (explode(PATH_SEPARATOR, ini_get("include_path")) as $path) {
  1646. if (strlen($path) > 0 && $path{strlen($path)-1} != DIRECTORY_SEPARATOR) {
  1647. $path .= DIRECTORY_SEPARATOR;
  1648. }
  1649. $f = realpath($path . $filename);
  1650. if ($f && is_file($f)) {
  1651. return $f;
  1652. }
  1653. }
  1654. return false;
  1655. }
  1656. /**
  1657. * A simple autoloader.
  1658. */
  1659. function k_autoload($classname) {
  1660. $filename = str_replace('_', '/', strtolower($classname)).'.php';
  1661. if (k_search_include_path($filename)) {
  1662. require_once($filename);
  1663. }
  1664. }
  1665. /**
  1666. * An error-handler which converts all errors (regardless of level) into exceptions.
  1667. * It respects error_reporting settings.
  1668. */
  1669. function k_exceptions_error_handler($severity, $message, $filename, $lineno) {
  1670. if (error_reporting() == 0) {
  1671. return;
  1672. }
  1673. if (error_reporting() & $severity) {
  1674. throw new ErrorException($message, 0, $severity, $filename, $lineno);
  1675. }
  1676. }