1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
16:
17: require_once(dirname(__FILE__) . '/Pinoco/_bootstrap.php');
18:
19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70:
71: class Pinoco extends Pinoco_DynamicVars
72: {
73: const VERSION = "0.8.0";
74:
75: private $_baseuri;
76: private $_basedir;
77: private $_sysdir;
78: private $_testing;
79:
80: private $_incdir;
81: private $_request;
82:
83: private $_path;
84: private $_script;
85: private $_activity;
86: private ;
87: private $_subpath;
88: private $_pathargs;
89: private $_directory_index;
90:
91: private $_renderers;
92: private $_page;
93:
94: private $_autolocal;
95:
96: private $_url_modifier;
97: private $_page_modifier;
98:
99:
100: private $_system_incdir;
101: private $_dispatcher;
102: private $_manually_rendered;
103: private $_script_include_stack;
104:
105: private static $_current_instance = null;
106:
107: 108: 109: 110: 111: 112: 113:
114: public static function create($sysdir, $options=array())
115: {
116:
117: $use_mod_rewrite = isset($options['use_mod_rewrite']) ? $options['use_mod_rewrite'] : true;
118: $use_path_info = isset($options['use_path_info']) ? $options['use_path_info'] : true;
119: $custom_path_info = isset($options['custom_path_info']) ? $options['custom_path_info'] : false;
120:
121:
122: $pathinfo_name = ($custom_path_info !== false) ? $custom_path_info : 'PATH_INFO';
123: if ($use_path_info) {
124: $pathinfo = isset($_SERVER[$pathinfo_name]) ? $_SERVER[$pathinfo_name] : @getenv('PATH_INFO');
125: }
126: else {
127: $pathinfo = isset($_GET[$pathinfo_name]) ? $_GET[$pathinfo_name] : "";
128: }
129:
130:
131: $path = $pathinfo;
132: if (!preg_match('/^\//', $path)) {
133: $path = "/" . $path;
134: }
135:
136:
137: if ($use_mod_rewrite) {
138: $gateway = "";
139: $dispatcher = "";
140: }
141: else {
142: $gateway = basename($_SERVER['SCRIPT_NAME']);
143: $dispatcher = "/" . $gateway;
144: if (!$use_path_info) {
145: $dispatcher .= "?" . $pathinfo_name . "=";
146: }
147: }
148:
149:
150: $uri = urldecode($_SERVER['REQUEST_URI']);
151: $seppos = strpos($uri, '?');
152: if ($seppos !== false) {
153: $uri = substr($uri, 0, $seppos);
154: }
155: $seppos = strpos($uri, '#');
156: if ($seppos !== false) {
157: $uri = substr($uri, 0, $seppos);
158: }
159: if ($use_mod_rewrite) {
160: $trailings = $pathinfo;
161: if (strpos($_SERVER['REQUEST_URI'], "/" . basename($_SERVER['SCRIPT_NAME'])) !== false) {
162: $trailings = "/" . basename($_SERVER['SCRIPT_NAME']) . $pathinfo;
163: }
164: }
165: elseif ($use_path_info) {
166: $trailings = "/" . $gateway . $pathinfo;
167: }
168: else {
169: $trailings = "/" . $gateway;
170: }
171:
172:
173: $uri = preg_replace('/\/\/+/', '/', $uri);
174: $trailings = preg_replace('/\/\/+/', '/', $trailings);
175:
176: $baseuri = substr($uri, 0, strlen($uri) - strlen($trailings));
177:
178:
179: return new self($baseuri, $dispatcher, $path, dirname($_SERVER['SCRIPT_FILENAME']), $sysdir);
180: }
181:
182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193:
194: function __construct($baseuri, $dispatcher, $path, $basedir, $sysdir, $testing=false)
195: {
196: $this->_testing = $testing;
197:
198: $this->_baseuri = $baseuri;
199: $this->_dispatcher = $dispatcher;
200: $this->_path = $path;
201: $this->_basedir = realpath($basedir);
202: if (!is_dir($this->_basedir)) {
203: throw new InvalidArgumentException("Invalid base directory:" . $basedir . " does not exist.");
204: }
205: $this->_sysdir = realpath($sysdir);
206: if (!is_dir($this->_sysdir)) {
207: throw new InvalidArgumentException("Invalid system directory:" . $sysdir . " does not exist.");
208: }
209:
210: $this->_incdir = self::newList();
211: $this->_incdir->push($this->sysdir . "/lib");
212:
213: $this->_system_incdir = get_include_path();
214:
215: if ($this->_path[strlen($this->_path) - 1] != '/' &&
216: (is_dir($this->_basedir . $this->_path) || is_dir($this->_sysdir . "/hooks" . $this->_path))) {
217: $this->_path .= '/';
218: }
219:
220: $this->_directory_index = "index.html index.php";
221:
222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234:
235:
236: $this->_request = new Pinoco_HttpRequestVars($this);
237:
238: $this->_script = null;
239: $this->_activity = self::newList();
240: $this->_sent_headers = self::newList();
241: $this->_subpath = null;
242: $this->_pathargs = self::newList();
243:
244: $this->_renderers = self::newVars();
245: $this->_renderers->setDefault(new Pinoco_NullRenderer($this));
246: $this->_renderers->set('html', new Pinoco_TALRenderer($this));
247: $this->_renderers->set('php', new Pinoco_NativeRenderer($this));
248:
249: $this->_page = "<"."default>";
250:
251: $this->_autolocal = self::newVars();
252:
253: $this->_url_modifier = null;
254: $this->_page_modifier = null;
255:
256: parent::__construct();
257:
258:
259: }
260:
261: public function __toString() { return __CLASS__ . " " . self::VERSION; }
262:
263: 264: 265: 266: 267: 268: 269: 270:
271: public function config($name, $source)
272: {
273: if (is_string($source)) {
274: $file = $this->_sysdir . '/' . ltrim($source, '/');
275: if (!is_file($file)) {
276: return $this;
277: }
278: $ext = pathinfo($file, PATHINFO_EXTENSION);
279: switch ($ext) {
280: case 'ini':
281: $source = parse_ini_file($file, true);
282: if ($source === false) {
283: throw new InvalidArgumentException('Can\'t load config file: ' . $source);
284: }
285: foreach ($source as &$v) {
286: if (is_array($v)) {
287: $v = Pinoco_Vars::fromArray($v);
288: }
289: }
290: break;
291: case 'php':
292: $source = require $file;
293: if (!(is_array($source) || is_object($source) && ($source instanceof Pinoco_ArrayConvertible))) {
294: throw new InvalidArgumentException('Can\'t load config file: ' . $source);
295: }
296: break;
297: default:
298: throw new InvalidArgumentException('Can\'t load config file: ' . $source);
299: }
300: }
301: elseif (!is_array($source)) {
302: throw new InvalidArgumentException('Can\'t load config.');
303: }
304: if (!$this->has($name)) {
305: $this->set($name, new Pinoco_Vars());
306: }
307: $this->get($name)->import($source);
308: return $this;
309: }
310:
311:
312: 313: 314: 315: 316: 317:
318: public static function newVars($init=array())
319: {
320: return Pinoco_Vars::fromArray($init);
321: }
322:
323: 324: 325: 326: 327: 328:
329: public static function newList($init=array())
330: {
331: return Pinoco_List::fromArray($init);
332: }
333:
334: 335: 336: 337: 338:
339: public static function newNothing()
340: {
341: return Pinoco_NothingVars::instance();
342: }
343:
344: 345: 346: 347: 348: 349:
350: public static function wrapVars(&$ref)
351: {
352: return Pinoco_Vars::wrap($ref);
353: }
354:
355: 356: 357: 358: 359: 360:
361: public static function wrapList(&$ref)
362: {
363: return Pinoco_List::wrap($ref);
364: }
365:
366: 367: 368: 369: 370: 371: 372: 373: 374:
375: public static function newObj($class, $args=Pinoco_OptionalParam::UNSPECIFIED)
376: {
377: $seppos = strrpos($class, '/');
378: if ($seppos !== false) {
379: $srcfile = substr($class, 0, $seppos);
380: $class = substr($class, $seppos + 1);
381: require_once $srcfile;
382: }
383: if (class_exists($class)) {
384: $argsvals = Pinoco_OptionalParam::trim(func_get_args());
385: array_shift($argsvals);
386: $argsvars = array();
387: for ($i = 0; $i < count($argsvals); $i++) {
388: $argsvars[$i] = '$argsvals[' . $i . ']';
389: }
390: $object = null;
391: eval(sprintf('$object = new %s(%s);', $class, implode(', ', $argsvars)));
392: return $object;
393: }
394: else {
395: if ($seppos !== false) {
396:
397: throw new InvalidArgumentException($class . " may not be defined on " . $srcfile . ".");
398: }
399: else {
400: throw new InvalidArgumentException($class . " is not defined.");
401: }
402: }
403: }
404:
405: 406: 407: 408: 409: 410: 411: 412: 413: 414:
415: public static function newPDOWrapper($dsn, $un="", $pw="", $opts=array())
416: {
417: return self::newObj(
418: 'Pinoco/PDOWrapper.php/Pinoco_PDOWrapper',
419: $dsn, $un, $pw, $opts
420: );
421: }
422:
423:
424: 425: 426: 427: 428:
429: public function get_baseuri() { return $this->_baseuri; }
430:
431: 432: 433: 434: 435:
436: public function get_basedir() { return $this->_basedir; }
437:
438: 439: 440: 441: 442:
443: public function get_sysdir() { return $this->_sysdir; }
444:
445: 446: 447: 448: 449:
450: public function get_testing() { return $this->_testing; }
451:
452: 453: 454: 455: 456:
457: public function get_incdir() { return $this->_incdir; }
458:
459: 460: 461: 462: 463:
464: public function get_request() { return $this->_request; }
465:
466: 467: 468: 469: 470:
471: public function get_path() { return $this->_path; }
472:
473: 474: 475: 476: 477:
478: public function get_script() { return $this->_script; }
479:
480: 481: 482: 483: 484:
485: public function get_activity() { return $this->_activity; }
486:
487: 488: 489: 490: 491:
492: public function () { return $this->_sent_headers; }
493:
494: 495: 496: 497: 498:
499: public function get_subpath() { return $this->_subpath; }
500:
501: 502: 503: 504: 505:
506: public function get_pathargs() { return $this->_pathargs; }
507:
508: 509: 510: 511: 512:
513: public function get_directory_index() { return $this->_directory_index; }
514:
515: 516: 517: 518: 519:
520: public function get_page() { return $this->_page; }
521:
522: 523: 524: 525: 526: 527:
528: public function get_renderers() { return $this->_renderers; }
529:
530: 531: 532: 533: 534:
535: public function get_autolocal() { return $this->_autolocal; }
536:
537: 538: 539: 540: 541:
542: public function get_url_modifier() { return $this->_url_modifier; }
543:
544: 545: 546: 547: 548:
549: public function get_page_modifier() { return $this->_page_modifier; }
550:
551: 552: 553: 554: 555: 556:
557: public function set_incdir($dirs) { $this->_incdir = $dirs; }
558:
559: 560: 561: 562: 563: 564: 565: 566:
567: public function set_page($page) { $this->_page = $page; }
568:
569: 570: 571: 572: 573: 574:
575: public function set_directory_index($files) { $this->_directory_index = $files; }
576:
577: 578: 579: 580: 581: 582:
583: public function set_url_modifier($callable) { $this->_url_modifier = $callable; }
584:
585: 586: 587: 588: 589: 590:
591: public function set_page_modifier($callable) { $this->_page_modifier = $callable; }
592:
593:
594: 595: 596: 597: 598: 599:
600: public static function skip()
601: {
602: throw new Pinoco_FlowControlSkip();
603: }
604:
605: 606: 607: 608: 609: 610:
611: public static function terminate()
612: {
613: throw new Pinoco_FlowControlTerminate();
614: }
615:
616: 617: 618: 619: 620: 621: 622: 623: 624:
625: public static function error($code, $title=null, $message=null)
626: {
627: throw new Pinoco_FlowControlHttpError($code, $title, $message);
628: }
629:
630: 631: 632: 633: 634: 635: 636: 637:
638: public static function redirect($url, $external=false)
639: {
640: throw new Pinoco_FlowControlHttpRedirect($url, $external);
641: }
642:
643: 644: 645: 646: 647: 648:
649: public static function notfound()
650: {
651: self::error(404);
652: }
653:
654: 655: 656: 657: 658: 659:
660: public static function forbidden()
661: {
662: self::error(403);
663: }
664:
665: 666: 667: 668: 669:
670: public function nocache()
671: {
672: $this->header('Cache-Control: no-cache');
673: $this->header('Expires: ' . gmdate('D, d M Y H:i:s T', 0));
674: }
675:
676: 677: 678: 679: 680: 681: 682: 683: 684: 685:
686: public function abortIfNotModified($timestamp=null, $etag=null, $lifetime=86400)
687: {
688: $server = $this->request->server;
689:
690: $pragma = $server->get('HTTP_PRAGMA');
691: $cache_control = $server->get('HTTP_CACHE_CONTROL');
692: $ignore_cache = false;
693: if ($pragma == 'no-cache' || $cache_control == 'no-cache') {
694: $ignore_cache = true;
695: }
696: $modified = true;
697: if(!$ignore_cache && $lifetime > 0) {
698: if ($modified && $timestamp !== null) {
699: if ($timestamp <= @strtotime($server->get('HTTP_IF_MODIFIED_SINCE'))) {
700: $modified = false;
701: }
702: }
703: if ($modified && $etag !== null) {
704: if ($etag == trim($server->get('HTTP_IF_NONE_MATCH'), '" ')) {
705: $modified = false;
706: }
707: }
708: }
709: if ($timestamp !== null) {
710: $this->header('Last-modified: ' . gmdate('D, d M Y H:i:s T', $timestamp));
711: }
712: if ($etag !== null) {
713: $this->header('ETag: "' . $etag . '"');
714: }
715: if ($lifetime > 0) {
716: if ($timestamp !== null || $etag !== null) {
717: $this->header('Cache-Control: max-age=' . $lifetime);
718: $this->header('Expires: ' . gmdate('D, d M Y H:i:s T', time() + $lifetime));
719: }
720: }
721: else {
722: $this->nocache();
723: }
724: if (!$modified) {
725: self::error('304');
726: }
727: }
728:
729: 730: 731: 732: 733: 734: 735: 736: 737:
738: public function serveStatic($filename, $lifetime=86400, $mime_type=null)
739: {
740: if (!is_file($filename)) {
741: self::error(404);
742: }
743: if($lifetime > 0) {
744: $stat = stat($filename);
745: $last_modified = max($stat['mtime'], $stat['ctime']);
746: $etag = md5(file_get_contents($filename, false, null, 0, 1024) . $last_modified);
747: $this->abortIfNotModified($last_modified, $etag, $lifetime);
748: }
749: if (!$mime_type) {
750: $mime_type = self::mimeType($filename);
751: }
752: $this->header('Content-type: ' . $mime_type);
753: readfile($filename);
754: $this->render(false);
755: $this->terminate();
756: }
757:
758: public function router()
759: {
760: return new Pinoco_Router($this);
761: }
762:
763: 764: 765: 766: 767: 768:
769: public static function parentPath($path)
770: {
771: $dn = dirname($path);
772: if ($dn == "\\") { $dn = "/"; }
773: if ($dn == ".") { $dn = ""; }
774: return $dn;
775: }
776:
777: 778: 779: 780: 781: 782: 783:
784: public function resolvePath($path, $base=false)
785: {
786: if (strlen($path) > 0 && $path[0] != '/') {
787:
788: if ($base === false) {
789: $thispath = $this->_path;
790: $base = $thispath[strlen($thispath) - 1] != "/" ?
791: self::parentPath($thispath) : rtrim($thispath, "/");
792: }
793: $bes = explode("/", rtrim($base, "/"));
794: $pes = explode("/", $path);
795: foreach ($pes as $pe) {
796: if ($pe == "..") {
797: array_pop($bes);
798: }
799: elseif ($pe != ".") {
800: array_push($bes, $pe);
801: }
802: }
803: return implode("/", $bes);
804: }
805: else {
806: return $path;
807: }
808: }
809:
810: 811: 812: 813: 814: 815:
816: public function isRenderablePath($path)
817: {
818: $sepp = strpos($path, "?");
819: if ($sepp !== false) { $path = substr($path, 0, $sepp); }
820: $sepp = strpos($path, "#");
821: if ($sepp !== false) { $path = substr($path, 0, $sepp); }
822: $ext = pathinfo($path, PATHINFO_EXTENSION);
823: return $ext && $this->_renderers->has($ext);
824: }
825:
826: 827: 828: 829: 830: 831: 832: 833:
834: public function header($string, $replace=true, $http_response_code=null)
835: {
836: $name = null;
837: $is_http = preg_match('/^HTTP\\//', $string);
838: if (!$is_http && preg_match('/^(.+?)\\s*:/', $string, $m)) {
839: $name = $m[1];
840: }
841:
842: if ($is_http || $replace) {
843: $tmp = self::newList();
844: foreach ($this->_sent_headers as $h) {
845: if ($is_http && preg_match('/^HTTP\\//', $h) ||
846: $name !== null && preg_match('/^' . preg_quote($name) . '\\s*:/i', $h)
847: ) {
848: continue;
849: }
850: $tmp->push($h);
851: }
852: $tmp->push($string);
853: $this->_sent_headers = $tmp;
854: }
855: else {
856: $this->_sent_headers->push($string);
857: }
858:
859: if (!$this->_testing && !headers_sent()) {
860: if (!is_null($http_response_code)) {
861: @header($string, $replace, $http_response_code);
862: }
863: else {
864: @header($string, $replace);
865: }
866: return true;
867: }
868: else {
869: return false;
870: }
871: }
872:
873: 874: 875: 876: 877: 878: 879: 880: 881: 882: 883: 884:
885: public function setcookie($name, $value=null, $expire=0, $path=null, $domain=null, $secure=false, $httponly=false)
886: {
887: $tmp = array();
888: if (!empty($value)) {
889: $tmp[] = urlencode($name) . '=' . urlencode($value);
890: if ($expire != 0) {
891: $tmp[] = 'expires=' . gmdate("D, d-M-Y H:i:s T", $expire);
892: }
893: }
894: else {
895:
896: $tmp[] = urlencode($name) . '=';
897: $tmp[] = 'expires=' . gmdate("D, d-M-Y H:i:s T", time() - 31536001);
898: }
899:
900: if (empty($path)) {
901: $tmp[] = 'path=' . rtrim($this->baseuri, '/') . '/';
902: }
903: else {
904: $tmp[] = 'path=' . $path;
905: }
906:
907: if (!empty($domain)) { $tmp[] = 'domain=' . $domain; }
908: if ($secure) { $tmp[] = 'secure'; }
909: if ($httponly) { $tmp[] = 'httponly'; }
910:
911: return $this->header('Set-Cookie: ' . implode('; ', $tmp), false);
912: }
913:
914: 915: 916: 917: 918: 919: 920:
921: public function url($path='', $pure=false)
922: {
923: if ($path != '') {
924: $path = $this->resolvePath($path);
925: }
926: $renderable = $this->isRenderablePath($path) ||
927: !is_file($this->_basedir . $path) ||
928: is_dir($this->_basedir . $path);
929:
930:
931: if ($this->_dispatcher != "" && $renderable) {
932:
933: $dqpos = strpos($this->_dispatcher, "?");
934: $pqpos = strpos($path, "?");
935: if ($dqpos !== false && $pqpos !== false) {
936: $path = substr($path, 0, $pqpos) . "&" . substr($path, $pqpos + 1);
937: }
938: $url = rtrim($this->_baseuri, "/") . $this->_dispatcher . $path;
939: }
940: else {
941: $url = rtrim($this->_baseuri, "/") . $path;
942: }
943: return (!$pure && $this->_url_modifier != null) ? call_user_func($this->_url_modifier, $url, $renderable) : $url;
944: }
945:
946: 947: 948: 949: 950: 951: 952: 953:
954: public function _page_from_path_with_directory_index($path, $last_pathelem=null)
955: {
956: $page = "";
957: $pes = explode("/", $path);
958: array_shift($pes);
959: while (count($pes) > 1) {
960: $pe = array_shift($pes);
961: if (is_dir($this->_basedir . $page . '/' . $pe)) {
962: $page .= '/' . $pe;
963: }
964: elseif (is_dir($this->_basedir . $page . '/_default')) {
965: $page .= '/_default';
966: }
967: else {
968: return false;
969: }
970: }
971: $page .= '/' . $pes[0];
972:
973: if ($page[strlen($page) - 1] == "/") {
974: $di = "";
975: $idxs = explode(" ", $this->_directory_index);
976: if ($last_pathelem && in_array($last_pathelem, $idxs)) {
977: $di = $last_pathelem;
978: }
979: if ($di == "") {
980: foreach ($idxs as $idx) {
981: if (is_file($this->_basedir . $page . $idx)) {
982: $di = $idx;
983: break;
984: }
985: }
986: }
987: if ($di == "") {
988: foreach ($idxs as $idx) {
989: $deffile = "_default." . pathinfo($idx, PATHINFO_EXTENSION);
990: if (is_file($this->_basedir . $page . $deffile)) {
991: $di = $deffile;
992: break;
993: }
994: }
995: }
996: $page .= $di;
997: }
998: if (is_file($this->_basedir . $page)) {
999: return $page;
1000: }
1001: else {
1002: $ext = pathinfo($page, PATHINFO_EXTENSION);
1003: if ($ext && $this->_renderers->has($ext)) {
1004: $default_page = self::parentPath($page) . "/_default." . $ext;
1005: if (is_file($this->_basedir . $default_page)) {
1006: return $default_page;
1007: }
1008: }
1009: }
1010: return false;
1011: }
1012:
1013: 1014: 1015: 1016: 1017: 1018: 1019: 1020: 1021:
1022: public function render($page)
1023: {
1024: if (!$page) {
1025: $this->_manually_rendered = true;
1026: return;
1027: }
1028: $page = $this->resolvePath($page);
1029: $ext = pathinfo($page, PATHINFO_EXTENSION);
1030: if ($ext && is_file($this->_basedir . '/' . $page) && isset($this->_renderers[$ext])) {
1031:
1032: $renderer = $this->_renderers[$ext];
1033: $renderer->prepareAndRender($page);
1034: }
1035: else {
1036: throw new InvalidArgumentException("File $page is not exists or not renderable.");
1037: }
1038: $this->_manually_rendered = true;
1039: }
1040:
1041: 1042: 1043: 1044: 1045: 1046:
1047: public static function mimeType($filename)
1048: {
1049: 1050: 1051: 1052: 1053: 1054: 1055: 1056: 1057: 1058: 1059: 1060: 1061: 1062: 1063: 1064: 1065: 1066: 1067:
1068:
1069: include_once dirname(__FILE__) . '/Pinoco/MIMEType.php';
1070: return Pinoco_MIMEType::fromFileName($filename);
1071: }
1072:
1073: 1074: 1075: 1076: 1077:
1078: public static function instance()
1079: {
1080: return self::$_current_instance;
1081: }
1082:
1083: public static function __callStatic($name, $arguments)
1084: {
1085:
1086:
1087:
1088:
1089: $instance = self::instance();
1090: if ($instance) {
1091: return call_user_func_array(array($instance, $name), $arguments);
1092: }
1093: else {
1094: throw new BadMethodCallException(__CLASS__ . " method called in invalid state");
1095: }
1096: }
1097:
1098:
1099: 1100: 1101: 1102: 1103:
1104: public static function ()
1105: {
1106: $CREDIT_LOGO = __CLASS__ . "/" . self::VERSION;
1107: if (!headers_sent()) {
1108: $found = false;
1109: foreach (headers_list() as $http_header) {
1110: if (preg_match('/^X-Powered-By:/', $http_header)) {
1111: $found = true;
1112: if (!preg_match('/ ' . preg_quote($CREDIT_LOGO, '/') . '/', $http_header)) {
1113: header($http_header . " " . $CREDIT_LOGO);
1114: }
1115: break;
1116: }
1117: }
1118: if (!$found) {
1119: header("X-Powered-By: " . $CREDIT_LOGO);
1120: }
1121: }
1122: }
1123:
1124: 1125: 1126: 1127: 1128: 1129: 1130: 1131: 1132: 1133: 1134: 1135: 1136: 1137: 1138: 1139: 1140: 1141: 1142: 1143: 1144: 1145: 1146:
1147:
1148: 1149: 1150: 1151: 1152:
1153: public function updateIncdir()
1154: {
1155: $sep = substr(PHP_OS, 0, 3) == "WIN" ? ";" : ":";
1156: $runinc = array();
1157:
1158: array_push($runinc, dirname($this->script));
1159: $cwd = getcwd();
1160: chdir($this->sysdir);
1161: $runinc = array_merge($runinc, array_map('realpath', $this->_incdir->toArray()));
1162: chdir($cwd);
1163: array_push($runinc, $this->_system_incdir);
1164:
1165: set_include_path(implode($sep, $runinc));
1166: }
1167:
1168: 1169: 1170: 1171: 1172: 1173: 1174: 1175: 1176: 1177:
1178: public function _error_handler($errno, $errstr, $errfile, $errline)
1179: {
1180: if ((error_reporting() & $errno) == 0) {
1181: return false;
1182: }
1183:
1184: $errno2txt = array(
1185: E_NOTICE=>"Notice", E_USER_NOTICE=>"Notice",
1186: E_WARNING=>"Warning", E_USER_WARNING=>"Warning",
1187: E_ERROR=>"Fatal Error", E_USER_ERROR=>"Fatal Error"
1188: );
1189: $errors = isset($errno2txt[$errno]) ? $errno2txt[$errno] : "Unknown";
1190:
1191: $trace = debug_backtrace();
1192: array_shift($trace);
1193: $stacktrace = array();
1194: for ($i=0; $i < count($trace); $i++) {
1195: $stacktrace[] = htmlspecialchars(sprintf("#%d %s(%d): %s%s%s()",
1196: $i,
1197: @$trace[$i]['file'],
1198: @$trace[$i]['line'],
1199: @$trace[$i]['class'],
1200: @$trace[$i]['type'],
1201: @$trace[$i]['function']
1202: ));
1203: }
1204:
1205: ob_start();
1206: if (ini_get("display_errors")) {
1207: printf("<br />\n<b>%s</b>: %s in <b>%s</b> on line <b>%d</b><br />\n", $errors, $errstr, $errfile, $errline);
1208: echo "\n<pre>" . implode("\n", $stacktrace) . "</pre><br />\n";
1209: }
1210: if (ini_get('log_errors')) {
1211: error_log(sprintf("PHP %s: %s in %s on line %d", $errors, $errstr, $errfile, $errline));
1212: }
1213: if ($errno & (E_ERROR | E_USER_ERROR)) {
1214: if (!headers_sent()) {
1215: $protocol = $this->request->server->get('SERVER_PROTOCOL', 'HTTP/1.0');
1216: if (!preg_match('/^HTTP\/.*$/', $protocol)) {
1217: $protocol = 'HTTP/1.0';
1218: }
1219: header($protocol . ' 500 Fatal Error');
1220: header('Content-Type:text/html');
1221: }
1222: }
1223: ob_end_flush();
1224: if ($errno & (E_ERROR | E_USER_ERROR)) {
1225: echo str_repeat(' ', 100)."\n";
1226: exit(1);
1227: }
1228: return true;
1229: }
1230:
1231: 1232: 1233: 1234: 1235: 1236: 1237:
1238: public function _exception_handler($e)
1239: {
1240: if (!headers_sent()) {
1241: $protocol = $this->request->server->get('SERVER_PROTOCOL', 'HTTP/1.0');
1242: if (!preg_match('/^HTTP\/.*$/', $protocol)) {
1243: $protocol = 'HTTP/1.0';
1244: }
1245: header($protocol . ' 500 Uncaught Exception');
1246: header('Content-Type:text/html');
1247: }
1248:
1249: $line = $e->getFile();
1250: if ($e->getLine()) {
1251: $line .= ' line '.$e->getLine();
1252: }
1253:
1254: if (ini_get('display_errors')) {
1255: $title = '500 ' . get_class($e);
1256: $body = "<p><strong>\n ".htmlspecialchars($e->getMessage()).'</strong></p>' .
1257: '<p>In '.htmlspecialchars($line)."</p><pre>\n".htmlspecialchars($e->getTraceAsString()).'</pre>';
1258: } else {
1259: $title = "500 Uncaught Exception";
1260: $body = "<p>The server encountered an uncaught exception and was unable to complete your request.</p>";
1261: }
1262:
1263: if (ini_get('log_errors')) {
1264: error_log($e->getMessage().' in '.$line);
1265: }
1266:
1267: if (ob_get_level() > 0 && ob_get_length() > 0) {
1268: $curbuf = ob_get_contents();
1269: ob_clean();
1270: }
1271: if (isset($curbuf) && preg_match('/<html/i', $curbuf)) {
1272: echo $curbuf;
1273: echo "<hr />";
1274: echo "<h1>" . $title . "</h1>\n" . $body . '</body></html>';
1275: }
1276: else {
1277: echo '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">' . "\n";
1278: echo "<html><head>\n<title>".$title."</title>\n</head><body>\n";
1279: if (isset($curbuf)) {
1280: echo $curbuf;
1281: echo "<hr />";
1282: }
1283: echo "<h1>" . $title . "</h1>\n" . $body . '</body></html>';
1284: }
1285: echo str_repeat(' ', 100)."\n";
1286: exit(1);
1287: }
1288:
1289: 1290: 1291: 1292: 1293: 1294: 1295: 1296: 1297:
1298: public function _includeWithThis($script_abs_path, $localvars=array())
1299: {
1300:
1301: if (!preg_match('/^([A-Za-z]+:)?[\\/\\\\].+/', $script_abs_path) ||
1302: !is_file($script_abs_path)) {
1303: return null;
1304: }
1305:
1306: if (!is_array($this->_script_include_stack)) {
1307: $this->_script_include_stack = array();
1308: }
1309: array_push($this->_script_include_stack, $script_abs_path);
1310: unset($script_abs_path);
1311: extract($localvars);
1312: unset($localvars);
1313: $retval = include($this->_script_include_stack[count($this->_script_include_stack) - 1]);
1314: array_pop($this->_script_include_stack);
1315: return $retval;
1316: }
1317:
1318: 1319: 1320: 1321: 1322: 1323: 1324:
1325: public function subscript($script)
1326: {
1327: $is_abs = $script[0] != '/';
1328: if (strncasecmp(PHP_OS, 'win', 3)) {
1329: $is_abs |= !preg_match('/^[A-Z]:(\\\\|\\/)/i', $script);
1330: }
1331: if ($this->_script && $is_abs) {
1332: $script = dirname($this->_script) . '/' . $script;
1333: }
1334: $prev_script = $this->_script;
1335: $this->_script = $script;
1336: $this->_activity->push($this->_script);
1337: $this->updateIncdir();
1338: try {
1339: $retval = $this->_includeWithThis($this->_script, $this->_autolocal->toArray());
1340: $this->_script = $prev_script;
1341: return $retval;
1342: }
1343: catch (Pinoco_FlowControlSkip $ex) {
1344: $this->_script = $prev_script;
1345: return null;
1346: }
1347: catch (Pinoco_FlowControl $ex) {
1348: $this->_script = $prev_script;
1349: if ($ex instanceof Pinoco_FlowControl) {
1350: throw $ex;
1351: }
1352: return null;
1353: }
1354: }
1355:
1356: 1357: 1358: 1359: 1360: 1361: 1362: 1363:
1364: private function _run_hook_if_exists($script, $subpath)
1365: {
1366: if (is_file($script)) {
1367: $this->_subpath = $subpath;
1368: try {
1369: $this->subscript($script);
1370: }
1371: catch (Pinoco_FlowControl $ex) {
1372: $this->_subpath = null;
1373: if ($ex instanceof Pinoco_FlowControl) {
1374: throw $ex;
1375: }
1376: }
1377: $this->_subpath = null;
1378: return true;
1379: }
1380: else {
1381: return false;
1382: }
1383: }
1384:
1385: 1386: 1387: 1388: 1389: 1390:
1391: public function run($output_buffering=true)
1392: {
1393: self::$_current_instance = $this;
1394:
1395: if (!(function_exists('xdebug_is_enabled') && xdebug_is_enabled())
1396: && PHP_SAPI !== 'cli')
1397: {
1398: $special_error_handler_enabled = true;
1399: set_error_handler(array($this, "_error_handler"));
1400: set_exception_handler(array($this, "_exception_handler"));
1401: }
1402:
1403: if ($output_buffering || $this->testing) {
1404: ob_start();
1405: }
1406:
1407: $this->_system_incdir = get_include_path();
1408:
1409: $this->_manually_rendered = false;
1410: try {
1411: $hookbase = $this->_sysdir . "/hooks";
1412:
1413: $uris = explode("/", ltrim($this->_path, "/"));
1414: $process = array();
1415: $processed = false;
1416: try {
1417: while (count($uris) > 0) {
1418: $dpath = (count($process) == 0 ? "" : "/") . implode('/', $process);
1419:
1420: $fename_orig = $uris[0];
1421:
1422:
1423: $this->_run_hook_if_exists($hookbase . $dpath . "/_enter.php", implode('/', $uris));
1424:
1425: array_shift($uris);
1426:
1427:
1428: if (!$this->_testing && $this->_dispatcher == "") {
1429: if (strpos(
1430: $this->request->server->get('REQUEST_URI'),
1431: "/" . basename($this->request->server->get('SCRIPT_NAME'))
1432: ) !== false) {
1433: $this->forbidden();
1434: }
1435: }
1436:
1437:
1438:
1439:
1440:
1441:
1442:
1443:
1444: if (preg_match('/^_.*$/', $fename_orig)) {
1445: $this->notfound();
1446: }
1447:
1448:
1449: if (count($uris) > 0) {
1450: if (is_dir($hookbase . $dpath . '/' . $fename_orig)) {
1451: $fename = $fename_orig;
1452: }
1453: elseif (is_dir($hookbase . $dpath . '/_default')) {
1454: $this->_pathargs->push($fename_orig);
1455: $fename = '_default';
1456: }
1457: else {
1458: $fename = $fename_orig;
1459: }
1460: }
1461: else {
1462: $fename = $fename_orig;
1463: }
1464:
1465: if (count($uris) == 0 && $fename == "") {
1466: foreach (explode(" ", $this->_directory_index) as $idx) {
1467: if (
1468: is_file($this->_basedir . $dpath . "/" . $idx) ||
1469: is_file($hookbase . $dpath . "/" . $idx . ".php")
1470: ) {
1471: $fename = $idx;
1472: break;
1473: }
1474: }
1475: }
1476: array_push($process, $fename);
1477:
1478:
1479: if (count($uris) == 0) {
1480: if ($fename == "") {
1481: foreach (explode(" ", $this->_directory_index) as $idx) {
1482: $ext = pathinfo($idx, PATHINFO_EXTENSION);
1483: if ($ext && is_file($hookbase . $dpath . "/_default." . $ext . ".php")) {
1484: $fename = "_default." . $ext;
1485: break;
1486: }
1487: }
1488: if ($fename == "") {
1489: $fename = "_default";
1490: }
1491: $this->_pathargs->push($fename_orig);
1492: }
1493: elseif (!is_file($hookbase . $dpath . "/" . $fename . ".php")) {
1494: $ext = pathinfo($fename, PATHINFO_EXTENSION);
1495: if ($ext && is_file($hookbase . $dpath . "/_default." . $ext . ".php")) {
1496: $fename = "_default." . $ext;
1497: }
1498: else {
1499: $fename = "_default";
1500: }
1501: $this->_pathargs->push($fename_orig);
1502: }
1503: }
1504:
1505:
1506: if (is_file($hookbase . $dpath . "/" . $fename . ".php")) {
1507: $processed = true;
1508: if ($this->_run_hook_if_exists($hookbase . $dpath . "/" . $fename . ".php", implode('/', $uris))) {
1509: break;
1510: }
1511: }
1512: }
1513: $dummy = 1;
1514: }
1515: catch (Pinoco_FlowControlTerminate $ex) {
1516: }
1517:
1518:
1519: if (!$this->_manually_rendered && $this->_page) {
1520:
1521: if ($this->_page != "<"."default>") {
1522: $pagepath = $this->resolvePath($this->_page);
1523: $page = $this->_page_from_path_with_directory_index($pagepath, $processed ? $fename : false);
1524: }
1525: else {
1526: $pagepath = $this->_path;
1527: if ($this->_page_modifier != null) {
1528: $this->updateIncdir();
1529: $pagepath = call_user_func($this->_page_modifier, $pagepath);
1530: }
1531: if ($pagepath) {
1532: $page = $this->_page_from_path_with_directory_index($pagepath, $processed ? $fename : false);
1533: }
1534: else {
1535: $page = false;
1536: }
1537: }
1538:
1539: if ($page && is_file($this->_basedir . $page)) {
1540:
1541: if ($this->isRenderablePath($this->_basedir . $page)) {
1542: $this->render($page);
1543: }
1544: else {
1545: try {
1546: $this->serveStatic($this->_basedir . $page);
1547: }
1548: catch (Pinoco_FlowControlHttpError $ex) {
1549: throw $ex;
1550: }
1551: catch (Pinoco_FlowControl $ex) { }
1552: }
1553: }
1554: elseif (!$processed) {
1555:
1556: if ($this->_path[strlen($this->_path) - 1] == "/") {
1557: $this->forbidden();
1558: }
1559: else {
1560: $this->notfound();
1561: }
1562: }
1563: elseif ($this->_page != "<"."default>") {
1564:
1565: $this->error(500, "Internal Server Error", "File not found: " . $this->_page);
1566: }
1567: }
1568: $dummy = 1;
1569: }
1570: catch (Pinoco_FlowControlHttpError $ex) {
1571: $ex->respond($this);
1572: }
1573:
1574:
1575: do {
1576: $fename = array_pop($process);
1577: array_unshift($uris, $fename);
1578: $dpath = (count($process) == 0 ? "" : "/") . implode('/', $process);
1579:
1580:
1581: try {
1582:
1583: $this->_run_hook_if_exists($hookbase . $dpath . "/_leave.php", implode('/', $uris));
1584: $dummy = 1;
1585: }
1586: catch (Pinoco_FlowControl $ex) { }
1587:
1588: } while (count($process) > 0);
1589:
1590: set_include_path($this->_system_incdir);
1591:
1592: if ($output_buffering || $this->testing) {
1593: if ($this->testing) {
1594: $all_output_while_running = ob_get_clean();
1595: }
1596: else {
1597: ob_end_flush();
1598: }
1599: }
1600:
1601: if (isset($special_error_handler_enabled)) {
1602: restore_exception_handler();
1603: restore_error_handler();
1604: }
1605:
1606:
1607: if ($this->testing) {
1608:
1609: return $all_output_while_running;
1610: }
1611:
1612: return "";
1613: }
1614:
1615: 1616: 1617: 1618: 1619: 1620: 1621: 1622: 1623:
1624: public static function testenv($basedir, $sysdir, $baseuri="/", $dispatcher="")
1625: {
1626: return new Pinoco_TestEnvironment($basedir, $sysdir, $baseuri, $dispatcher);
1627: }
1628:
1629: }
1630:
1631: