3676733b8ed487b0e6412b0ce9e346db79db30fe
[pear] / HTML / FlexyFramework2.php
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4: */
3 // +----------------------------------------------------------------------+
4 // | PHP Version 4                                                        |
5 // +----------------------------------------------------------------------+
6 // | Copyright (c) 1997-2002 The PHP Group                                |
7 // +----------------------------------------------------------------------+
8 // | This source file is subject to version 2.02 of the PHP license,      |
9 // | that is bundled with this package in the file LICENSE, and is        |
10 // | available at through the world-wide-web at                           |
11 // | http://www.php.net/license/2_02.txt.                                 |
12 // | If you did not receive a copy of the PHP license and are unable to   |
13 // | obtain it through the world-wide-web, please send a note to          |
14 // | license@php.net so we can mail you a copy immediately.               |
15 // +----------------------------------------------------------------------+
16 // | Authors:  Alan Knowles <alan@akbkhome.com>                           |
17 // +----------------------------------------------------------------------+
18 //
19 // $Id: FlexyFramework.php,v 1.8 2003/02/22 01:52:50 alan Exp $
20 //
21 //  Description
22 //  A Page (URL) to Object Mapper
23 //  Cleaned up version.. - for use on new projects -- not BC!! beware!!!
24
25
26 //-----------------------------------------------------------
27 // Debian APACHE - some idiot disabled AcceptPathInfo - it needs adding back.
28 //-----------------------------------------------------------
29
30  
31  
32 // Initialize Static Options
33 require_once 'HTML/FlexyFramework2/Page.php';  
34
35
36 // To be removed ?? or made optional or something..
37  
38
39 // remove E_ANAL  
40  
41 error_reporting(E_ALL & ~E_STRICT );
42 //ini_set('display_errors','off');
43 //ini_set('log_errors','off');
44
45
46
47
48
49
50 /**
51 * The URL to Object Mapper
52 *
53 * Usage:
54 * Create a index.php and add these lines.
55 *  
56 * ini_set("include_path", "/path/to/application"); 
57 * require_once 'HTML/FlexyFramework.php';
58 * HTML_FlexyFramework::factory(array("dir"=>"/where/my/config/dir/is");
59 *
60 *
61 * the path could include pear's path, if you dont install all the pear 
62 * packages into the development directory.
63 *
64 * It attempts to load a the config file from the includepath, 
65 * looks for ConfigData/default.ini
66 * or ConfigData/{hostname}.ini
67 * if your file is called staging.php rather than index.php 
68 * it will try staging.ini
69 *
70 */
71  
72 class HTML_FlexyFramework2 {
73     
74     /**
75      * Confirgurable items..
76      * If we set them to 'true', they must be set, otherwise they are optional.
77      */
78     var $project; // base class name
79     var $database; // set to true even if nodatabase=true
80     
81     // optional
82     var $debug = false;
83     var $enable = false; // modules
84     var $disable = false; // modules or permissions
85     var $appName = false;
86     var $appNameShort = false; // appname (which has templates)
87     var $version = false; // give it a version name. (appended to compile dir)
88     var $nodatabase = false; // set to true to block db config and testing.
89     var $fatalAction = false; // page to redirct to on failure. (eg. databse down etc.)
90     var $charset = false; // default UTF8
91     var $dataObjectsCache = true;  // use dataobjects ini cache.. - let's try this as the default behaviour...
92     var $dataObjectsCacheExpires = 72000; // 20 hours..
93     var $languages = false; // language settings -- see _handlelanguage
94     var $projectExtends = false; // if this is an array, it's a fallback of 'Projects' that can be called
95     var $initOnly = false; // use to prevent run() being called...
96                 // use $this->run($_SERVER['REQUEST_URI'],false); to manually boot it..
97
98     
99     // derived.
100     var $cli = false; // from cli 
101     var $run = false; // from cli
102     var $enableArray = false; // from enable.
103     var $classPrefix = false; // from prject.
104     var $baseDir = false ; // (directory+project)
105     var $rootDir = false ; // (directory that index.php is in!)
106     
107     var $baseURL = false;
108     var $rootURL = false ; // basename($baseURL)
109     
110     var $page = false; // active page..
111     var $timer = false; // the debug timer
112     var $calls = false; // the number of calls made to run!
113     var $start = false; // the start tiem.
114     
115     var $baseRequest = '';
116     var $ext; // the striped extention.
117     
118     var $dataObjectsOriginalIni = ''; // 1 houre..
119     
120     // used to be $_GLOBALS[__CLASS__]
121     
122     static $singleton; 
123     
124     
125     /**
126      * 
127      * Constructor - with assoc. array of props as option
128      * called by index.php usually, and runs the app code,
129      *
130      * uses 'universal construcor' format, so the argument relates directly to properties of this object.
131      * 
132      */
133     
134     
135     function __construct($config)
136     {
137         if (isset(self::$singleton)) {
138             trigger_error("FlexyFramework Construct called twice!", E_ERROR);
139         }
140         
141         self::$singleton = $this;
142         
143         $this->calls = 0;
144
145         $m = explode(' ',microtime());
146         $this->start = $m[0] + $m[1];
147         
148         $config = $this->loadModuleConfig($config);
149         
150         foreach($config as $k=>$v) {
151             $this->$k = $v;
152         }
153         $this->_parseConfig();
154         
155         // echo '<PRE>'; print_r($this);exit;
156         if ($this->cli) {
157             $args = $_SERVER['argv'];
158             array_shift($args );
159             array_shift($args );
160             $this->_run($this->run,false,$args);
161             return;
162         }
163     
164         // handle apache mod_rewrite..
165         // it looks like this might not work anymore..
166         
167         if ($this->initOnly) {
168             return;
169         }
170         
171         
172         if (!empty($_SERVER['REDIRECT_URL'])) {
173             
174             $this->_run($_SERVER['SCRIPT_NAME'] . $_SERVER['REQUEST_URI'],false);
175             return ;
176         }
177         
178         
179         $this->_run($_SERVER['REQUEST_URI'],false);
180             
181         
182     }
183     /**
184      * This is the standard way to get information about the application settings.
185      * $ff = HTML_FlexyFramework::get();
186      * if ($ff->SomeVar[...])....
187      *
188      */
189     static function get()
190     {
191         return self::$singleton;
192     }
193     /*
194      * looks for files in the path and load up the default values for config?
195      */
196     function loadModuleConfig($cfg)
197     {
198         
199         
200         $proj = $cfg['project'];
201         $rootDir = realpath(dirname($_SERVER["SCRIPT_FILENAME"]));
202
203         $cls = $proj.'_Config';
204         if (file_exists($rootDir . '/'.str_replace('_','/', $cls). '.php')) {
205             require_once str_replace('_','/', $cls). '.php';
206             $c = new $cls();
207             if (method_exists($c,'init')) {
208                 $cfg = $c->init($this,$cfg);
209             }
210         }
211         
212         $mods = empty($cfg['enable']) ? array() : explode(',',$cfg['enable']);
213         array_unshift($mods,'');
214         
215         foreach($mods as $m) {
216             $cls = $proj. (strlen($m) ? '_'. $m  : '' ) . '_Config';
217
218             if (!file_exists($rootDir . '/'.str_replace('_','/', $cls). '.php')) {
219                 continue;
220             }
221             require_once str_replace('_','/', $cls). '.php';
222             $c = new $cls();
223             if (method_exists($c,'init')) {
224                 $cfg = $c->init($this,$cfg);
225             }
226         }
227         return $cfg;
228     }
229     
230   
231     /**
232      * parse the configuration set by the constructor.
233      * 
234      *
235      */
236   
237     function _parseConfig()
238     {
239         
240         // make sure required values are set.. (anything that is not defaulted to false..)
241         foreach(get_class_vars(__CLASS__) as $k =>$v) {
242             if ($v === false && !isset($this->$k)) {
243                 die("$k is not set");
244             }
245         }
246         
247         $this->_handleLanguages();
248         
249         // enable modules.
250         if (!empty($this->enable)) {
251             $this->enableArray = explode(',', $this->enable);
252             
253             if (!in_array('Core',$this->enableArray ) &&
254                 !in_array('Core', explode(',', $this->disable ? $this->disable : '')))
255             {
256                 $this->enable = 'Core,'. $this->enable ;
257                 $this->enableArray = explode(',', $this->enable);
258             }
259         }
260         // are we running cli?
261         $this->cli = php_sapi_name() == 'cli'; 
262         
263         // will these work ok with cli?
264         $bits = explode(basename($_SERVER["SCRIPT_FILENAME"]), $_SERVER["SCRIPT_NAME"]);
265         if (!$this->cli) {
266             $bits[0] = str_replace('%2F','/',urlencode($bits[0]));
267             $this->baseURL = $bits[0] . basename($_SERVER["SCRIPT_FILENAME"]);
268             
269             if (empty($_SERVER['SCRIPT_NAME'])) {
270                 $this->baseURL = ''; // ??? this is if we replace top level...
271             }
272         }
273         // if cli - you have to have set baseURL...
274         
275         
276         $this->rootDir = realpath(dirname($_SERVER["SCRIPT_FILENAME"]));
277         $this->baseDir = $this->rootDir .'/'. $this->project;
278         $this->rootURL = dirname($this->baseURL);
279         $this->rootURL = ($this->rootURL == '/') ? '' : $this->rootURL;
280          
281       
282         //var_dump($this->baseURL);
283         
284         if (!isset($this->database) && isset($this->PDO_DataObject['database'])) {
285             $this->database = $this->PDO_DataObject['database'];
286         }
287         
288          $this->classPrefix   = str_replace('/', '_', $this->project) . '_';
289         
290         // list the available options..
291         if ($this->cli && empty($_SERVER['argv'][1])) {
292             require_once 'HTML/FlexyFramework2/Cli.php';
293             $fcli = new HTML_FlexyFramework2_Cli($this);
294             $fcli->cliHelp();
295             exit;
296         }
297         
298         
299         // see if it's a framework assignment.
300         $ishelp = false;
301         if ($this->cli) {
302             require_once 'HTML/FlexyFramework2/Cli.php';
303             $fcli = new HTML_FlexyFramework2_Cli($this);
304             $res = $fcli->parseDefaultOpts();
305             if ($res === true) {
306                 $ishelp = true;
307             } 
308              
309         }
310         
311         
312         $this->run = $this->cli ? $_SERVER['argv'][1] : false;
313      
314         
315         $this->_parseConfigDataObjects();
316         if ($this->dataObjectsCache && !$this->nodatabase) {
317             $this->_configDataObjectsCache();
318         }
319         
320         $this->_parseConfigTemplate();
321         $this->_parseConfigMail();
322  
323         //echo '<PRE>';print_r($this);exit;
324         
325         //$this->_exposeToPear();
326                 
327
328         $this->_validateEnv();
329         
330         if ($ishelp) {
331             return;
332         }
333
334         $this->_validateDatabase();
335  
336         $this->_validateTemplate();
337         
338     }
339     /**
340      *
341      *
342      *'languages' => array(
343             'param' => '_lang',
344             'avail' => array('en','zh_HK', 'zh_CN'),
345             'default' => 'en',
346             'cookie' => 'TalentPricing_lang',
347             'localemap' => array(
348                 'en' => 'en_US.utf8',
349                 'zh_HK' => 'zh_TW.utf8',
350                 'zh_CN' => 'zh_CN.utf8',
351             )
352         ),
353     */
354     
355     function _handleLanguages()
356     {
357         if (
358                 empty($this->languages) ||
359                 (
360                         !isset($this->languages['cookie']) && !isset($this->languages['default'])
361                 )
362         ) {
363             return;
364         }
365         
366         $cfg = $this->languages;
367         
368         $default = $cfg['default'];
369         
370         if(!empty($_SERVER["HTTP_ACCEPT_LANGUAGE"])){
371             
372             $brower_langs = explode(",", $_SERVER["HTTP_ACCEPT_LANGUAGE"]);
373             
374             foreach ($brower_langs as $bl) {
375                 $l = preg_replace('/;(.*)/', '', $bl);
376                 
377                 $l = str_replace('-', '_', $l);
378                 
379                 if(!in_array($l, $cfg['avail'])){
380                     continue;
381                 }
382                 
383                 $default = $l;
384                 break;
385             }
386         }
387            
388         $lang = isset($_COOKIE[$cfg['cookie']]) ?  $_COOKIE[$cfg['cookie']] : $default;
389
390         if (isset($_REQUEST[$cfg['param']])) {
391             $lang = $_REQUEST[$cfg['param']];
392         }
393     
394         if (!in_array($lang, $cfg['avail'])) {
395             $lang = $cfg['default'];
396         }
397         if (isset($cfg['localemap'][$lang])) {
398             setlocale(LC_ALL, $cfg['localemap'][$lang]);
399         }
400         setcookie($cfg['cookie'], $lang, 0, '/');
401         
402         $this->locale = $lang;
403         
404         if (!empty($this->HTML_Template_Flexy)) {
405             $this->HTML_Template_Flexy['locale'] = $lang;   //set a language for template engine
406         }
407          
408     }
409     
410     /**
411      * overlay array properties..
412      */
413     
414     function applyIf($prop, $ar) {
415         if (!isset($this->$prop)) {
416             $this->$prop = $ar;
417             return;
418         }
419         // add only things that where not set!!!.
420         $this->$prop = array_merge($ar,$this->$prop);
421         
422         return;
423         //foreach($ar as $k=>$v) {
424         //    if (!isset($this->$prop->$k)) {
425          //       $this->$prop->$k = $v;
426           //  }
427        // }
428     }
429     
430     /**
431      * DataObject cache 
432      * - if turned on (dataObjectsCache = true) then 
433      *  a) ini file points to a parsed version of the structure.
434      *  b) links.ini is a merged version of the configured link files.
435      * 
436      * This only will force a generation if no file exists at all.. - after that it has to be called manually 
437      * from the core page.. - which uses the Expires time to determine if regeneration is needed..
438      * 
439      * 
440      */
441     
442     function _configDataObjectsCache()
443     {
444         // cli works under different users... it may cause problems..
445         $this->debug(__METHOD__);
446         if (function_exists('posix_getpwuid')) {
447             $uinfo = posix_getpwuid( posix_getuid () ); 
448             $user = $uinfo['name'];
449         } else {
450             $user = getenv('USERNAME'); // windows.
451         }
452         
453         
454
455         $iniCache = ini_get('session.save_path') .'/' . 
456                'pdocfg-' . $user . '/'. str_replace('/', '_', $this->project) ;
457         
458         
459         if ($this->appNameShort) {
460             $iniCache .= '_' . $this->appNameShort;
461         }
462         if ($this->version) {
463             $iniCache .= '.' . $this->version;
464         }
465         if ($this->database === false) {
466             return;
467         }
468         
469         $dburl = parse_url($this->database);
470         if (!empty($dburl['path'])) {
471             $iniCache .= '-'.ltrim($dburl['path'],'/');
472         }
473         
474         $iniCache .= '.ini';
475         $this->debug(__METHOD__ . " : ini cache : $iniCache");
476         
477         $dburl = parse_url($this->database);
478          //override ini setting... - store original..
479         
480         if (isset($this->PDO_DataObject['schema_location'])) {
481             $this->dataObjectsOriginalIni = $this->PDO_DataObject['schema_location'];
482             ///print_r($this->DB_DataObject);exit;
483         }
484         // 
485         $this->PDO_DataObject['schema_location']   = $iniCache;
486         PDO_DataObject::config($this->PDO_DataObject);
487         
488         // we now have the configuration file name..
489          
490         if (!file_exists($iniCache) || empty( $this->dataObjectsCacheExpires)) {
491             $this->generateDataobjectsCache(true);
492             return;
493         }
494      
495         
496         
497     }
498     /**
499      * generateDataobjectsCache:
500      *
501      * if the 'cache file' does not exist, then
502      * create it using HTML_FlexyFramework2_Generator
503      * 
504      * create xxx.ini and xxx.links.ini 
505      * 
506      * @arg force (boolean) force generation - default false;
507      * 
508      */
509      
510     function generateDataobjectsCache($force = false)
511     {
512         //$this->debug('generateDataobjectsCache: force=' . ($force ? 'yes' : 'no'));
513         if (!$this->dataObjectsCache) { // does not use dataObjects Caching..
514              PDO_DataObject::config('schema_location', $this->dataObjectsOriginalIni );
515             
516             $this->debug('generateDataobjectsCache', 'dataObjectsCache - empty');
517             return;
518         }
519         
520         $dburl = parse_url($this->database);
521         $dbnick =  basename($dburl['path']);
522         
523         
524        
525         $iniCache = $this->PDO_DataObject['schema_location'];
526         
527         if ($force && file_exists($iniCache)) {
528             $files = glob(dirname($iniCache).'/*.ini');
529             foreach($files as $f) {
530                 unlink($f);
531             } 
532             clearstatcache();
533         }
534         
535         $iniCacheTmp = $iniCache . '.tmp' .md5(rand());  // random to stop two processes using the same file.
536         // has it expired..
537         $force = ($force ? $force : !file_exists($iniCache)) || !$this->dataObjectsCacheExpires;
538         // $this->debug('generateDataobjectsCache: after check : force=' . ($force ? 'yes' : 'no'));
539          // not force or not expired, do not bother..
540         if (!$force) {
541             if ((filemtime($iniCache) + $this->dataObjectsCacheExpires) >time()) {
542                 return;
543             }
544         }
545         
546         
547         $this->debug('generateDataobjectsCache', 'dataObjectsCache  generating');
548          //echo "GENERATE?";
549         
550         // force quoting of column names..
551         // unless it forced off..
552         if (!isset($this->PDO_DataObject['quote_identifiers'] )) { 
553             $this->PDO_DataObject['quote_identifiers'] = true;
554         }
555         if (!file_exists(dirname($iniCache))) {
556             mkdir(dirname($iniCache),0700, true);
557         }
558         
559         $this->PDO_DataObject['schema_location'] = $iniCacheTmp;
560         
561         PDO_DataObject::reset();
562         
563         PDO_DataObject::config($this->PDO_DataObject);
564         
565         
566         
567         // DB_DataObject::debugLevel(1);      
568         require_once 'HTML/FlexyFramework2/Generator.php';
569         
570         PDO_DataObject::config('database', $this->database);
571         $generator = new HTML_FlexyFramework2_Generator();
572         $generator->start();
573         
574         $this->debug('generateDataobjectsCache', 'dataObjectsCache  writing');
575         HTML_FlexyFramework2_Generator::writeCache($iniCacheTmp, $iniCache); 
576         // reset the cache to the correct lcoation.
577         PDO_DataObject::config('schema_location',  $iniCache);
578         $this->PDO_DataObject['schema_location'] = $iniCache;
579         //$this->_exposeToPear();
580         
581         //$GLOBALS['_DB_DATAOBJECT']['INI'][$this->database] =   parse_ini_file($iniCache, true);
582         //$GLOBALS['_DB_DATAOBJECT']['SEQUENCE']
583         // clear any dataobject cache..
584           
585         
586         //die("done");
587         
588     }
589     /**
590      * DataObject Configuration:
591      * Always in Project/DataObjects
592      * unless enableArray is available...
593      * 
594      * 
595      * 
596      */
597     function _parseConfigDataObjects()
598     {
599         if ($this->nodatabase && !$this->database) {
600             return;
601         }
602         
603         // better done here.. ?? only include if we have database configuration?
604         require_once 'PDO/DataObject.php';
605         
606         $dburl = parse_url($this->database);
607         $dbini = 'ini_'. basename($dburl['path']);
608                 
609         $dbinis =  array(); //array(dirname(__FILE__) . '/Pman/DataObjects/pman.ini');
610         $dbreq =  array(); //array( dirname(__FILE__) . '/Pman/DataObjects/');
611         $dbcls =  array(); //array('Pman_DataObjects_');
612
613         $project = explode('/',$this->project)[0]; 
614         
615         if (!empty($this->enableArray)) {
616                 
617             $tops = array_merge( array($project), empty($this->projectExtends) ? array() : $this->projectExtends);
618             
619             foreach($tops as $td) {
620                  $bd = $this->rootDir .'/'.$td;
621                 foreach($this->enableArray as $m) {
622                     // look in Pman/MODULE/DataObjects/*
623                      if (file_exists($bd.'/'.$m.'/DataObjects')) {
624                         $dbinis[] = $bd.'/'.$m.'/DataObjects/'. strtolower($td).'.ini';
625                         $dbcls[] = $td.'_'. $m . '_DataObjects_';
626                         $dbreq[] = $bd.'/'.$m.'/DataObjects';
627                         continue;
628                     }
629                     // look in MODULE/DataObjects ?? DO WE SUPPORT THIS ANYMORE???
630                     if (file_exists($bd.'/../'.$m.'/DataObjects')) {
631                         $dbinis[] = $bd.'/../'.$m.'/DataObjects/'. strtolower($td).'.ini';
632                         $dbcls[] = $td. '_DataObjects_';
633                         $dbreq[] = $bd.'/../'.$m.'/DataObjects';
634                     }
635                         
636                         
637                       
638                 }
639             }     
640         } else {
641             
642             if (isset($this->PDO_DataObject['schema_location'])) {
643                 $dbinis[] = $this->PDO_DataObject['schema_location'] .'/'.basename($dburl['path']).'.ini';
644             } else {
645                 $dbinis[] = $this->baseDir.'/DataObjects/'.basename($dburl['path']).'.ini';
646             }
647             // non modular.
648             
649             $dbcls[] = $project .'_DataObjects_';
650             $dbreq[] = $this->baseDir.'/DataObjects';
651         }
652             
653         
654         $this->applyIf('PDO_DataObject', array(   
655         
656             'class_location' =>  implode(PATH_SEPARATOR,$dbreq),
657             'class_prefix' =>  implode(PATH_SEPARATOR,$dbcls),
658             'database'        => $this->database,    
659             ///'require_prefix' => 
660          //   'schema_location' => dirname(__FILE__) . '/Pman/DataObjects/',
661              'schema_location' => implode(PATH_SEPARATOR,$dbinis),
662          
663            //   'debug' => 5,
664         ));
665         PDO_DataObject::config($this->PDO_DataObject);
666         
667     }
668     /**
669      Set up thetemplate
670      * 
671      */
672     function _parseConfigTemplate()
673     {
674         
675         // compile.
676         if (function_exists('posix_getpwuid')) {
677             $uinfo = posix_getpwuid( posix_getuid () ); 
678          
679             $user = $uinfo['name'];
680         } else {
681             $user = getenv('USERNAME'); // windows.
682         }
683         
684         $compileDir = ini_get('session.save_path') .'/' . 
685             $user . '_compiled_templates_' . $this->project;
686         
687         if ($this->appNameShort) {
688             $compileDir .= '_' . $this->appNameShort;
689         }
690         if ($this->version) {
691             $compileDir .= '.' . $this->version;
692         }
693         
694         // templates. -- all this should be cached!!!
695         $src = array();
696          
697         
698         if ($this->appNameShort && !in_array('Core', explode(',', $this->disable ? $this->disable : ''))) {
699             // in app based version, template directory is in Core
700             
701             $src = array(  
702                 $this->baseDir . '/Core/templates'
703             );
704         }
705         
706         if(!empty($this->projectExtends)){
707             foreach ($this->projectExtends as $e){
708                 $add = $this->rootDir . '/' . $e .'/templates';
709                 if (!in_array($add,$src) && file_exists($add)) {
710                     $src[] = $add;
711                 }
712             }
713         }
714         
715         $src[] = $this->baseDir . '/templates';
716         
717         
718         
719         if (!empty($this->enableArray)) {
720              
721             
722             foreach($this->enableArray as $m) {
723                 $add = $this->baseDir . '/' . $m .'/templates';
724                 if (!in_array($add,$src) && file_exists($add) && $this->appNameShort != $m) {
725                     $src[] = $add;
726                 }
727                 
728             }
729             if (!empty($this->projectExtends)  )  {
730                 foreach ($this->projectExtends as $extend){
731                     foreach($this->enableArray as $m) {
732                         $add = $this->rootDir . '/' . $extend . '/' . $m .'/templates';
733                         if (!in_array($add,$src) && file_exists($add) && $this->appNameShort != $m) {
734                             $src[] = $add;
735                         }
736                     }
737                 }
738     
739             }
740         }
741          
742         
743         if ($this->appNameShort) {
744             $src[] =  $this->baseDir . '/'. $this->appNameShort. '/templates';
745         }
746         
747         // images may come from multiple places: - if we have multiple template directories.?
748         // how do we deal with this..?
749         // images/ << should always be mapped to master!
750         // for overridden appdir ones we will have to se rootURL etc.
751         
752         $url_rewrite = 'images/:'. $this->rootURL . '/'. $this->project. '/templates/images/';
753         
754         $this->applyIf('HTML_Template_Flexy', array(
755             'templateDir' => implode(PATH_SEPARATOR, $src),
756             'compileDir' => $compileDir,
757             'multiSource' => true,
758             'forceCompile' => 0,
759             'url_rewrite' => $url_rewrite,
760             'filters' => 'Php,SimpleTags', /// for non-tokenizer version?
761             'debug' => $this->debug ? 1 : 0,
762             'useTokenizer' => 1,
763              
764             
765         
766         
767         ));
768     } 
769     
770     function _parseConfigMail()
771     {
772         $this->applyIf('HTML_Template_Flexy', array(
773            'debug' => 0,
774            'driver' => 'smtp',
775            'host' => 'localhost',
776            'port' => 25,
777         ));
778     }
779     
780     /**
781      * exposes to PEAR::getStaticProperty..
782      * at present, only FlexyFramework uses this..
783      * // we should really stop it..
784      * ctor for Flexy - should read from config...
785      */
786     /*
787     function _exposeToPear()
788     {
789         $cls = array_keys(get_class_vars(__CLASS__));
790         $base = array();
791         
792         // anything that get's set, that's not in our default properties
793         // is assumed to be an option set .
794         foreach(get_object_vars($this) as $k=>$v) {
795             if (in_array($k,$cls)) {
796                 $base[$k] = $v;
797                 continue;
798             }
799             $options = &PEAR::getStaticProperty($k,'options');
800             $options = $v;
801         }
802        // $options = &PEAR::getStaticProperty('HTML_FlexyFramework2','options');
803        // $options = $base;
804          //   apply them..
805     }
806     */
807     
808     
809     function _validateEnv() 
810     {
811         /* have I been initialized */
812         
813         /* 
814         if (get_magic_quotes_gpc() && !$this->cli) {
815             $this->fatalError(
816                 "magic quotes is enabled add the line<BR>
817                    php_value magic_quotes_gpc 0<BR>
818                    to your .htaccess file <BR>
819                    (Apache has to be configured to &quot;AllowOverride Options AuthConfig&quot; for the directory)
820                    ");
821                 
822         }
823         */
824         // set up error handling - 
825         //$this->error = new HTML_FlexyFramework2_Error();
826         
827         /// fudge work around bugs in PEAR::setErrorHandling(,)
828         //$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_CALLBACK;
829         //$GLOBALS['_PEAR_default_error_options'] = array($this->error,'raiseError');
830         
831         
832         
833         if ($this->debug) {
834             require_once 'Benchmark/Timer.php'; 
835             $this->timer = new BenchMark_Timer(true);
836             register_shutdown_function(function() { echo $this->timer->getOutput(); });
837         }
838
839     }
840     
841     function _validateDatabase()
842     {
843         //echo "<PRE>"; print_r($this);
844
845         if ($this->nodatabase) {
846             return;
847         }
848         
849         // database is the only setting - we dont support mult databses?
850           
851             
852         $x = new PDO_Dataobject();
853         PDO_Dataobject::config('database', $this->database);
854         try {
855             $x->PDO();
856         } catch (Exception $e) {
857                                 
858
859                 $this->fatalError("Configuration or Database Error: could not connect to Database, <BR>
860                     Please check the value given to HTML_FlexyFramework2, or run with debug on!<BR>
861                      <BR> ".$e->getMessage());
862         }
863         
864         
865         
866     }
867     function _validateTemplate()
868     {
869         // check that we have a writeable directory for flexy's compiled templates.
870         
871         if (empty($this->HTML_Template_Flexy['compileDir'])) {
872             return;
873         }
874         
875         if ( !file_exists($this->HTML_Template_Flexy['compileDir']))  {
876             mkdir($this->HTML_Template_Flexy['compileDir'], 0700, true);
877             clearstatcache();
878              
879             if ( !file_exists($this->HTML_Template_Flexy['compileDir']))  {
880             
881                 $this->fatalError("Configuration Error: you specified a directory that does not exist for<BR>
882                     HTML_Template_Flexy => compileDir  {$this->HTML_Template_Flexy['compileDir']}<BR>\n"
883                 );
884             }
885         }
886         
887         if (!is_writeable($this->HTML_Template_Flexy['compileDir'])) {
888             $this->fatalError("Configuration Error: Please make sure the template cache directory is writeable<BR>
889                     eg. <BR>
890                     chmod 700 {$this->HTML_Template_Flexy['compileDir']}<BR>
891                     chgrp apache_user  {$this->HTML_Template_Flexy['compileDir']}<BR>\n"
892             );
893         }
894         //echo "<PRE>";print_R($config);
895         
896         
897          
898           
899         
900         
901     }
902   
903   
904    
905         
906     
907     
908     /**
909     * Quality Redirector
910     *
911     * Usage in a page.:
912     * HTML_FlexyFramework::run('someurl/someother',array('somearg'=>'xxx'));
913     * ...do clean up...
914     * exit; <- dont cary on!!!!
915     *
916     * You should really
917     * 
918     * @param   string           redirect to url 
919     * @param   array Args Optional      any data you want to send to the next page..
920     * 
921     *
922     * @return   false
923     * @access   public
924     * @static
925     */
926   
927     
928     static function run($request,$args=array()) 
929     {
930         self::$singleton->_run($request,true,$args);
931         return false;
932     }
933     
934     /**
935      * initPage - load the page up, and set the variables, but do not actually run it.
936      * - used where we want features from this framework, buy may actually be running another one..
937      *
938      * call with ($_SERVER["SCRIPT_NAME"]) for root page...
939      * 
940      */
941     public function initPage($request, $isRedirect=false, $args=array())
942     {
943         $newRequest = $this->_getRequest($request,$isRedirect);
944         
945         // find the class/file to load
946         list($classname,$subRequest) = $this->requestToClassName($newRequest,FALSE);
947         
948         
949         $this->debug("requestToClassName return = CLASSNAME: $classname SUB REQUEST: $subRequest");
950         
951         // assume that this was handled by getclassname ?????
952         if (!$classname) {
953             return false;
954         }
955         
956         // make page data/object accessable at anypoint in time using  this
957         // not sure if this is used anymore - or even works..?
958         
959         $classobj =  new  $classname();  // normally do not have constructors.
960         
961         $classobj->baseURL = $this->baseURL;
962         $classobj->rootURL = $this->rootURL;
963         $classobj->rootDir = $this->rootDir;
964         $classobj->bootLoader  = $this;
965         $classobj->request = $newRequest;
966         $classobj->timer = &$this->timer;
967         $classobj->subrequest = $subRequest;
968          
969         $this->page = $classobj;
970         return $classobj;
971     }
972     
973     
974     /**
975     * The main execution loop
976     *
977     * recursivly self called if redirects (eg. return values from page start methods)
978     * 
979     * @param   string from $_REQUEST or redirect from it'self.
980     * @param   boolean isRedirect  = is the request a redirect  TRUE = always handle as 'GET', 1 = handle as POST/GET depending on method, 0 = handle as initial request.
981     *
982     *
983     * @return   false || other    false indicates no page was served!
984     * @access   public|private
985     * @see      see also methods.....
986     */
987   
988     public function _run($request,$isRedirect = false,$args = array()) 
989     {
990         
991         // clean the request up.
992         $this->calls++;
993         
994         if ($this->calls > 5) {
995             // to many redirections...
996             trigger_error("FlexyFramework:: too many redirects - backtrace me!",E_USER_ERROR);
997             exit;
998         }
999         
1000         $classobj  = $this->initPage($request,$isRedirect,$args);
1001         $classobj->cli = $this->cli;
1002         
1003         if ($this->cli && !$isRedirect ) { // redirect always just takes redirect args..
1004             require_once 'HTML/FlexyFramework2/Cli.php';
1005             $fcli = new HTML_FlexyFramework2_Cli($this);
1006             $nargs = $fcli->cliParse(get_class($classobj));
1007             $args = $nargs === false ? $args : $nargs; /// replace if found.
1008             $classobj->cli_args = $nargs;
1009         }
1010         
1011         // echo '<PRE>'; print_r($this);exit;
1012         // echo "CHECK GET AUTH?";
1013         if (!method_exists($classobj, 'getAuth')) {
1014         //    echo "NO GET AUTH?";
1015             $this->fatalError("class ". get_class($classobj) . " does not have a getAuth Method");
1016             return false;
1017         }
1018         
1019         /* check auth on the page */
1020         if (is_string($redirect = $classobj->getAuth())) {
1021             $this->debug("GOT AUTH REDIRECT".$redirect);
1022             return $this->_run($redirect,TRUE);
1023         }
1024         // used HTML_FlexyFramework::run();
1025                  
1026
1027         if ($redirect === false) {
1028             $this->debug("GOT AUTH FALSE");    
1029             return false; /// Access deined!!! - 
1030         }
1031      
1032         // allow the page to implement caching (for the full page..)
1033         // although normally it should implement caching on the outputBody() method.
1034         
1035         if (method_exists($classobj,"getCache")) {
1036             if ($result = $classobj->getCache()) {
1037                 return $result;
1038             }
1039         }
1040         /* allow redirect from start */
1041         if (method_exists($classobj,"start")) {
1042             if (is_string($redirect = $classobj->start($classobj->subrequest,$isRedirect,$args)))  {
1043                 $this->debug("REDIRECT $redirect <BR>");
1044                 return $this->_run($redirect,TRUE);
1045             }
1046             if ($redirect === false) {
1047                 return false;
1048             }
1049         }
1050                 
1051
1052          // used HTML_FlexyFramework::run();
1053         
1054         /* load the modules 
1055          * Modules are common page components like navigation headers etc.
1056          * that can have dynamic code.
1057          * Code has been removed now..
1058          */
1059         
1060         
1061         if ($this->timer) {
1062             $this->timer->setMarker("After $request loadModules Modules"); 
1063         }
1064         
1065         /* output it  - (our base page does not implement output for cli. */
1066         
1067         if ( method_exists($classobj,'output')) {
1068             $classobj->output(); 
1069         }
1070         
1071         
1072         if ($this->timer) {
1073             $this->timer->setMarker("After $request output"); 
1074             $this->timer->stop(); //?? really - yes...
1075            
1076             
1077         }
1078         
1079         if ($this->cli) {
1080             return true;
1081         }
1082         
1083         
1084         exit; /// die here...
1085         
1086     }
1087     
1088     /**
1089     * map the request into an object and run the page.
1090     *
1091     * The core of the work is done here.
1092     * 
1093     * 
1094     * @param   request  the request string
1095     * @param   boolean isRedirect - indicates that it should not attempt to strip the .../index.php from the request.
1096     * 
1097     * @access  private
1098     */
1099   
1100     function _getRequest($request, $isRedirect) 
1101     {
1102         
1103         
1104         
1105         if ($this->cli) {
1106             return $request;
1107         }
1108         
1109         $startRequest = $request;
1110         $rq = explode('?', $request);
1111         $request =  array_shift($rq);
1112         $this->debug("INPUT REQUEST $request<BR>");
1113         if (!$isRedirect) {
1114             // check that request forms contains baseurl????
1115              
1116             $subreq = substr($request,0,strlen($this->baseURL));
1117             if ($subreq != substr($this->baseURL,0,strlen($subreq))) {
1118                 $this->fatalError(
1119                     "Configuration error: Got base of $subreq which does not 
1120                         match configuration of: $this->baseURL} ");
1121             }
1122             $request = substr($request,strlen($this->baseURL));
1123              
1124         }
1125         // strip front
1126         // echo "REQUEST WAS: $request<BR>";
1127         // $request = preg_replace('/^'.preg_quote($base_url,'/').'/','',trim($request));
1128         // echo "IS NOW: $request<BR>";
1129         // strip end
1130         // strip valid html stuff
1131         //$request = preg_replace('/\/[.]+/','',$request);
1132         
1133
1134         $request = preg_replace('/^[\/]*/','',$request);
1135         $request = preg_replace('/\?.*$/','',$request);
1136         $request = preg_replace('/[\/]*$/','',$request);
1137         $this->baseRequest = $request;
1138         $request = str_replace('&','',$request); // any other invalid characters???
1139         $request = preg_replace('/\.([a-z]+)$/','',$request);
1140         $this->ext = substr($this->baseRequest , strlen($request));
1141         
1142         // REDIRECT ROO to index.php! for example..
1143         
1144         // this has a slight issue when we are using 'hidden index.php' mod_rewrite..
1145         
1146         /*
1147          normal (non-mod_rewrite)
1148          $_SERVER['REQUEST_URI'] start with baseURL..
1149          */
1150         if (isset($_SERVER['REQUEST_URI']) && substr($_SERVER['REQUEST_URI'], 0, strlen($this->baseURL)) != $this->baseURL) {
1151             // then we are rewriting?
1152             $this->baseURL = $this->rootURL;
1153             return $request;
1154         }
1155         
1156         //var_dump(array($this->baseURL, $this->rootURL, $request ,$isRedirect, $_SERVER['REQUEST_URI'])); phpinfo();exit;
1157         if (!$request && !$isRedirect) {
1158             
1159             
1160             if ($this->baseURL && (strlen($startRequest) < strlen($this->baseURL))) {
1161                 
1162                 // needs to handle https + port
1163                 $http = ((!empty($_SERVER["HTTPS"]) && $_SERVER["HTTPS"]  == 'on')) ? 'https' : 'http';
1164                 $sp = '';
1165                 if (!empty($_SERVER['SERVER_PORT'])) {
1166                     if ((($http == 'http') && ($_SERVER['SERVER_PORT'] == 80)) || (($http == 'https') && ($_SERVER['SERVER_PORT'] == 443))) {
1167                         // standard ports..
1168                     } else {
1169                         $sp .= ':'.((int) $_SERVER['SERVER_PORT']);
1170                     }
1171                 }
1172                 //var_dump($startRequest);      echo '<PRE>'; print_R($this);         phpinfo();exit;
1173                 $host = !empty($_SERVER["HTTP_X_FORWARDED_HOST"]) ? $_SERVER["HTTP_X_FORWARDED_HOST"] : $_SERVER["HTTP_HOST"];
1174                 header('Location: '.$http.'://'.$host .$sp . $this->baseURL);
1175  
1176                 exit;
1177             }
1178             $request = "";
1179         }
1180         $this->debug("OUTPUT REQUEST $request<BR>");
1181         return $request;
1182     }
1183     
1184    
1185     
1186     
1187     /**
1188     * get the Class name and filename to load
1189     *
1190     * Parses the request and converts that into a File + Classname
1191     * if the class doesnt exist it will attempt to find a file below it, and
1192     * call that one with the data.
1193     * Used by the module loader to determine the location of the modules
1194     *   
1195     * @param   request  the request string
1196     * @param   boolean showError - if false, allows you to continue if the class doesnt exist.
1197     * 
1198     *
1199     * @return   array classname, filepath
1200     * @access   private
1201     * @static
1202     */
1203   
1204     function requestToClassName($request,$showError=TRUE) 
1205     {
1206        // if ($request == "error") {
1207        //     return array("HTML_FlexyFramework_Error","");
1208        // }
1209         
1210         // special classes ::
1211         if ($this->cli && in_array($request, array('DataObjects'))) {
1212             require_once 'HTML/FlexyFramework2/'. $request . '.php';
1213             return array('HTML_FlexyFramework2_'. $request,'');
1214         }
1215         
1216         
1217         $request_array=explode("/",$request);
1218         $original_request_array = $request_array;
1219         $sub_request_array = array();
1220         $l = count($request_array)-1;
1221         if ($l > 10) { // ?? configurable?
1222             //PEAR::raiseError("Request To Long");
1223             $this->fatalError("Request To Long - " . $request);
1224         }
1225
1226         
1227         $classname='';
1228         // tidy up request array
1229         
1230         if ($request_array) {
1231             foreach(array_keys($request_array) as $i) {
1232                 $request_array[$i] = preg_replace('/[^a-z0-9]/i','_',urldecode($request_array[$i]));
1233             }
1234         }
1235         //echo "<PRE>"; print_r($request_array);
1236         // technically each module should do a check here... similar to this..
1237         
1238         
1239         for ($i=$l;$i >-1;$i--) {
1240             $location = implode('/',$request_array) . ".php";
1241             if ($location == '.php') {
1242                 $this->debug("SKIP first path check, as request str is empty");
1243                 break;
1244             }
1245             
1246             $this->debug("baseDIR = {$this->baseDir}");
1247             
1248             $floc = "{$this->baseDir}/$location";
1249             $this->debug("CHECK LOCATION = $location");
1250             
1251             
1252             
1253             if (!empty($location) && $location != '.php' && @file_exists($floc )) {             // hide? error???
1254                 require_once $floc ;
1255                 $classname = $this->classPrefix . implode('_',$request_array);
1256                 $this->debug("FOUND FILE - SET CLASS = $classname <BR>");
1257                 break;
1258             } 
1259             
1260             // in here check the 'projectExtends' versions..?
1261             
1262             if(!empty($this->projectExtends)){
1263                 $this->debug("Trying project Extends<BR>");
1264                 $has_extend_class = false;
1265                 
1266                 foreach ($this->projectExtends as $e){
1267                     $floc = "{$this->rootDir}/{$e}/$location";
1268                     $this->debug("Trying file: $floc");
1269                     if (!empty($location) && @file_exists($floc)) {             // hide? error???
1270                         require_once $floc ;
1271                         $classname = $e . '_' . implode('_',$request_array);
1272                         $has_extend_class = true;
1273                         $this->debug("FOUND FILE - SET CLASS = $classname <BR>");
1274                         break;
1275                     } 
1276                 }
1277                 
1278                 if(!empty($has_extend_class)){
1279                     break;
1280                 }
1281                 
1282             }
1283             
1284             
1285             $this->debug("$floc  - !!FOUND NOT FILE!!");
1286             
1287             $sub_request_array[] = $original_request_array[$i];
1288             unset($request_array[$i]);
1289             unset($original_request_array[$i]);
1290         }
1291          
1292         // is this really needed here!
1293         
1294         $classname = preg_replace('/[^a-z0-9]/i','_',$classname);
1295         $this->debug("CLASSNAME is '$classname'");
1296         // got it ok.
1297         if ($classname && class_exists($classname)) {
1298             $this->debug("using $classname");
1299             //print_r($sub_request_array);
1300             return array($classname,implode('/',array_reverse($sub_request_array)));
1301         }
1302         // stop looping..
1303         if ($showError) {
1304             $this->fatalError("INVALID REQUEST: \n $request FILE:".$this->baseDir. "/{$location}  CLASS:{$classname}");
1305             
1306         } 
1307         
1308         
1309         $this->debug("Try base {$this->baseDir}.php");   
1310         // try {project name}.php
1311         // this used to be silenced @ - if this fails we are usually pretty fried..
1312         
1313         if (file_exists($this->baseDir.'.php')) {
1314             
1315             
1316             $classname = str_replace('/', '_', $this->project); //   basename($this->baseDir);
1317             
1318             $this->debug("FOUND {$this->baseDir} requring and checking class $classname");   
1319             require_once $this->baseDir.'.php';
1320             $this->debug("require success");
1321             
1322             if (!class_exists($classname)) {
1323                 $this->fatalError( "{$this->baseDir}.php did not contain class $classname");
1324             }
1325         }
1326         // got projectname.php
1327         if ($classname && class_exists($classname)) {
1328             $this->debug("using $classname");
1329             //print_r($sub_request_array);
1330              
1331             return array($classname,implode('/',array_reverse($sub_request_array)));
1332         }    
1333             
1334         
1335         $this->fatalError( "can not find {$this->baseDir}.php"); // dies..
1336               
1337      
1338     }
1339     
1340     /**
1341     * ensure Single CLi process 
1342     * usage:
1343     * HTML_FlexyFramework2::ensureSingle(__FILE__, $this);
1344     * @param string filename of running class
1345     * @param object class
1346     */
1347       
1348     static function ensureSingle($sig, $class) 
1349     {
1350         //echo "check single: $sig / ". get_class($class) ."\n";
1351         $ff = HTML_FlexyFramework2::get();
1352         if (function_exists('posix_getpwuid')) {
1353             $uinfo = posix_getpwuid( posix_getuid () ); 
1354             $user = $uinfo['name'];
1355         } else {
1356             $user = getenv('USERNAME'); // windows.
1357         }
1358         $fdir = ini_get('session.save_path') .'/' . 
1359                 $user . '_cli_' . $ff->project ;
1360      
1361         
1362         if (!file_exists($fdir)) {
1363             mkdir($fdir, 0777);
1364         }
1365         
1366         $lock = $fdir.'/'. md5($sig) . '.' . get_class($class);
1367         //echo "check single: lock : $lock\n";
1368         if (!file_exists($lock)) {
1369             file_put_contents($lock, getmypid());
1370             //echo "check single: lock : DOES NOT EXIST\n";
1371             return true;
1372         }
1373         $oldpid = file_get_contents($lock);
1374         if (!file_exists('/proc/' . $oldpid)) {
1375             
1376             file_put_contents($lock, getmypid());
1377           //  echo "check single: lock : PROC NOT EXIST\n";
1378             return true;
1379         }
1380         // file exists, but process might not be the same..
1381         $ca = explode('_', get_class($class));
1382         $name = array_pop($ca);
1383         $cmd = file_get_contents('/proc/' . $oldpid.'/cmdline');
1384         if (!preg_match('/php/i',$cmd) || !preg_match('/'.$name.'/i',$cmd)) {
1385             file_put_contents($lock, getmypid());
1386             //echo "check single: lock : CMDLINE !have PHP \n";
1387             return true;
1388         }
1389         die("process " . $sig . " already running\n");
1390         
1391     }
1392     /**
1393      * removes the lock for the applicaiton - use with care...
1394      *
1395      *
1396      */
1397     static function ensureSingleClear($sig, $class)
1398     {
1399         $ff = HTML_FlexyFramework2::get();
1400         if (function_exists('posix_getpwuid')) {
1401             $uinfo = posix_getpwuid( posix_getuid () ); 
1402             $user = $uinfo['name'];
1403         } else {
1404             $user = getenv('USERNAME'); // windows.
1405         }
1406         $fdir = ini_get('session.save_path') .'/' . 
1407                 $user . '_cli_' . $ff->project ;
1408      
1409         
1410         if (!file_exists($fdir)) {
1411             mkdir($fdir, 0777);
1412         }
1413         $lock = $fdir.'/'. md5($sig);
1414         if (!file_exists($lock)) {
1415             
1416             return true;
1417         }
1418         unlink($lock);;
1419     }
1420     
1421     
1422     /**
1423     * Debugging 
1424     * 
1425     * @param   string  text to output.
1426     * @access   public
1427     */
1428   
1429     function debug($output) {
1430        
1431         if (empty($this->debug)) {  
1432             return;
1433         }
1434         echo $this->cli ? 
1435               "HTML_FlexyFramework2::debug  - ".$output."\n" 
1436             : "<B>HTML_FlexyFramework2::debug</B> - ".$output."<BR>\n";
1437     
1438     }
1439     /**
1440     * Raises a fatal error. - normally only used when setting up to help get the config right.
1441     * 
1442     * can redirect to fatal Action page.. - hoepfully not issued before basic vars are set up..
1443     * 
1444     * @param   string  text to output.
1445     * @access   public
1446     */
1447     
1448     function fatalError($msg,$showConfig = 0) 
1449     {
1450         
1451         
1452          if ($this->fatalAction) {
1453             HTML_FlexyFramework2::run($this->fatalAction,$msg);
1454             exit;
1455         }
1456         header('HTTP/1.1 503 Service Temporarily Unavailable');
1457         header('Status: 503 Service Temporarily Unavailable');
1458         header('Retry-After: 300');
1459         echo $this->cli ? $msg ."\n" : "<H1>$msg</H1>configuration information<PRE>";
1460         if ($showConfig) {
1461             
1462             print_r($this);
1463         }
1464         $ff = HTML_FlexyFramework2::get();
1465         $ff->debug($msg);
1466         exit;
1467     }    
1468 }
1469