PageRenderTime 57ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/vendor/konstrukt/lib/konstrukt/konstrukt.inc.php

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