PageRenderTime 53ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/konstrukt/konstrukt.inc.php

http://konstrukt.googlecode.com/
PHP | 1714 lines | 1043 code | 45 blank | 626 comment | 94 complexity | d73d9f7772a815ccaebfad6c0ab6f661 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. foreach (explode(",", $input) as $tuple) {
  694. if ($tuple) {
  695. $types[] = $this->parseType($tuple);
  696. }
  697. }
  698. return $types;
  699. }
  700. /**
  701. * @param string
  702. * @return array
  703. */
  704. function parseType($tuple) {
  705. $tuple = trim($tuple);
  706. if (preg_match('~^(.*)/([^; ]*)(\s*;\s*q\s*=\s*([0-9.]+))?$~', $tuple, $mm)) {
  707. return array($mm[1] . '/' . $mm[2], $mm[1], $mm[2], isset($mm[4]) ? $mm[4] : 1);
  708. }
  709. return array($tuple, $tuple, '*', 1);
  710. }
  711. /**
  712. * @param array
  713. * @param array
  714. * @return integer
  715. */
  716. function _sortByQ($a, $b) {
  717. if ($a[3] === $b[3]) {
  718. return 0;
  719. }
  720. return ($a[3] > $b[3]) ? -1 : 1;
  721. }
  722. /**
  723. * @param array
  724. * @param array
  725. * @return boolean
  726. */
  727. function compare($a, $b) {
  728. return ($a[1] === $b[1] || $a[1] === '*' || $b[1] === '*') && ($a[2] === $b[2] || $a[2] === '*' || $b[2] === '*');
  729. }
  730. /**
  731. * @param array
  732. * @return string
  733. */
  734. function match($candidates = array(), $user_override = null) {
  735. if ($user_override) {
  736. $types = $this->parse($user_override);
  737. } else {
  738. $types = $this->types;
  739. }
  740. if (count($types) == 0 && count($candidates) > 0) {
  741. return $candidates[0];
  742. }
  743. foreach ($types as $type) {
  744. foreach ($candidates as $candidate) {
  745. $candidate_type = $this->parseType($candidate);
  746. if ($this->compare($candidate_type, $type)) {
  747. return $candidate_type[0];
  748. }
  749. }
  750. }
  751. }
  752. }
  753. /**
  754. * The document is a container for properties of the HTML-document.
  755. * The default implementation (this) is rather limited. If you have more specific needs,
  756. * you can add you own subclass and use that instead. In that case, you should follow the
  757. * same convention of explicit getters/setters, rather than using public properties etc.
  758. */
  759. class k_Document {
  760. /** @var string */
  761. protected $title = "No Title";
  762. /** @var array */
  763. protected $scripts = array();
  764. /** @var array */
  765. protected $styles = array();
  766. /** @var array */
  767. protected $onload = array();
  768. function title() {
  769. return $this->title;
  770. }
  771. function setTitle($title) {
  772. return $this->title = $title;
  773. }
  774. function scripts() {
  775. return $this->scripts;
  776. }
  777. function addScript($script) {
  778. return $this->scripts[] = $script;
  779. }
  780. function styles() {
  781. return $this->styles;
  782. }
  783. function addStyle($style) {
  784. return $this->styles[] = $style;
  785. }
  786. function onload() {
  787. return $this->onload;
  788. }
  789. function addOnload($onload) {
  790. return $this->onload[] = $onload;
  791. }
  792. function __set($name, $value) {
  793. throw new Exception("Setting of undefined property not allowed");
  794. }
  795. }
  796. /**
  797. * Exception is raised when trying to change an immutable property.
  798. */
  799. class k_PropertyImmutableException extends Exception {
  800. /** @var string */
  801. protected $message = 'Tried to change immutable property after initial assignment';
  802. }
  803. /**
  804. * Exception is raised when an input content-type has an unsupported charset.
  805. */
  806. class k_UnsupportedContentTypeCharsetException extends Exception {
  807. /** @var string */
  808. protected $message = 'Received an unsupported chaset in content-type';
  809. }
  810. /**
  811. * A component is the baseclass for all userland components
  812. * Each component should be completely isolated from its surrounding, only
  813. * depending on its parent context
  814. */
  815. abstract class k_Component implements k_Context {
  816. /** @var k_Context */
  817. protected $context;
  818. /** @var k_UrlState */
  819. protected $url_state;
  820. /**
  821. * UrlState, will be initialised with these values, upon creation.
  822. * @var array
  823. */
  824. protected $url_init = array();
  825. /** @var k_ComponentCreator */
  826. protected $component_creator;
  827. /** @var k_Document */
  828. protected $document;
  829. /** @var k_DebugListener */
  830. protected $debugger;
  831. /**
  832. * Log something to the debugger.
  833. */
  834. protected function debug($mixed) {
  835. $this->debugger->log($mixed);
  836. }
  837. /**
  838. * @param k_Context
  839. * @return void
  840. */
  841. function setContext(k_Context $context) {
  842. if ($this->context !== null) {
  843. throw new k_PropertyImmutableException();
  844. }
  845. $this->context = $context;
  846. }
  847. /**
  848. * @param k_UrlState
  849. * @return void
  850. */
  851. function setUrlState(k_UrlState $url_state) {
  852. if ($this->url_state !== null) {
  853. throw new k_PropertyImmutableException();
  854. }
  855. $this->url_state = $url_state;
  856. foreach ($this->url_init as $key => $value) {
  857. $this->url_state->init($key, $value);
  858. }
  859. }
  860. /**
  861. * @param k_ComponentCreator
  862. * @return void
  863. */
  864. function setComponentCreator(k_ComponentCreator $component_creator) {
  865. if ($this->component_creator !== null) {
  866. throw new k_PropertyImmutableException();
  867. }
  868. $this->component_creator = $component_creator;
  869. }
  870. /**
  871. * @param k_Document
  872. * @return void
  873. */
  874. function setDocument(k_Document $document) {
  875. if ($this->document !== null) {
  876. throw new k_PropertyImmutableException();
  877. }
  878. $this->document = $document;
  879. }
  880. /**
  881. * @param k_DebugListener
  882. * @return void
  883. */
  884. function setDebugger(k_DebugListener $debugger) {
  885. $this->debugger = $debugger;
  886. }
  887. /**
  888. * @param string
  889. * @param mixed The default value to return, if the value doesn't exist
  890. * @return string
  891. */
  892. function query($key = null, $default = null) {
  893. return $this->url_state->get($key, $default);
  894. }
  895. /**
  896. * @param string
  897. * @param string
  898. * @return string
  899. */
  900. function body($key = null, $default = null) {
  901. return $this->context->body($key, $default);
  902. }
  903. function rawHttpRequestBody() {
  904. return $this->context->rawHttpRequestBody();
  905. }
  906. function header($key = null, $default = null) {
  907. return $this->context->header($key, $default);
  908. }
  909. function cookie($key = null, $default = null) {
  910. return $this->context->cookie($key, $default);
  911. }
  912. /**
  913. * @param string
  914. * @param mixed The default value to return, if the value doesn't exist
  915. * @return string
  916. */
  917. function session($key = null, $default = null) {
  918. return $this->context->session($key, $default);
  919. }
  920. function file($key = null, $default = null) {
  921. return $this->context->file($key, $default);
  922. }
  923. /**
  924. * @return string
  925. */
  926. function method() {
  927. return $this->context->method();
  928. }
  929. function requestUri() {
  930. return $this->context->requestUri();
  931. }
  932. /**
  933. * @return string
  934. */
  935. function serverName() {
  936. return $this->context->serverName();
  937. }
  938. function remoteAddr() {
  939. return $this->context->remoteAddr();
  940. }
  941. function identity() {
  942. return $this->context->identity();
  943. }
  944. function language() {
  945. return $this->context->language();
  946. }
  947. function translator() {
  948. return $this->context->translator();
  949. }
  950. function t($phrase, k_Language $language = null) {
  951. return $this->context->t($phrase, $language);
  952. }
  953. /*
  954. * @return k_Document
  955. */
  956. function document() {
  957. return $this->document;
  958. }
  959. /**
  960. * @param mixed
  961. * @param array
  962. * @return string
  963. */
  964. function url($path = "", $params = array()) {
  965. if (is_array($path)) {
  966. $path = implode('/', array_map('rawurlencode', $path));
  967. }
  968. return $this->context->url(
  969. $path
  970. ? (substr($path, 0, 1) === '/'
  971. ? $path
  972. : ($path === '.'
  973. ? $this->name()
  974. : (preg_match('~^\.([^/.]+)~', $path, $mm)
  975. ? ($this->name() . '.' . $mm[1])
  976. : $this->segment() . '/' . $path)))
  977. : $this->segment(),
  978. $this->url_state->merge($params));
  979. }
  980. /**
  981. * @return string
  982. */
  983. function subspace() {
  984. return preg_replace('~^[^/]*[/]{0,1}~', '', $this->context->subspace());
  985. }
  986. function negotiateContentType($candidates = array(), $user_override = null) {
  987. return $this->context->negotiateContentType($candidates, $user_override);
  988. }
  989. protected function contentTypeShortName() {
  990. $value = null;
  991. $parameters = array();
  992. foreach (explode(";", $this->header('content-type')) as $tuple) {
  993. if (preg_match('/^([^=]+)=(.+)$/', $tuple, $reg)) {
  994. $parameters[strtolower(trim($reg[1]))] = $reg[2];
  995. } elseif ($value !== null) {
  996. throw new Exception("HTTP parse error. Header line has multiple values");
  997. } else {
  998. $value = $tuple;
  999. }
  1000. }
  1001. $content_type = isset($value) ? $value : '';
  1002. $charset = isset($parameters['charset']) ? strtolower($parameters['charset']) : 'utf-8';
  1003. if ($charset !== 'utf-8') {
  1004. // This could probably be dealt with a bit more elegantly, but that would require us to re-implement PHP's native input-parsing.
  1005. // For now, if you get this error, you should just fix the problem by requiring your clients to speak utf-8.
  1006. throw new k_UnsupportedContentTypeCharsetException();
  1007. }
  1008. return isset($GLOBALS['konstrukt_content_types'][$content_type]) ? $GLOBALS['konstrukt_content_types'][$content_type] : null;
  1009. }
  1010. /**
  1011. * The full path segment for this components representation.
  1012. * @return string
  1013. */
  1014. protected function segment() {
  1015. if (preg_match('~^([^/]+)[/]{0,1}~', $this->context->subspace(), $mm)) {
  1016. return $mm[1];
  1017. }
  1018. // special case for top-level + subtype
  1019. if (preg_match('~^/(\.[^/]+)[/]{0,1}~', $this->context->subspace(), $mm)) {
  1020. return $mm[1];
  1021. }
  1022. }
  1023. /**
  1024. * The name part of the uri segment.
  1025. * @return string
  1026. */
  1027. protected function name() {
  1028. if ($segment = $this->segment()) {
  1029. return preg_replace('/\..*$/', '', $segment);
  1030. }
  1031. }
  1032. /**
  1033. * @return string
  1034. */
  1035. protected function subtype() {
  1036. if (preg_match('/\.(.+)$/', $this->segment(), $mm)) {
  1037. return $mm[1];
  1038. }
  1039. }
  1040. /**
  1041. * @return string
  1042. */
  1043. protected function next() {
  1044. if (preg_match('~^[^/.]+~', $this->subspace(), $mm)) {
  1045. return $mm[0];
  1046. }
  1047. }
  1048. /**
  1049. * @param string
  1050. * @param string A namespace for querystring parameters
  1051. * @return string
  1052. */
  1053. protected function forward($class_name, $namespace = "") {
  1054. if (is_array($class_name)) {
  1055. $namespace = $class_name[1];
  1056. $class_name = $class_name[0];
  1057. }
  1058. $next = $this->createComponent($class_name, $namespace);
  1059. return $next->dispatch();
  1060. }
  1061. /**
  1062. * @param string
  1063. * @param string
  1064. * @return k_Component
  1065. */
  1066. protected function createComponent($class_name, $namespace) {
  1067. return $this->component_creator->create($class_name, $this, $namespace);
  1068. }
  1069. /**
  1070. * @return string
  1071. */
  1072. function dispatch() {
  1073. $this->debugger->logDispatch($this, $this->name(), $this->next());
  1074. $next = $this->next();
  1075. if ($next) {
  1076. $class_name = $this->map($next);
  1077. if (!$class_name) {
  1078. throw new k_PageNotFound();
  1079. }
  1080. return $this->wrap($this->forward($class_name));
  1081. }
  1082. return $this->execute();
  1083. }
  1084. protected function map($name) {}
  1085. /**
  1086. * @return string
  1087. */
  1088. function execute() {
  1089. $method = $this->method();
  1090. if (!in_array($method, array('head','get','post','put','delete'))) {
  1091. throw new k_MethodNotAllowed();
  1092. }
  1093. return $this->{$method}();
  1094. }
  1095. function HEAD() {
  1096. throw new k_NotImplemented();
  1097. }
  1098. function GET() {
  1099. return $this->render();
  1100. }
  1101. function POST() {
  1102. $postfix = $this->contentTypeShortName();
  1103. if ($postfix) {
  1104. if (method_exists($this, 'post' . $postfix)) {
  1105. return $this->{'post' . $postfix}();
  1106. }
  1107. }
  1108. foreach (get_class_methods($this) as $m) {
  1109. if (preg_match('~^post.+~', $m)) {
  1110. // There is at least one acceptable handler for post
  1111. throw new k_NotAcceptable();
  1112. }
  1113. }
  1114. throw new k_NotImplemented();
  1115. }
  1116. function PUT() {
  1117. $postfix = $this->contentTypeShortName();
  1118. if ($postfix) {
  1119. if (method_exists($this, 'put' . $postfix)) {
  1120. return $this->{'put' . $postfix}();
  1121. }
  1122. }
  1123. foreach (get_class_methods($this) as $m) {
  1124. if (preg_match('~^put.+~', $m)) {
  1125. // There is at least one acceptable handler for put
  1126. throw new k_NotAcceptable();
  1127. }
  1128. }
  1129. throw new k_NotImplemented();
  1130. }
  1131. function DELETE() {
  1132. throw new k_NotImplemented();
  1133. }
  1134. /**
  1135. * Renders the components view.
  1136. * This method delegates control to an appropriate handler, based on content-type negotiation.
  1137. * The typical handler would be `renderHtml`. If no handler is present, an exception is raised.
  1138. */
  1139. function render() {
  1140. $accept = array();
  1141. foreach ($GLOBALS['konstrukt_content_types'] as $type => $name) {
  1142. $handler = 'render' . $name;
  1143. if (method_exists($this, $handler)) {
  1144. $accept[$type] = $handler;
  1145. $accept[$name] = $handler;
  1146. }
  1147. }
  1148. $content_type = $this->negotiateContentType(array_keys($accept), $this->subtype());
  1149. if (isset($accept[$content_type])) {
  1150. // subview dispatch
  1151. $subview = $this->subview();
  1152. $subhandler = $accept[$content_type] . $subview;
  1153. if ($subview && method_exists($this, $subhandler)) {
  1154. return k_coerce_to_response(
  1155. $this->{$subhandler}(),
  1156. k_content_type_to_response_type($content_type));
  1157. }
  1158. return k_coerce_to_response(
  1159. $this->{$accept[$content_type]}(),
  1160. k_content_type_to_response_type($content_type));
  1161. }
  1162. if (count($accept) > 0) {
  1163. throw new k_NotAcceptable();
  1164. }
  1165. throw new k_NotImplemented();
  1166. }
  1167. function wrap($content) {
  1168. $typed = k_coerce_to_response($content);
  1169. $response_type = ($typed instanceof k_HttpResponse) ? 'http' : k_content_type_to_response_type($typed->contentType());
  1170. $handler = 'wrap' . $response_type;
  1171. // is there a wrapper for this content-type ?
  1172. if (method_exists($this, $handler)) {
  1173. $wrapped = $this->{$handler}($typed->toContentType($typed->internalType()));
  1174. // if the wrapper returns a k_Response, we just pass it through
  1175. if ($wrapped instanceof k_Response) {
  1176. return $wrapped;
  1177. }
  1178. // if the content is a typed response, we clone its status + headers
  1179. if ($content instanceof k_Response) {
  1180. $wrapped = k_coerce_to_response($wrapped, $response_type);
  1181. $wrapped->setStatus($typed->status());
  1182. foreach ($typed->headers() as $key => $value) {
  1183. $wrapped->setHeader($key, $value);
  1184. }
  1185. $wrapped->setCharset($typed->charset());
  1186. }
  1187. return $wrapped;
  1188. }
  1189. // no wrapper ? do nothing.
  1190. return $content;
  1191. }
  1192. /**
  1193. * Returns the subview component of the URL, if any.
  1194. * @return string
  1195. */
  1196. function subview() {
  1197. if (preg_match('~^.*\?([^=&]+)~i', $this->requestUri(), $mm)) {
  1198. return urldecode($mm[1]);
  1199. }
  1200. }
  1201. }
  1202. /**
  1203. * Used for persisting state over the query-string, and for namespacing query-string parameters
  1204. */
  1205. class k_UrlState {
  1206. /** @var k_Context */
  1207. protected $context;
  1208. /** @var string */
  1209. protected $namespace;
  1210. /** @var array */
  1211. protected $state = array();
  1212. /** @var array */
  1213. protected $default_values = array();
  1214. /**
  1215. * @param k_Context
  1216. * @param string
  1217. * @return void
  1218. */
  1219. function __construct(k_Context $context, $namespace = "") {
  1220. $this->context = $context;
  1221. $this->namespace = $namespace;
  1222. }
  1223. /**
  1224. * @param string
  1225. * @param string
  1226. * @return void
  1227. */
  1228. function init($key, $default = "") {
  1229. $this->state[$this->namespace . $key] = $this->context->query($this->namespace . $key, (string) $default);
  1230. $this->default_values[$this->namespace . $key] = (string) $default;
  1231. }
  1232. function has($key) {
  1233. return isset($this->state[$this->namespace . $key]);
  1234. }
  1235. /**
  1236. * @param string
  1237. * @param mixed The default value to return, if the value doesn't exist
  1238. * @return string
  1239. */
  1240. function get($key, $default = null) {
  1241. return isset($this->state[$this->namespace . $key]) ? $this->state[$this->namespace . $key] : $this->context->query($this->namespace . $key, $default);
  1242. }
  1243. /**
  1244. * @param string
  1245. * @param string
  1246. * @return void
  1247. */
  1248. function set($key, $value) {
  1249. $this->state[$this->namespace . $key] = (string) $value;
  1250. }
  1251. /**
  1252. * @param array
  1253. * @return array
  1254. */
  1255. function merge($params = array()) {
  1256. $result = $this->state;
  1257. foreach ($params as $key => $value) {
  1258. $result[$this->namespace . $key] = $value;
  1259. }
  1260. // filter off default values, since they are implied
  1261. foreach ($result as $ns_key => $value) {
  1262. if (isset($this->default_values[$ns_key]) && $value == $this->default_values[$ns_key]) {
  1263. $result[$ns_key] = null;
  1264. }
  1265. }
  1266. return $result;
  1267. }
  1268. }
  1269. /**
  1270. * A metaresponse represents an abstract event in the application, which needs alternate handling.
  1271. * This would typically be an error-condition.
  1272. * In the simplest invocation, a metaresponse maps directly to a component, which renders a generic error.
  1273. */
  1274. abstract class k_MetaResponse extends Exception {
  1275. abstract function componentName();
  1276. }
  1277. /**
  1278. * Raise this if the user must be authorised to access the requested resource.
  1279. */
  1280. class k_NotAuthorized extends k_MetaResponse {
  1281. /** @var string */
  1282. protected $message = 'You must be authorised to access this resource';
  1283. function componentName() {
  1284. return 'k_DefaultNotAuthorizedComponent';
  1285. }
  1286. }
  1287. /**
  1288. * Raise this if the user doesn't have access to the requested resource.
  1289. */
  1290. class k_Forbidden extends k_MetaResponse {
  1291. /** @var string */
  1292. protected $message = 'The requested page is forbidden';
  1293. function componentName() {
  1294. return 'k_DefaultForbiddenComponent';
  1295. }
  1296. }
  1297. /**
  1298. * Raise this if the requested resource couldn't be found.
  1299. */
  1300. class k_PageNotFound extends k_MetaResponse {
  1301. /** @var string */
  1302. protected $message = 'The requested page was not found';
  1303. function componentName() {
  1304. return 'k_DefaultPageNotFoundComponent';
  1305. }
  1306. }
  1307. /**
  1308. * Raise this if resource doesn't support the requested HTTP method.
  1309. * @see k_NotImplemented
  1310. */
  1311. class k_MethodNotAllowed extends k_MetaResponse {
  1312. /** @var string */
  1313. protected $message = 'The request HTTP method is not supported by the handling component';
  1314. function componentName() {
  1315. return 'k_DefaultMethodNotAllowedComponent';
  1316. }
  1317. }
  1318. /**
  1319. * Raise this if the request isn't yet implemented.
  1320. * This is roughly the HTTP equivalent to a "todo"
  1321. */
  1322. class k_NotImplemented extends k_MetaResponse {
  1323. /** @var string */
  1324. protected $message = 'The server does not support the functionality required to fulfill the request';
  1325. function componentName() {
  1326. return 'k_DefaultNotImplementedComponent';
  1327. }
  1328. }
  1329. /**
  1330. * Raise this if the request can't be processed due to details of the request (Such as the Content-Type or other headers)
  1331. */
  1332. class k_NotAcceptable extends k_MetaResponse {
  1333. /** @var string */
  1334. 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';
  1335. function componentName() {
  1336. return 'k_DefaultNotNotAcceptableComponent';
  1337. }
  1338. }
  1339. /**
  1340. * @see k_NotAuthorized
  1341. */
  1342. class k_DefaultNotAuthorizedComponent extends k_Component {
  1343. function dispatch() {
  1344. $response = $this->render();
  1345. $response->setStatus(401);
  1346. $response->setHeader('WWW-Authenticate', 'Basic realm="Restricted"');
  1347. return $response;
  1348. }
  1349. function render() {
  1350. return new k_HtmlResponse('<html><body><h1>HTTP 401 - Not Authorized</h1></body></html>');
  1351. }
  1352. }
  1353. /**
  1354. * @see k_Forbidden
  1355. */
  1356. class k_DefaultForbiddenComponent extends k_Component {
  1357. function dispatch() {
  1358. $response = $this->render();
  1359. $response->setStatus(403);
  1360. return $response;
  1361. }
  1362. function render() {
  1363. return new k_HtmlResponse('<html><body><h1>HTTP 403 - Forbidden</h1></body></html>');
  1364. }
  1365. }
  1366. /**
  1367. * @see k_PageNotFound
  1368. */
  1369. class k_DefaultPageNotFoundComponent extends k_Component {
  1370. function dispatch() {
  1371. $response = $this->render();
  1372. $response->setStatus(404);
  1373. return $response;
  1374. }
  1375. function render() {
  1376. return new k_HtmlResponse('<html><body><h1>HTTP 404 - Page Not Found</h1></body></html>');
  1377. }
  1378. }
  1379. /**
  1380. * @see k_MethodNotAllowed
  1381. */
  1382. class k_DefaultMethodNotAllowedComponent extends k_Component {
  1383. function dispatch() {
  1384. $response = $this->render();
  1385. $response->setStatus(405);
  1386. return $response;
  1387. }
  1388. function render() {
  1389. return new k_HtmlResponse('<html><body><h1>HTTP 405 - Method Not Allowed</h1></body></html>');
  1390. }
  1391. }
  1392. /**
  1393. * @see k_NotAcceptable
  1394. */
  1395. class k_DefaultNotNotAcceptableComponent extends k_Component {
  1396. function dispatch() {
  1397. $response = $this->render();
  1398. $response->setStatus(406);
  1399. return $response;
  1400. }
  1401. function render() {
  1402. return new k_HtmlResponse('<html><body><h1>HTTP 406 - Not Acceptable</h1></body></html>');
  1403. }
  1404. }
  1405. /**
  1406. * @see k_NotImplemented
  1407. */
  1408. class k_DefaultNotImplementedComponent extends k_Component {
  1409. function dispatch() {
  1410. $response = $this->render();
  1411. $response->setStatus(501);
  1412. return $response;
  1413. }
  1414. function render() {
  1415. return new k_HtmlResponse('<html><body><h1>HTTP 501 - Not Implemented</h1></body></html>');
  1416. }
  1417. }
  1418. /**
  1419. * Creates an application bootstrap.
  1420. * @return k_Bootstrap
  1421. */
  1422. function k() {
  1423. return new k_Bootstrap();
  1424. }
  1425. /**
  1426. * Application bootstrap.
  1427. */
  1428. class k_Bootstrap {
  1429. /** @var k_HttpRequest */
  1430. protected $http_request;
  1431. /** @var k_ComponentCreator */
  1432. protected $components;
  1433. /** @var k_charset_CharsetStrategy */
  1434. protected $charset_strategy;
  1435. /** @var boolean */
  1436. protected $is_debug = false;
  1437. /** @var string */
  1438. protected $log_filename = null;
  1439. /** @var string */
  1440. protected $href_base = null;
  1441. /** @var k_IdentityLoader */
  1442. protected $identity_loader;
  1443. /** @var k_LanguageLoader */
  1444. protected $language_loader;
  1445. /** @var k_TranslatorLoader */
  1446. protected $translator_loader;
  1447. /** @var k_adapter_GlobalsAccess */
  1448. protected $globals_access;
  1449. /**
  1450. * Serves a http request, given a root component name.
  1451. * @param $root_class_name string The classname of an instance of k_Component
  1452. * @return k_Response
  1453. */
  1454. function run($root_class_name) {
  1455. $debugger = new k_MultiDebugListener();
  1456. $this->components()->setDebugger($debugger);
  1457. if ($this->is_debug) {
  1458. $debugger->add(new k_logging_WebDebugger());
  1459. }
  1460. if ($this->log_filename) {
  1461. $debugger->add(new k_logging_LogDebugger($this->log_filename));
  1462. }
  1463. try {
  1464. $debugger->logRequestStart($this->context());
  1465. return $debugger->decorate($this->dispatchRoot($root_class_name));
  1466. } catch (Exception $ex) {
  1467. $debugger->logException($ex);
  1468. throw $ex;
  1469. }
  1470. }
  1471. protected function dispatchRoot($root_class_name) {
  1472. try {
  1473. $class_name = $root_class_name;
  1474. while (true) {
  1475. try {
  1476. $root = $this->components()->create($class_name, $this->context());
  1477. $response = $root->dispatch();
  1478. if (!($response instanceof k_Response)) {
  1479. $response = new k_HtmlResponse($response);
  1480. }
  1481. $response->setCharset($this->charsetStrategy()->responseCharset());
  1482. return $response;
  1483. } catch (k_MetaResponse $ex) {
  1484. $class_name = $ex->componentName();
  1485. }
  1486. }
  1487. } catch (k_Response $ex) {
  1488. return $ex;
  1489. }
  1490. }
  1491. /**
  1492. * Sets the context to use. Usually, this is an instance of k_HttpRequest.
  1493. * @param k_Context
  1494. * @return k_Bootstrap
  1495. */
  1496. function setContext(k_Context $http_request) {
  1497. $this->http_request = $http_request;
  1498. return $this;
  1499. }
  1500. /**
  1501. * Sets the componentcreator to use.
  1502. * @param k_ComponentCreator
  1503. * @return k_Bootstrap
  1504. */
  1505. function setComponentCreator(k_ComponentCreator $components) {
  1506. $this->components = $components;
  1507. return $this;
  1508. }
  1509. /**
  1510. * Set the charsetstrategy.
  1511. * @param k_charset_CharsetStrategy
  1512. * @return k_Bootstrap
  1513. */
  1514. function setCharsetStrategy(k_charset_CharsetStrategy $charset_strategy) {
  1515. $this->charset_strategy = $charset_strategy;
  1516. return $this;
  1517. }
  1518. /**
  1519. * Set the identity loader.
  1520. * @param k_IdentityLoader
  1521. * @return k_Bootstrap
  1522. */
  1523. function setIdentityLoader(k_IdentityLoader $identity_loader) {
  1524. $this->identity_loader = $identity_loader;
  1525. return $this;
  1526. }
  1527. /**
  1528. * Set the language loader.
  1529. * @param k_LanguageLoader
  1530. * @return k_Bootstrap
  1531. */
  1532. function setLanguageLoader(k_LanguageLoader $language_loader) {
  1533. $this->language_loader = $language_loader;
  1534. return $this;
  1535. }
  1536. /**
  1537. * Set the translator loader.
  1538. * @param k_TranslatorLoader
  1539. * @return k_Bootstrap
  1540. */
  1541. function setTranslatorLoader(k_TranslatorLoader $translator_loader) {
  1542. $this->translator_loader = $translator_loader;
  1543. return $this;
  1544. }
  1545. /**
  1546. * Enable/disable the in-browser debug-bar.
  1547. * @param boolean
  1548. * @return k_Bootstrap
  1549. */
  1550. function setDebug($is_debug = true) {
  1551. $this->is_debug = !! $is_debug;
  1552. return $this;
  1553. }
  1554. /**
  1555. * Specifies a filename to log debug information to.
  1556. * @param string
  1557. * @return k_Bootstrap
  1558. */
  1559. function setLog($filename) {
  1560. $this->log_filename = $filename;
  1561. return $this;
  1562. }
  1563. /**
  1564. * Sets the base href, if the application isn't mounted at the web root.
  1565. * @param string
  1566. * @return k_Bootstrap
  1567. */
  1568. function setHrefBase($href_base) {
  1569. $this->href_base = $href_base;
  1570. return $this;
  1571. }
  1572. /**
  1573. * @return k_Context
  1574. */
  1575. protected function context() {
  1576. if (!isset($this->http_request)) {
  1577. $this->http_request = new k_HttpRequest($this->href_base, null, $this->identityLoader(), $this->languageLoader(), $this->translatorLoader(), $this->globalsAccess());
  1578. }
  1579. return $this->http_request;
  1580. }
  1581. /**
  1582. * @return k_ComponentCreator
  1583. */
  1584. protected function components() {
  1585. if (!isset($this->components)) {
  1586. $this->components = new k_DefaultComponentCreator();
  1587. }
  1588. return $this->components;
  1589. }
  1590. /**
  1591. * @return k_charset_CharsetStrategy
  1592. */
  1593. protected function charsetStrategy() {
  1594. if (!isset($this->charset_strategy)) {
  1595. $this->charset_strategy = new k_charset_Utf8CharsetStrategy();
  1596. }
  1597. return $this->charset_strategy;
  1598. }
  1599. /**
  1600. * @return k_IdentityLoader
  1601. */
  1602. protected function identityLoader() {
  1603. if (!isset($this->identity_loader)) {
  1604. $this->identity_loader = new k_DefaultIdentityLoader();
  1605. }
  1606. return $this->identity_loader;
  1607. }
  1608. /**
  1609. * @return mixed k_LanguageLoader or null
  1610. */
  1611. protected function languageLoader() {
  1612. return $this->language_loader;
  1613. }
  1614. /**
  1615. * @return mixed k_TranslatorLoader or null
  1616. */
  1617. protected function translatorLoader() {
  1618. return $this->translator_loader;
  1619. }
  1620. /**
  1621. * @return k_adapter_GlobalsAccess
  1622. */
  1623. protected function globalsAccess() {
  1624. if (!isset($this->globals_access)) {
  1625. $this->globals_access = new k_adapter_SafeGlobalsAccess($this->charsetStrategy());
  1626. }
  1627. return $this->globals_access;
  1628. }
  1629. }
  1630. /**
  1631. * Resolves a filename according to the includepath.
  1632. * Returns on the first match or false if no match is found.
  1633. */
  1634. function k_search_include_path($filename) {
  1635. if (is_file($filename)) {
  1636. return $filename;
  1637. }
  1638. foreach (explode(PATH_SEPARATOR, ini_get("include_path")) as $path) {
  1639. if (strlen($path) > 0 && $path{strlen($path)-1} != DIRECTORY_SEPARATOR) {
  1640. $path .= DIRECTORY_SEPARATOR;
  1641. }
  1642. $f = realpath($path . $filename);
  1643. if ($f && is_file($f)) {
  1644. return $f;
  1645. }
  1646. }
  1647. return false;
  1648. }
  1649. /**
  1650. * A simple autoloader.
  1651. */
  1652. function k_autoload($classname) {
  1653. $filename = str_replace('_', '/', strtolower($classname)).'.php';
  1654. if (k_search_include_path($filename)) {
  1655. require_once($filename);
  1656. }
  1657. }
  1658. /**
  1659. * An error-handler which converts all errors (regardless of level) into exceptions.
  1660. * It respects error_reporting settings.
  1661. */
  1662. function k_exceptions_error_handler($severity, $message, $filename, $lineno) {
  1663. if (error_reporting() == 0) {
  1664. return;
  1665. }
  1666. if (error_reporting() & $severity) {
  1667. throw new ErrorException($message, 0, $severity, $filename, $lineno);
  1668. }
  1669. }