Overview

Packages

  • Pinoco
    • PAL

Classes

  • Pinoco
  • Pinoco_Delegate
  • Pinoco_DynamicVars
  • Pinoco_HttpRequestVars
  • Pinoco_List
  • Pinoco_MIMEType
  • Pinoco_NativeRenderer
  • Pinoco_NothingVars
  • Pinoco_NullRenderer
  • Pinoco_OptionalParam
  • Pinoco_Pagination
  • Pinoco_PDOStatementWrapper
  • Pinoco_PDOWrapper
  • Pinoco_Renderer
  • Pinoco_Router
  • Pinoco_TALRenderer
  • Pinoco_TestEnvironment
  • Pinoco_Validator
  • Pinoco_ValidatorContext
  • Pinoco_Vars

Interfaces

  • Pinoco_ArrayConvertible

Functions

  • __pinoco_autoload_impl
  • Overview
  • Package
  • Class
  • Tree
   1: <?php
   2: /**
   3:  * Pinoco: makes existing static web site dynamic transparently.
   4:  * Copyright 2010-2012, Hisateru Tanaka <tanakahisateru@gmail.com>
   5:  *
   6:  * Licensed under The MIT License
   7:  * Redistributions of files must retain the above copyright notice.
   8:  *
   9:  * PHP Version 5
  10:  *
  11:  * @author     Hisateru Tanaka <tanakahisateru@gmail.com>
  12:  * @copyright  Copyright 2010-2012, Hisateru Tanaka <tanakahisateru@gmail.com>
  13:  * @license    MIT License (http://www.opensource.org/licenses/mit-license.php)
  14:  * @package    Pinoco
  15:  */
  16: 
  17: require_once(dirname(__FILE__) . '/Pinoco/_bootstrap.php');
  18: 
  19: /**
  20:  * Pinoco web site environment
  21:  * It makes existing static web site dynamic transparently.
  22:  *
  23:  * Install PHPTAL.
  24:  * Make your application directory anywhere.
  25:  *
  26:  * Put .htaccess in your site root.
  27:  * <code>
  28:  * RewriteEngine On
  29:  * RewriteCond %{REQUEST_FILENAME} \.(html|php)$ [OR]
  30:  * RewriteCond %{REQUEST_FILENAME} !-f
  31:  * RewriteCond %{REQUEST_FILENAME} !_gateway\.php$
  32:  * RewriteRule ^(.*)$   _gateway.php/$1 [L,QSA]
  33:  * #...or RewriteRule ^(.*)$   _gateway.php?PATH_INFO=$1 [L,QSA]
  34:  * </code>
  35:  *
  36:  * Put _gateway.php in your site root.
  37:  * <code>
  38:  * require_once 'Pinoco.php';
  39:  * Pinoco::create("*** your_app_dir ***", array(
  40:  * //    'use_mod_rewrite'  => true,  // true or false default true
  41:  * //    'use_path_info'    => true,  // true or false default true
  42:  * //    'custom_path_info' => false, // false(auto) or string default false
  43:  * //    'directory_index'  => "index.html index.php", // string like DirectoryIndex directive default "index.html index.php"
  44:  * ))->run();
  45:  * </code>
  46:  *
  47:  * Pinoco::create guesses HTTP request to create Pinoco instance.
  48:  * If this method can't work file in your environment, you can create Pinoco
  49:  * environment manually via "new" operator using your own parameters.
  50:  *
  51:  * @package Pinoco
  52:  * @property-read string $baseuri Base URI
  53:  * @property-read string $basedir Base directory
  54:  * @property-read string $sysdir  Application directory
  55:  * @property-read bool $testing  Test mode flag
  56:  * @property Pinoco_List $incdir  Include pathes
  57:  * @property-read Pinoco_HttpRequestVars $request Request related global variables wrapper
  58:  * @property-read string $path    Path under base URI
  59:  * @property-read string $script  Current hook script
  60:  * @property-read Pinoco_List $activity  Activity history of hook scripts
  61:  * @property-read Pinoco_List $sent_headers  Sent headers via Pinoco
  62:  * @property-read string $subpath Sub-path under current hook script
  63:  * @property-read Pinoco_List $pathargs Path elements matches _default[.*] hooks
  64:  * @property string $directory_index Space separated directory index files(like Apache)
  65:  * @property string $page         Template file to be rendered
  66:  * @property-read Pinoco_Vars $renderers File extension to rendering module mappings
  67:  * @property-read Pinoco_Vars $autolocal Auto extracted variables into local scope
  68:  * @property callback $url_modifier  URL modification callback
  69:  * @property callback $page_modifier Template page base path modification callback
  70:  */
  71: class Pinoco extends Pinoco_DynamicVars
  72: {
  73:     const VERSION = "0.8.0";
  74: 
  75:     private $_baseuri;   // R gateway index.php location on internet
  76:     private $_basedir;   // R gateway index.php location on file system
  77:     private $_sysdir;    // R base directory for scripts
  78:     private $_testing;   // R test mode flag
  79: 
  80:     private $_incdir;  // R/W include search directories
  81:     private $_request;  // R request related global variables wrapper
  82: 
  83:     private $_path;      // R string
  84:     private $_script;    // R string
  85:     private $_activity;  // R list
  86:     private $_sent_headers; //R list
  87:     private $_subpath;   // R string or null
  88:     private $_pathargs;  // R list
  89:     private $_directory_index;  // R/W string
  90: 
  91:     private $_renderers; // R/M vars
  92:     private $_page;       // R/W string
  93: 
  94:     private $_autolocal; // R/M vars
  95: 
  96:     private $_url_modifier;  // R/W callable
  97:     private $_page_modifier; // R/W callable
  98: 
  99:     // hidden
 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:      * It provides a suitable Pinoco instance in regular usage.
 109:      *
 110:      * @param string $sysdir
 111:      * @param array $options
 112:      * @return Pinoco
 113:      */
 114:     public static function create($sysdir, $options=array())
 115:     {
 116:         // options
 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:         // raw path info
 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:         // path
 131:         $path = $pathinfo;
 132:         if (!preg_match('/^\//', $path)) {
 133:             $path = "/" . $path;
 134:         }
 135: 
 136:         // dispatcher
 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:         // base uri (gateway placed path)
 150:         $uri = urldecode($_SERVER['REQUEST_URI']);  // to urldecoded path like path_info or _GET params
 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:         // remove double slashes in path expression (e.g. /foo//bar)
 173:         $uri = preg_replace('/\/\/+/', '/', $uri);
 174:         $trailings = preg_replace('/\/\/+/', '/', $trailings);
 175: 
 176:         $baseuri = substr($uri, 0, strlen($uri) - strlen($trailings));
 177: 
 178:         // build engine
 179:         return new self($baseuri, $dispatcher, $path, dirname($_SERVER['SCRIPT_FILENAME']), $sysdir);
 180:     }
 181: 
 182:     /**
 183:      * Pinoco constructor.
 184:      *
 185:      * @param string $baseuri
 186:      * @param string $dispatcher
 187:      * @param string $path
 188:      * @param string $basedir
 189:      * @param string $sysdir
 190:      * @param bool $testing
 191:      * @throws InvalidArgumentException
 192:      * @see src/Pinoco#create($sysdir, $options)
 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"); // default lib dir
 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"; // default index files
 221: 
 222:         /*
 223:         if ($this->_path[strlen($this->_path) - 1] == '/') {
 224:             foreach (explode(" ", $directory_index) as $indexfile) {
 225:                 if (file_exists($this->_basedir . $this->_path . $indexfile)) {
 226:                     $this->_path .= $indexfile;
 227:                     break;
 228:                 }
 229:             }
 230:         }
 231:         if ($this->_path[strlen($this->_path) - 1] == '/') {
 232:             $this->_path .= 'index.html';
 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>";  // To be resolved automatically
 250: 
 251:         $this->_autolocal = self::newVars();
 252: 
 253:         $this->_url_modifier = null;
 254:         $this->_page_modifier = null;
 255: 
 256:         parent::__construct();
 257: 
 258:         // chdir($this->_sysdir);
 259:     }
 260: 
 261:     public function __toString() { return __CLASS__ . " " . self::VERSION; }
 262: 
 263:     /**
 264:      * Imports named attribute from file or array.
 265:      *
 266:      * @param string $name Attribute name of Pinoco instance.
 267:      * @param string|array $source Config file path based on the app dir or array.
 268:      * @throws InvalidArgumentException
 269:      * @return Pinoco
 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; // pass configuration if file not exists
 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:     // factory
 312:     /**
 313:      * It provides a new Vars object (that can be filled with existing Array).
 314:      *
 315:      * @param mixed $init
 316:      * @return Pinoco_Vars
 317:      */
 318:     public static function newVars($init=array())
 319:     {
 320:         return Pinoco_Vars::fromArray($init);
 321:     }
 322: 
 323:     /**
 324:      * It provides a new List object (that can be filled with existing Array).
 325:      *
 326:      * @param mixed $init
 327:      * @return Pinoco_List
 328:      */
 329:     public static function newList($init=array())
 330:     {
 331:         return Pinoco_List::fromArray($init);
 332:     }
 333: 
 334:     /**
 335:      * It provides the NothingVars object.
 336:      *
 337:      * @return Pinoco_NothingVars
 338:      */
 339:     public static function newNothing()
 340:     {
 341:         return Pinoco_NothingVars::instance();
 342:     }
 343: 
 344:     /**
 345:      * It provides a Vars object as existing Array wrapper.
 346:      *
 347:      * @param array &$ref
 348:      * @return Pinoco_Vars
 349:      */
 350:     public static function wrapVars(&$ref)
 351:     {
 352:         return Pinoco_Vars::wrap($ref);
 353:     }
 354: 
 355:     /**
 356:      * It provides a List object as existing Array wrapper.
 357:      *
 358:      * @param array &$ref
 359:      * @return Pinoco_List
 360:      */
 361:     public static function wrapList(&$ref)
 362:     {
 363:         return Pinoco_List::wrap($ref);
 364:     }
 365: 
 366:     /**
 367:      * It provides a new object by "path/to/src.php/ClassName" syntax.
 368:      *
 369:      * @deprecated
 370:      * @param string $class
 371:      * @param mixed $args,...
 372:      * @throws InvalidArgumentException
 373:      * @return object
 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:                 /* @var $srcfile string */
 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:      * Wrapped PDO factory
 407:      *
 408:      * @deprecated
 409:      * @param string $dsn
 410:      * @param string $un
 411:      * @param string $pw
 412:      * @param array $opts
 413:      * @return Pinoco_PDOWrapper
 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:     // reserved props
 424:     /**
 425:      * Web site root URI.
 426:      *
 427:      * @return string
 428:      */
 429:     public function get_baseuri() { return $this->_baseuri; }
 430: 
 431:     /**
 432:      * Web site root directory in local file system.
 433:      *
 434:      * @return string
 435:      */
 436:     public function get_basedir() { return $this->_basedir; }
 437: 
 438:     /**
 439:      * Application directory.
 440:      *
 441:      * @return string
 442:      */
 443:     public function get_sysdir()  { return $this->_sysdir; }
 444: 
 445:     /**
 446:      * Test mode flag.
 447:      *
 448:      * @return bool
 449:      */
 450:     public function get_testing()  { return $this->_testing; }
 451: 
 452:     /**
 453:      * Include search directories.
 454:      *
 455:      * @return Pinoco_List
 456:      */
 457:     public function get_incdir() { return $this->_incdir; }
 458: 
 459:     /**
 460:      * Request related global variables wrapper.
 461:      *
 462:      * @return Pinoco_Vars
 463:      */
 464:     public function get_request() { return $this->_request; }
 465: 
 466:     /**
 467:      * Local resource path under base URI.
 468:      *
 469:      * @return string
 470:      */
 471:     public function get_path()    { return $this->_path; }
 472: 
 473:     /**
 474:      * Current hook script if it running.
 475:      *
 476:      * @return string
 477:      */
 478:     public function get_script()  { return $this->_script; }
 479: 
 480:     /**
 481:      * Hook scripts invocation log.
 482:      *
 483:      * @return Pinoco_List
 484:      */
 485:     public function get_activity() { return $this->_activity; }
 486: 
 487:     /**
 488:      * Sent headers via Pinoco's method.
 489:      *
 490:      * @return Pinoco_List
 491:      */
 492:     public function get_sent_headers() { return $this->_sent_headers; }
 493: 
 494:     /**
 495:      * Partial path under current script.
 496:      *
 497:      * @return string
 498:      */
 499:     public function get_subpath() { return $this->_subpath; }
 500: 
 501:     /**
 502:      * Path elements resolved by _default folders or files.
 503:      *
 504:      * @return Pinoco_List
 505:      */
 506:     public function get_pathargs() { return $this->_pathargs; }
 507: 
 508:     /**
 509:      * Default files (separated by white space) for directory access.
 510:      *
 511:      * @return string
 512:      */
 513:     public function get_directory_index() { return $this->_directory_index; }
 514: 
 515:     /**
 516:      * File name to override view.
 517:      *
 518:      * @return string
 519:      */
 520:     public function get_page()    { return $this->_page; }
 521: 
 522:     /**
 523:      * Page renderers repository.
 524:      * A renderer object is registered to file extension as dictionary key.
 525:      *
 526:      * @return Pinoco_Vars
 527:      */
 528:     public function get_renderers() { return $this->_renderers; }
 529: 
 530:     /**
 531:      * Automatically extracted variables for hooks and pages.
 532:      *
 533:      * @return Pinoco_Vars
 534:      */
 535:     public function get_autolocal() { return $this->_autolocal; }
 536: 
 537:     /**
 538:      * Special filter for url conversion.
 539:      *
 540:      * @return callback
 541:      */
 542:     public function get_url_modifier() { return $this->_url_modifier; }
 543: 
 544:     /**
 545:      * Special filter for default view file.
 546:      *
 547:      * @return callback
 548:      */
 549:     public function get_page_modifier() { return $this->_page_modifier; }
 550: 
 551:     /**
 552:      * Include search directories.
 553:      *
 554:      * @param Pinoco_List $dirs
 555:      * @return void
 556:      */
 557:     public function set_incdir($dirs) { $this->_incdir = $dirs; }
 558: 
 559:     /**
 560:      * File name to override view.
 561:      * Set false if you want an empty output.
 562:      * Set "<default>" if you want to reset to default view.
 563:      *
 564:      * @param string $page
 565:      * @return void
 566:      */
 567:     public function set_page($page) { $this->_page = $page; }
 568: 
 569:     /**
 570:      * Default files (separated by white space) for directory access.
 571:      *
 572:      * @param string $files
 573:      * @return void
 574:      */
 575:     public function set_directory_index($files) { $this->_directory_index = $files; }
 576: 
 577:     /**
 578:      * Special filter for url conversion.
 579:      *
 580:      * @param callback $callable
 581:      * @return void
 582:      */
 583:     public function set_url_modifier($callable) { $this->_url_modifier = $callable; }
 584: 
 585:     /**
 586:      * Special filter for default view file.
 587:      *
 588:      * @param callback $callable
 589:      * @return void
 590:      */
 591:     public function set_page_modifier($callable) { $this->_page_modifier = $callable; }
 592: 
 593:     // flow control
 594:     /**
 595:      * Current hook process will be skipped and invoke the next hook script.
 596:      *
 597:      * @throws Pinoco_FlowControlSkip
 598:      * @return void
 599:      */
 600:     public static function skip()
 601:     {
 602:         throw new Pinoco_FlowControlSkip();
 603:     }
 604: 
 605:     /**
 606:      * Whole hook stage before rendering is terminated and rendering phase is started immediately.
 607:      *
 608:      * @throws Pinoco_FlowControlTerminate
 609:      * @return void
 610:      */
 611:     public static function terminate()
 612:     {
 613:         throw new Pinoco_FlowControlTerminate();
 614:     }
 615: 
 616:     /**
 617:      * It cancels hooks and rendering and respond HTTP error to browser.
 618:      *
 619:      * @param int $code
 620:      * @param string $title
 621:      * @param string $message
 622:      * @throws Pinoco_FlowControlHttpError
 623:      * @return void
 624:      */
 625:     public static function error($code, $title=null, $message=null)
 626:     {
 627:         throw new Pinoco_FlowControlHttpError($code, $title, $message);
 628:     }
 629: 
 630:     /**
 631:      * Special error to let browser change the location to access.
 632:      *
 633:      * @param string $url
 634:      * @param bool $external
 635:      * @throws Pinoco_FlowControlHttpRedirect
 636:      * @return void
 637:      */
 638:     public static function redirect($url, $external=false)
 639:     {
 640:         throw new Pinoco_FlowControlHttpRedirect($url, $external);
 641:     }
 642: 
 643:     /**
 644:      * Error for Not Found.
 645:      *
 646:      * @throws Pinoco_FlowControlHttpError
 647:      * @return void
 648:      */
 649:     public static function notfound()
 650:     {
 651:         self::error(404);
 652:     }
 653: 
 654:     /**
 655:      * Error for Forbidden.
 656:      *
 657:      * @throws Pinoco_FlowControlHttpError
 658:      * @return void
 659:      */
 660:     public static function forbidden()
 661:     {
 662:         self::error(403);
 663:     }
 664: 
 665:     /**
 666:      * This method sends headers not to be cached.
 667:      *
 668:      * @return void
 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:      * Conditional flow control. Send cache hints and also might send
 678:      * "304 Not Modified" status if the content has not been changed
 679:      * from previously sent (detected by incoming request header).
 680:      *
 681:      * @param int $timestamp
 682:      * @param string $etag
 683:      * @param int $lifetime
 684:      * @return void
 685:      */
 686:     public function abortIfNotModified($timestamp=null, $etag=null, $lifetime=86400)
 687:     {
 688:         $server = $this->request->server;
 689:         // These headers would be sent when required with super reload(SHIFT+F5).
 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:      * Serves static file or send 304 no-modified response automatically.
 731:      * This method terminates hook script flow.
 732:      *
 733:      * @param string $filename
 734:      * @param int $lifetime
 735:      * @param string $mime_type
 736:      * @return void
 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:      * Utility to get the parent path.
 765:      *
 766:      * @param string $path
 767:      * @return string
 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:      * It makes relative path absolute.
 779:      *
 780:      * @param string $path
 781:      * @param string|bool $base
 782:      * @return string
 783:      */
 784:     public function resolvePath($path, $base=false)
 785:     {
 786:         if (strlen($path) > 0 && $path[0] != '/') {
 787:             // make path absolute if relative
 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:      * Returns if the path can be rendered by registered renderers.
 812:      *
 813:      * @param string $path
 814:      * @return bool
 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:      * PHP's header function wrapper.
 828:      *
 829:      * @param string $string
 830:      * @param bool $replace
 831:      * @param int $http_response_code
 832:      * @return bool
 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:      * PHP's setcookie function wrapper.
 875:      *
 876:      * @param string $name
 877:      * @param string $value
 878:      * @param int $expire
 879:      * @param string $path
 880:      * @param string $domain
 881:      * @param bool $secure
 882:      * @param bool $httponly
 883:      * @return bool
 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:             // you can easily delete a cookie value by setting null.
 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, '/') . '/'; // setcookie's default is based on front controller.
 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:      * Returns host based URI from site based or sub-path based relative one.
 916:      *
 917:      * @param string $path
 918:      * @param bool $pure cancels to call user modifier if true
 919:      * @return string
 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:         // guess to use gateway script but not in use mod_rewrite.
 931:         if ($this->_dispatcher != "" && $renderable) {
 932:             // join both url params of dispatcher and path if they have "?" commonly.
 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:      * Returns default page file considered with directory index.
 948:      *
 949:      * @param string $path
 950:      * @param string $last_pathelem
 951:      * @return string|bool
 952:      * @internal
 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:      * Invokes renderer with page file immediately.
1015:      * Default rendering will be canceled.
1016:      * Pass false if you want an empty response.
1017:      *
1018:      * @param string $page
1019:      * @throws InvalidArgumentException
1020:      * @return void
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:             /* @var $renderer Pinoco_Renderer */
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:      * MIME type of file.
1043:      *
1044:      * @param string $filename
1045:      * @return string
1046:      */
1047:     public static function mimeType($filename)
1048:     {
1049:         /*----------- not work well, fix me ----------
1050:         if (file_exists($filename) && ini_get('mime_magic.magicfile')) {
1051:             if (function_exists('finfo_open'))
1052:             {
1053:                 $finfo = finfo_open(FILEINFO_MIME_TYPE);
1054:                 $type = finfo_file($finfo, $filename);
1055:                 finfo_close($finfo);
1056:                 if ($type) {
1057:                     return $type;
1058:                 }
1059:             }
1060:             if (function_exists('mime_content_type')) {
1061:                 $type = mime_content_type($filename);
1062:                 if ($type) {
1063:                     return $type;
1064:                 }
1065:             }
1066:         }
1067:         ----------------------------------- */
1068:         // final fallback process
1069:         include_once dirname(__FILE__) . '/Pinoco/MIMEType.php';
1070:         return Pinoco_MIMEType::fromFileName($filename);
1071:     }
1072: 
1073:     /**
1074:      * Returns currently running Pinoco instance.
1075:      *
1076:      * @return Pinoco
1077:      */
1078:     public static function instance()
1079:     {
1080:         return self::$_current_instance;
1081:     }
1082: 
1083:     public static function __callStatic($name, $arguments)
1084:     {
1085:         // Pinoco::instance()->some_method() can write as Pinoco::some_method()
1086:         // PHP >= 5.3.0
1087:         // Why not? => public static function __getStatic() / public static function __setStatic() ...
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:     // runtime core
1099:     /**
1100:      * Writes Pinoco credit into HTTP header.
1101:      *
1102:      * @return void
1103:      */
1104:     public static function creditIntoHeader()
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:     NOT IN USE NOW!
1126:     private function _hook_or_page_exists()
1127:     {
1128:         $hookbase = $this->_sysdir . "/hooks";
1129:         $uris = explode("/", ltrim($this->_path, "/"));
1130:         $dpath = "";
1131:         foreach ($uris as $fename) {
1132:             if (preg_match('/^_.*$/', $fename)) {
1133:                 return false;
1134:             }
1135:             if (file_exists($hookbase . $dpath . "/" . $fename . ".php")) {
1136:                 return true;
1137:             }
1138:             $dpath .= "/" . $fename;
1139:         }
1140:         if (file_exists($this->basedir . $this->_path) ||
1141:             file_exists($hookbase . dirname($this->_path) . "/_default.php")) {
1142:             return true;
1143:         }
1144:         return false;
1145:     }
1146:     */
1147: 
1148:     /**
1149:      * Updates 'include_path' in php.ini by this->incdir.
1150:      *
1151:      * @return void
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:      * Internal method to handle PHP errors.
1170:      *
1171:      * @param string $errno
1172:      * @param string $errstr
1173:      * @param string $errfile
1174:      * @param string $errline
1175:      * @return bool
1176:      * @ignore
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"; // IE won't display error pages < 512b
1226:             exit(1);
1227:         }
1228:         return true;
1229:     }
1230: 
1231:     /**
1232:      * Internal method to handle PHP exceptions.
1233:      *
1234:      * @param Exception $e
1235:      * @return void
1236:      * @ignore
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"; // IE won't display error pages < 512b
1286:         exit(1);
1287:     }
1288: 
1289:     /**
1290:      * It reads and executes another PHP file with any local variables.
1291:      * It can read already executed file.
1292:      *
1293:      * @param string $script_abs_path must be absolute path for local file system.
1294:      * @param array $localvars
1295:      * @return mixed
1296:      * @internal
1297:      */
1298:     public function _includeWithThis($script_abs_path, $localvars=array())
1299:     {
1300:         // script path must be absolute and exist.
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:      * Execute an external script in isolated variable scope.
1320:      *
1321:      * @param string $script Script filename absolute path or relative based on current script.
1322:      * @throws Pinoco_FlowControl
1323:      * @return mixed
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) { // Avoid IDE inspection warning of "unknown exception thrown".
1350:                 throw $ex;
1351:             }
1352:             return null;
1353:         }
1354:     }
1355: 
1356:     /**
1357:      * Runs a hook script.
1358:      *
1359:      * @param string $script
1360:      * @param string $subpath
1361:      * @throws Pinoco_FlowControl
1362:      * @return bool
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) { // Avoid IDE inspection warning of "unknown exception thrown".
1374:                     throw $ex;
1375:                 }
1376:             }
1377:             $this->_subpath = null;
1378:             return true;
1379:         }
1380:         else {
1381:             return false;
1382:         }
1383:     }
1384: 
1385:     /**
1386:      * Executes all process of Pinoco engine.
1387:      *
1388:      * @param bool $output_buffering
1389:      * @return void|string
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:                     // enter
1423:                     $this->_run_hook_if_exists($hookbase . $dpath . "/_enter.php", implode('/', $uris));
1424: 
1425:                     array_shift($uris);
1426: 
1427:                     // For mod_rewrite users: direct access to gateway should be rejected.
1428:                     if (!$this->_testing && $this->_dispatcher == "") { // No dispatcher indicates to force to use mod_rewrite.
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:                     // NOT IN USE NOW!
1438:                     // preprocess notfound -- if handler or page is not exists
1439:                     //if (!$this->_hook_or_page_exists()) {
1440:                     //    $this->notfound();
1441:                     //}
1442: 
1443:                     // invisible file entry name.
1444:                     if (preg_match('/^_.*$/', $fename_orig)) {
1445:                         $this->notfound();
1446:                     }
1447: 
1448:                     // default dir
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:                     // resolve index file for directory access(the last element is empty like "/foo/").
1465:                     if (count($uris) == 0 && $fename == "") {
1466:                         foreach (explode(" ", $this->_directory_index) as $idx) {
1467:                             if (
1468:                                 is_file($this->_basedir . $dpath . "/" . $idx) ||  //base
1469:                                 is_file($hookbase . $dpath . "/" . $idx . ".php")  //sys
1470:                             ) {
1471:                                 $fename = $idx;
1472:                                 break;
1473:                             }
1474:                         }
1475:                     }
1476:                     array_push($process, $fename);
1477: 
1478:                     // default script support for the last element(=file)
1479:                     if (count($uris) == 0) {
1480:                         if ($fename == "") { // case: no index file found for dir access
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:                     // main script
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; // NEVER REMOVE THIS LINE FOR eAccelerator's BUG!!
1514:             }
1515:             catch (Pinoco_FlowControlTerminate $ex) {
1516:             }
1517: 
1518:             //render
1519:             if (!$this->_manually_rendered && $this->_page) {
1520:                 /* @var $fename string */
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:                     // non-html but existing => raw binary with mime-type header
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:                     // no page and no tarminal hook indicates resource was not found or forbidden
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:                     // page specified in hook-script but not found it
1565:                     $this->error(500, "Internal Server Error", "File not found: " . $this->_page);
1566:                 }
1567:             }
1568:             $dummy = 1; // NEVER REMOVE THIS LINE FOR eAccelerator's BUG!!
1569:         }
1570:         catch (Pinoco_FlowControlHttpError $ex) { // contains Redirect
1571:             $ex->respond($this);
1572:         }
1573: 
1574:         // cleanup process
1575:         do {
1576:             $fename = array_pop($process);
1577:             array_unshift($uris, $fename);
1578:             $dpath = (count($process) == 0 ? "" : "/") . implode('/', $process);
1579: 
1580:             // leave (All flow control exceptions work as skip exception)
1581:             try {
1582:                 /* @var $hookbase string */
1583:                 $this->_run_hook_if_exists($hookbase . $dpath . "/_leave.php", implode('/', $uris));
1584:                 $dummy = 1; // NEVER REMOVE THIS LINE FOR eAccelerator's BUG!!
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:         // DON'T CLEAR self::$_current_instance = null;
1606: 
1607:         if ($this->testing) {
1608:             /* @var $all_output_while_running string */
1609:             return $all_output_while_running;
1610:         }
1611: 
1612:         return "";
1613:     }
1614: 
1615:     /**
1616:      * Provides a testable Pinoco instance for unit test.
1617:      *
1618:      * @param string $sysdir
1619:      * @param string $basedir
1620:      * @param string $baseuri
1621:      * @param string $dispatcher
1622:      * @return Pinoco_TestEnvironment
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: 
Pinoco 0.8.0 Documentation API documentation generated by ApiGen 2.8.0