use md5sum on ini files to determine if they need re-writing
[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         
528         $replace = array();
529         
530         if (file_exists($iniCache)) {
531             $files = glob(dirname($iniCache).'/*.ini');
532             foreach($files as $f) {
533                 $replace[$f] = md5(file_get_contents($f)); // hash it..
534                
535             }
536         }
537         
538       
539         
540         $iniCacheTmp = $iniCache . '.tmp' .md5(rand());  // random to stop two processes using the same file.
541         // has it expired..
542         $force = ($force ? $force : !file_exists($iniCache)) || !$this->dataObjectsCacheExpires;
543         // $this->debug('generateDataobjectsCache: after check : force=' . ($force ? 'yes' : 'no'));
544          // not force or not expired, do not bother..
545         if (!$force) {
546             if ((filemtime($iniCache) + $this->dataObjectsCacheExpires) >time()) {
547                 return;
548             }
549         }
550         
551         
552         $this->debug('generateDataobjectsCache', 'dataObjectsCache  generating');
553          //echo "GENERATE?";
554         
555         // force quoting of column names..
556         // unless it forced off..
557         if (!isset($this->PDO_DataObject['quote_identifiers'] )) { 
558             $this->PDO_DataObject['quote_identifiers'] = true;
559         }
560         if (!file_exists(dirname($iniCache))) {
561             mkdir(dirname($iniCache),0700, true);
562         }
563         
564         $this->PDO_DataObject['schema_location'] = $iniCacheTmp;
565         
566         PDO_DataObject::reset();
567         
568         PDO_DataObject::config($this->PDO_DataObject);
569         
570         
571         
572         // DB_DataObject::debugLevel(1);      
573         require_once 'HTML/FlexyFramework2/Generator.php';
574         
575         PDO_DataObject::config('database', $this->database);
576         $generator = new HTML_FlexyFramework2_Generator();
577         $generator->start();
578         
579         $this->debug('generateDataobjectsCache', 'dataObjectsCache  writing');
580         HTML_FlexyFramework2_Generator::writeCache($iniCacheTmp, $iniCache, $replace); 
581         // reset the cache to the correct lcoation.
582         PDO_DataObject::config('schema_location',  $iniCache);
583         $this->PDO_DataObject['schema_location'] = $iniCache;
584         //$this->_exposeToPear();
585         
586         //$GLOBALS['_DB_DATAOBJECT']['INI'][$this->database] =   parse_ini_file($iniCache, true);
587         //$GLOBALS['_DB_DATAOBJECT']['SEQUENCE']
588         // clear any dataobject cache..
589           
590         
591         //die("done");
592         
593     }
594     /**
595      * DataObject Configuration:
596      * Always in Project/DataObjects
597      * unless enableArray is available...
598      * 
599      * 
600      * 
601      */
602     function _parseConfigDataObjects()
603     {
604         if ($this->nodatabase && !$this->database) {
605             return;
606         }
607         
608         // better done here.. ?? only include if we have database configuration?
609         require_once 'PDO/DataObject.php';
610         
611         $dburl = parse_url($this->database);
612         $dbini = 'ini_'. basename($dburl['path']);
613                 
614         $dbinis =  array(); //array(dirname(__FILE__) . '/Pman/DataObjects/pman.ini');
615         $dbreq =  array(); //array( dirname(__FILE__) . '/Pman/DataObjects/');
616         $dbcls =  array(); //array('Pman_DataObjects_');
617
618         $project = explode('/',$this->project)[0]; 
619         
620         if (!empty($this->enableArray)) {
621                 
622             $tops = array_merge( array($project), empty($this->projectExtends) ? array() : $this->projectExtends);
623             
624             foreach($tops as $td) {
625                  $bd = $this->rootDir .'/'.$td;
626                 foreach($this->enableArray as $m) {
627                     // look in Pman/MODULE/DataObjects/*
628                      if (file_exists($bd.'/'.$m.'/DataObjects')) {
629                         $dbinis[] = $bd.'/'.$m.'/DataObjects/'. strtolower($td).'.ini';
630                         $dbcls[] = $td.'_'. $m . '_DataObjects_';
631                         $dbreq[] = $bd.'/'.$m.'/DataObjects';
632                         continue;
633                     }
634                     // look in MODULE/DataObjects ?? DO WE SUPPORT THIS ANYMORE???
635                     if (file_exists($bd.'/../'.$m.'/DataObjects')) {
636                         $dbinis[] = $bd.'/../'.$m.'/DataObjects/'. strtolower($td).'.ini';
637                         $dbcls[] = $td. '_DataObjects_';
638                         $dbreq[] = $bd.'/../'.$m.'/DataObjects';
639                     }
640                         
641                         
642                       
643                 }
644             }     
645         } else {
646             
647             if (isset($this->PDO_DataObject['schema_location'])) {
648                 $dbinis[] = $this->PDO_DataObject['schema_location'] .'/'.basename($dburl['path']).'.ini';
649             } else {
650                 $dbinis[] = $this->baseDir.'/DataObjects/'.basename($dburl['path']).'.ini';
651             }
652             // non modular.
653             
654             $dbcls[] = $project .'_DataObjects_';
655             $dbreq[] = $this->baseDir.'/DataObjects';
656         }
657             
658         
659         $this->applyIf('PDO_DataObject', array(   
660         
661             'class_location' =>  implode(PATH_SEPARATOR,$dbreq),
662             'class_prefix' =>  implode(PATH_SEPARATOR,$dbcls),
663             'database'        => $this->database,    
664             ///'require_prefix' => 
665          //   'schema_location' => dirname(__FILE__) . '/Pman/DataObjects/',
666              'schema_location' => implode(PATH_SEPARATOR,$dbinis),
667          
668            //   'debug' => 5,
669         ));
670         PDO_DataObject::config($this->PDO_DataObject);
671         
672     }
673     /**
674      Set up thetemplate
675      * 
676      */
677     function _parseConfigTemplate()
678     {
679         
680         // compile.
681         if (function_exists('posix_getpwuid')) {
682             $uinfo = posix_getpwuid( posix_getuid () ); 
683          
684             $user = $uinfo['name'];
685         } else {
686             $user = getenv('USERNAME'); // windows.
687         }
688         
689         $compileDir = ini_get('session.save_path') .'/' . 
690             $user . '_compiled_templates_' . $this->project;
691         
692         if ($this->appNameShort) {
693             $compileDir .= '_' . $this->appNameShort;
694         }
695         if ($this->version) {
696             $compileDir .= '.' . $this->version;
697         }
698         
699         // templates. -- all this should be cached!!!
700         $src = array();
701          
702         
703         if ($this->appNameShort && !in_array('Core', explode(',', $this->disable ? $this->disable : ''))) {
704             // in app based version, template directory is in Core
705             
706             $src = array(  
707                 $this->baseDir . '/Core/templates'
708             );
709         }
710         
711         if(!empty($this->projectExtends)){
712             foreach ($this->projectExtends as $e){
713                 $add = $this->rootDir . '/' . $e .'/templates';
714                 if (!in_array($add,$src) && file_exists($add)) {
715                     $src[] = $add;
716                 }
717             }
718         }
719         
720         $src[] = $this->baseDir . '/templates';
721         
722         
723         
724         if (!empty($this->enableArray)) {
725              
726             
727             foreach($this->enableArray as $m) {
728                 $add = $this->baseDir . '/' . $m .'/templates';
729                 if (!in_array($add,$src) && file_exists($add) && $this->appNameShort != $m) {
730                     $src[] = $add;
731                 }
732                 
733             }
734             if (!empty($this->projectExtends)  )  {
735                 foreach ($this->projectExtends as $extend){
736                     foreach($this->enableArray as $m) {
737                         $add = $this->rootDir . '/' . $extend . '/' . $m .'/templates';
738                         if (!in_array($add,$src) && file_exists($add) && $this->appNameShort != $m) {
739                             $src[] = $add;
740                         }
741                     }
742                 }
743     
744             }
745         }
746          
747         
748         if ($this->appNameShort) {
749             $src[] =  $this->baseDir . '/'. $this->appNameShort. '/templates';
750         }
751         
752         // images may come from multiple places: - if we have multiple template directories.?
753         // how do we deal with this..?
754         // images/ << should always be mapped to master!
755         // for overridden appdir ones we will have to se rootURL etc.
756         
757         $url_rewrite = 'images/:'. $this->rootURL . '/'. $this->project. '/templates/images/';
758         
759         $this->applyIf('HTML_Template_Flexy', array(
760             'templateDir' => implode(PATH_SEPARATOR, $src),
761             'compileDir' => $compileDir,
762             'multiSource' => true,
763             'forceCompile' => 0,
764             'url_rewrite' => $url_rewrite,
765             'filters' => 'Php,SimpleTags', /// for non-tokenizer version?
766             'debug' => $this->debug ? 1 : 0,
767             'useTokenizer' => 1,
768              
769             
770         
771         
772         ));
773     } 
774     
775     function _parseConfigMail()
776     {
777         $this->applyIf('HTML_Template_Flexy', array(
778            'debug' => 0,
779            'driver' => 'smtp',
780            'host' => 'localhost',
781            'port' => 25,
782         ));
783     }
784     
785     /**
786      * exposes to PEAR::getStaticProperty..
787      * at present, only FlexyFramework uses this..
788      * // we should really stop it..
789      * ctor for Flexy - should read from config...
790      */
791     /*
792     function _exposeToPear()
793     {
794         $cls = array_keys(get_class_vars(__CLASS__));
795         $base = array();
796         
797         // anything that get's set, that's not in our default properties
798         // is assumed to be an option set .
799         foreach(get_object_vars($this) as $k=>$v) {
800             if (in_array($k,$cls)) {
801                 $base[$k] = $v;
802                 continue;
803             }
804             $options = &PEAR::getStaticProperty($k,'options');
805             $options = $v;
806         }
807        // $options = &PEAR::getStaticProperty('HTML_FlexyFramework2','options');
808        // $options = $base;
809          //   apply them..
810     }
811     */
812     
813     
814     function _validateEnv() 
815     {
816         /* have I been initialized */
817         
818         /* 
819         if (get_magic_quotes_gpc() && !$this->cli) {
820             $this->fatalError(
821                 "magic quotes is enabled add the line<BR>
822                    php_value magic_quotes_gpc 0<BR>
823                    to your .htaccess file <BR>
824                    (Apache has to be configured to &quot;AllowOverride Options AuthConfig&quot; for the directory)
825                    ");
826                 
827         }
828         */
829         // set up error handling - 
830         //$this->error = new HTML_FlexyFramework2_Error();
831         
832         /// fudge work around bugs in PEAR::setErrorHandling(,)
833         //$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_CALLBACK;
834         //$GLOBALS['_PEAR_default_error_options'] = array($this->error,'raiseError');
835         
836         
837         
838         if ($this->debug) {
839             require_once 'Benchmark/Timer.php'; 
840             $this->timer = new BenchMark_Timer(true);
841             register_shutdown_function(function() { echo $this->timer->getOutput(); });
842         }
843
844     }
845     
846     function _validateDatabase()
847     {
848         //echo "<PRE>"; print_r($this);
849
850         if ($this->nodatabase) {
851             return;
852         }
853         
854         // database is the only setting - we dont support mult databses?
855           
856             
857         $x = new PDO_Dataobject();
858         PDO_Dataobject::config('database', $this->database);
859         try {
860             $x->PDO();
861         } catch (Exception $e) {
862                                 
863
864                 $this->fatalError("Configuration or Database Error: could not connect to Database, <BR>
865                     Please check the value given to HTML_FlexyFramework2, or run with debug on!<BR>
866                      <BR> ".$e->getMessage());
867         }
868         
869         
870         
871     }
872     function _validateTemplate()
873     {
874         // check that we have a writeable directory for flexy's compiled templates.
875         
876         if (empty($this->HTML_Template_Flexy['compileDir'])) {
877             return;
878         }
879         
880         if ( !file_exists($this->HTML_Template_Flexy['compileDir']))  {
881             mkdir($this->HTML_Template_Flexy['compileDir'], 0700, true);
882             clearstatcache();
883              
884             if ( !file_exists($this->HTML_Template_Flexy['compileDir']))  {
885             
886                 $this->fatalError("Configuration Error: you specified a directory that does not exist for<BR>
887                     HTML_Template_Flexy => compileDir  {$this->HTML_Template_Flexy['compileDir']}<BR>\n"
888                 );
889             }
890         }
891         
892         if (!is_writeable($this->HTML_Template_Flexy['compileDir'])) {
893             $this->fatalError("Configuration Error: Please make sure the template cache directory is writeable<BR>
894                     eg. <BR>
895                     chmod 700 {$this->HTML_Template_Flexy['compileDir']}<BR>
896                     chgrp apache_user  {$this->HTML_Template_Flexy['compileDir']}<BR>\n"
897             );
898         }
899         //echo "<PRE>";print_R($config);
900         
901         
902          
903           
904         
905         
906     }
907   
908   
909    
910         
911     
912     
913     /**
914     * Quality Redirector
915     *
916     * Usage in a page.:
917     * HTML_FlexyFramework::run('someurl/someother',array('somearg'=>'xxx'));
918     * ...do clean up...
919     * exit; <- dont cary on!!!!
920     *
921     * You should really
922     * 
923     * @param   string           redirect to url 
924     * @param   array Args Optional      any data you want to send to the next page..
925     * 
926     *
927     * @return   false
928     * @access   public
929     * @static
930     */
931   
932     
933     static function run($request,$args=array()) 
934     {
935         self::$singleton->_run($request,true,$args);
936         return false;
937     }
938     
939     /**
940      * initPage - load the page up, and set the variables, but do not actually run it.
941      * - used where we want features from this framework, buy may actually be running another one..
942      *
943      * call with ($_SERVER["SCRIPT_NAME"]) for root page...
944      * 
945      */
946     public function initPage($request, $isRedirect=false, $args=array())
947     {
948         $newRequest = $this->_getRequest($request,$isRedirect);
949         
950         // find the class/file to load
951         list($classname,$subRequest) = $this->requestToClassName($newRequest,FALSE);
952         
953         
954         $this->debug("requestToClassName return = CLASSNAME: $classname SUB REQUEST: $subRequest");
955         
956         // assume that this was handled by getclassname ?????
957         if (!$classname) {
958             return false;
959         }
960         
961         // make page data/object accessable at anypoint in time using  this
962         // not sure if this is used anymore - or even works..?
963         
964         $classobj =  new  $classname();  // normally do not have constructors.
965         
966         $classobj->baseURL = $this->baseURL;
967         $classobj->rootURL = $this->rootURL;
968         $classobj->rootDir = $this->rootDir;
969         $classobj->bootLoader  = $this;
970         $classobj->request = $newRequest;
971         $classobj->timer = &$this->timer;
972         $classobj->subrequest = $subRequest;
973          
974         $this->page = $classobj;
975         return $classobj;
976     }
977     
978     
979     /**
980     * The main execution loop
981     *
982     * recursivly self called if redirects (eg. return values from page start methods)
983     * 
984     * @param   string from $_REQUEST or redirect from it'self.
985     * @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.
986     *
987     *
988     * @return   false || other    false indicates no page was served!
989     * @access   public|private
990     * @see      see also methods.....
991     */
992   
993     public function _run($request,$isRedirect = false,$args = array()) 
994     {
995         
996         // clean the request up.
997         $this->calls++;
998         
999         if ($this->calls > 5) {
1000             // to many redirections...
1001             trigger_error("FlexyFramework:: too many redirects - backtrace me!",E_USER_ERROR);
1002             exit;
1003         }
1004         
1005         $classobj  = $this->initPage($request,$isRedirect,$args);
1006         $classobj->cli = $this->cli;
1007         
1008         if ($this->cli && !$isRedirect ) { // redirect always just takes redirect args..
1009             require_once 'HTML/FlexyFramework2/Cli.php';
1010             $fcli = new HTML_FlexyFramework2_Cli($this);
1011             $nargs = $fcli->cliParse(get_class($classobj));
1012             $args = $nargs === false ? $args : $nargs; /// replace if found.
1013             $classobj->cli_args = $nargs;
1014         }
1015         
1016         // echo '<PRE>'; print_r($this);exit;
1017         // echo "CHECK GET AUTH?";
1018         if (!method_exists($classobj, 'getAuth')) {
1019         //    echo "NO GET AUTH?";
1020             $this->fatalError("class ". get_class($classobj) . " does not have a getAuth Method");
1021             return false;
1022         }
1023         
1024         /* check auth on the page */
1025         if (is_string($redirect = $classobj->getAuth())) {
1026             $this->debug("GOT AUTH REDIRECT".$redirect);
1027             return $this->_run($redirect,TRUE);
1028         }
1029         // used HTML_FlexyFramework::run();
1030                  
1031
1032         if ($redirect === false) {
1033             $this->debug("GOT AUTH FALSE");    
1034             return false; /// Access deined!!! - 
1035         }
1036      
1037         // allow the page to implement caching (for the full page..)
1038         // although normally it should implement caching on the outputBody() method.
1039         
1040         if (method_exists($classobj,"getCache")) {
1041             if ($result = $classobj->getCache()) {
1042                 return $result;
1043             }
1044         }
1045         /* allow redirect from start */
1046         if (method_exists($classobj,"start")) {
1047             if (is_string($redirect = $classobj->start($classobj->subrequest,$isRedirect,$args)))  {
1048                 $this->debug("REDIRECT $redirect <BR>");
1049                 return $this->_run($redirect,TRUE);
1050             }
1051             if ($redirect === false) {
1052                 return false;
1053             }
1054         }
1055                 
1056
1057          // used HTML_FlexyFramework::run();
1058         
1059         /* load the modules 
1060          * Modules are common page components like navigation headers etc.
1061          * that can have dynamic code.
1062          * Code has been removed now..
1063          */
1064         
1065         
1066         if ($this->timer) {
1067             $this->timer->setMarker("After $request loadModules Modules"); 
1068         }
1069         
1070         /* output it  - (our base page does not implement output for cli. */
1071         
1072         if ( method_exists($classobj,'output')) {
1073             $classobj->output(); 
1074         }
1075         
1076         
1077         if ($this->timer) {
1078             $this->timer->setMarker("After $request output"); 
1079             $this->timer->stop(); //?? really - yes...
1080            
1081             
1082         }
1083         
1084         if ($this->cli) {
1085             return true;
1086         }
1087         
1088         
1089         exit; /// die here...
1090         
1091     }
1092     
1093     /**
1094     * map the request into an object and run the page.
1095     *
1096     * The core of the work is done here.
1097     * 
1098     * 
1099     * @param   request  the request string
1100     * @param   boolean isRedirect - indicates that it should not attempt to strip the .../index.php from the request.
1101     * 
1102     * @access  private
1103     */
1104   
1105     function _getRequest($request, $isRedirect) 
1106     {
1107         
1108         
1109         
1110         if ($this->cli) {
1111             return $request;
1112         }
1113         
1114         $startRequest = $request;
1115         $rq = explode('?', $request);
1116         $request =  array_shift($rq);
1117         $this->debug("INPUT REQUEST $request<BR>");
1118         if (!$isRedirect) {
1119             // check that request forms contains baseurl????
1120              
1121             $subreq = substr($request,0,strlen($this->baseURL));
1122             if ($subreq != substr($this->baseURL,0,strlen($subreq))) {
1123                 $this->fatalError(
1124                     "Configuration error: Got base of $subreq which does not 
1125                         match configuration of: $this->baseURL} ");
1126             }
1127             $request = substr($request,strlen($this->baseURL));
1128              
1129         }
1130         // strip front
1131         // echo "REQUEST WAS: $request<BR>";
1132         // $request = preg_replace('/^'.preg_quote($base_url,'/').'/','',trim($request));
1133         // echo "IS NOW: $request<BR>";
1134         // strip end
1135         // strip valid html stuff
1136         //$request = preg_replace('/\/[.]+/','',$request);
1137         
1138
1139         $request = preg_replace('/^[\/]*/','',$request);
1140         $request = preg_replace('/\?.*$/','',$request);
1141         $request = preg_replace('/[\/]*$/','',$request);
1142         $this->baseRequest = $request;
1143         $request = str_replace('&','',$request); // any other invalid characters???
1144         $request = preg_replace('/\.([a-z]+)$/','',$request);
1145         $this->ext = substr($this->baseRequest , strlen($request));
1146         
1147         // REDIRECT ROO to index.php! for example..
1148         
1149         // this has a slight issue when we are using 'hidden index.php' mod_rewrite..
1150         
1151         /*
1152          normal (non-mod_rewrite)
1153          $_SERVER['REQUEST_URI'] start with baseURL..
1154          */
1155         if (isset($_SERVER['REQUEST_URI']) && substr($_SERVER['REQUEST_URI'], 0, strlen($this->baseURL)) != $this->baseURL) {
1156             // then we are rewriting?
1157             $this->baseURL = $this->rootURL;
1158             return $request;
1159         }
1160         
1161         //var_dump(array($this->baseURL, $this->rootURL, $request ,$isRedirect, $_SERVER['REQUEST_URI'])); phpinfo();exit;
1162         if (!$request && !$isRedirect) {
1163             
1164             
1165             if ($this->baseURL && (strlen($startRequest) < strlen($this->baseURL))) {
1166                 
1167                 // needs to handle https + port
1168                 $http = ((!empty($_SERVER["HTTPS"]) && $_SERVER["HTTPS"]  == 'on')) ? 'https' : 'http';
1169                 $sp = '';
1170                 if (!empty($_SERVER['SERVER_PORT'])) {
1171                     if ((($http == 'http') && ($_SERVER['SERVER_PORT'] == 80)) || (($http == 'https') && ($_SERVER['SERVER_PORT'] == 443))) {
1172                         // standard ports..
1173                     } else {
1174                         $sp .= ':'.((int) $_SERVER['SERVER_PORT']);
1175                     }
1176                 }
1177                 //var_dump($startRequest);      echo '<PRE>'; print_R($this);         phpinfo();exit;
1178                 $host = !empty($_SERVER["HTTP_X_FORWARDED_HOST"]) ? $_SERVER["HTTP_X_FORWARDED_HOST"] : $_SERVER["HTTP_HOST"];
1179                 header('Location: '.$http.'://'.$host .$sp . $this->baseURL);
1180  
1181                 exit;
1182             }
1183             $request = "";
1184         }
1185         $this->debug("OUTPUT REQUEST $request<BR>");
1186         return $request;
1187     }
1188     
1189    
1190     
1191     
1192     /**
1193     * get the Class name and filename to load
1194     *
1195     * Parses the request and converts that into a File + Classname
1196     * if the class doesnt exist it will attempt to find a file below it, and
1197     * call that one with the data.
1198     * Used by the module loader to determine the location of the modules
1199     *   
1200     * @param   request  the request string
1201     * @param   boolean showError - if false, allows you to continue if the class doesnt exist.
1202     * 
1203     *
1204     * @return   array classname, filepath
1205     * @access   private
1206     * @static
1207     */
1208   
1209     function requestToClassName($request,$showError=TRUE) 
1210     {
1211        // if ($request == "error") {
1212        //     return array("HTML_FlexyFramework_Error","");
1213        // }
1214         
1215         // special classes ::
1216         if ($this->cli && in_array($request, array('DataObjects'))) {
1217             require_once 'HTML/FlexyFramework2/'. $request . '.php';
1218             return array('HTML_FlexyFramework2_'. $request,'');
1219         }
1220         
1221         
1222         $request_array=explode("/",$request);
1223         $original_request_array = $request_array;
1224         $sub_request_array = array();
1225         $l = count($request_array)-1;
1226         if ($l > 10) { // ?? configurable?
1227             //PEAR::raiseError("Request To Long");
1228             $this->fatalError("Request To Long - " . $request);
1229         }
1230
1231         
1232         $classname='';
1233         // tidy up request array
1234         
1235         if ($request_array) {
1236             foreach(array_keys($request_array) as $i) {
1237                 $request_array[$i] = preg_replace('/[^a-z0-9]/i','_',urldecode($request_array[$i]));
1238             }
1239         }
1240         //echo "<PRE>"; print_r($request_array);
1241         // technically each module should do a check here... similar to this..
1242         
1243         
1244         for ($i=$l;$i >-1;$i--) {
1245             $location = implode('/',$request_array) . ".php";
1246             if ($location == '.php') {
1247                 $this->debug("SKIP first path check, as request str is empty");
1248                 break;
1249             }
1250             
1251             $this->debug("baseDIR = {$this->baseDir}");
1252             
1253             $floc = "{$this->baseDir}/$location";
1254             $this->debug("CHECK LOCATION = $location");
1255             
1256             
1257             
1258             if (!empty($location) && $location != '.php' && @file_exists($floc )) {             // hide? error???
1259                 require_once $floc ;
1260                 $classname = $this->classPrefix . implode('_',$request_array);
1261                 $this->debug("FOUND FILE - SET CLASS = $classname <BR>");
1262                 break;
1263             } 
1264             
1265             // in here check the 'projectExtends' versions..?
1266             
1267             if(!empty($this->projectExtends)){
1268                 $this->debug("Trying project Extends<BR>");
1269                 $has_extend_class = false;
1270                 
1271                 foreach ($this->projectExtends as $e){
1272                     $floc = "{$this->rootDir}/{$e}/$location";
1273                     $this->debug("Trying file: $floc");
1274                     if (!empty($location) && @file_exists($floc)) {             // hide? error???
1275                         require_once $floc ;
1276                         $classname = $e . '_' . implode('_',$request_array);
1277                         $has_extend_class = true;
1278                         $this->debug("FOUND FILE - SET CLASS = $classname <BR>");
1279                         break;
1280                     } 
1281                 }
1282                 
1283                 if(!empty($has_extend_class)){
1284                     break;
1285                 }
1286                 
1287             }
1288             
1289             
1290             $this->debug("$floc  - !!FOUND NOT FILE!!");
1291             
1292             $sub_request_array[] = $original_request_array[$i];
1293             unset($request_array[$i]);
1294             unset($original_request_array[$i]);
1295         }
1296          
1297         // is this really needed here!
1298         
1299         $classname = preg_replace('/[^a-z0-9]/i','_',$classname);
1300         $this->debug("CLASSNAME is '$classname'");
1301         // got it ok.
1302         if ($classname && class_exists($classname)) {
1303             $this->debug("using $classname");
1304             //print_r($sub_request_array);
1305             return array($classname,implode('/',array_reverse($sub_request_array)));
1306         }
1307         // stop looping..
1308         if ($showError) {
1309             $this->fatalError("INVALID REQUEST: \n $request FILE:".$this->baseDir. "/{$location}  CLASS:{$classname}");
1310             
1311         } 
1312         
1313         
1314         $this->debug("Try base {$this->baseDir}.php");   
1315         // try {project name}.php
1316         // this used to be silenced @ - if this fails we are usually pretty fried..
1317         
1318         if (file_exists($this->baseDir.'.php')) {
1319             
1320             
1321             $classname = str_replace('/', '_', $this->project); //   basename($this->baseDir);
1322             
1323             $this->debug("FOUND {$this->baseDir} requring and checking class $classname");   
1324             require_once $this->baseDir.'.php';
1325             $this->debug("require success");
1326             
1327             if (!class_exists($classname)) {
1328                 $this->fatalError( "{$this->baseDir}.php did not contain class $classname");
1329             }
1330         }
1331         // got projectname.php
1332         if ($classname && class_exists($classname)) {
1333             $this->debug("using $classname");
1334             //print_r($sub_request_array);
1335              
1336             return array($classname,implode('/',array_reverse($sub_request_array)));
1337         }    
1338             
1339         
1340         $this->fatalError( "can not find {$this->baseDir}.php"); // dies..
1341               
1342      
1343     }
1344     
1345     /**
1346     * ensure Single CLi process 
1347     * usage:
1348     * HTML_FlexyFramework2::ensureSingle(__FILE__, $this);
1349     * @param string filename of running class
1350     * @param object class
1351     */
1352       
1353     static function ensureSingle($sig, $class) 
1354     {
1355         //echo "check single: $sig / ". get_class($class) ."\n";
1356         $ff = HTML_FlexyFramework2::get();
1357         if (function_exists('posix_getpwuid')) {
1358             $uinfo = posix_getpwuid( posix_getuid () ); 
1359             $user = $uinfo['name'];
1360         } else {
1361             $user = getenv('USERNAME'); // windows.
1362         }
1363         $fdir = ini_get('session.save_path') .'/' . 
1364                 $user . '_cli_' . $ff->project ;
1365      
1366         
1367         if (!file_exists($fdir)) {
1368             mkdir($fdir, 0777);
1369         }
1370         
1371         $lock = $fdir.'/'. md5($sig) . '.' . get_class($class);
1372         //echo "check single: lock : $lock\n";
1373         if (!file_exists($lock)) {
1374             file_put_contents($lock, getmypid());
1375             //echo "check single: lock : DOES NOT EXIST\n";
1376             return true;
1377         }
1378         $oldpid = file_get_contents($lock);
1379         if (!file_exists('/proc/' . $oldpid)) {
1380             
1381             file_put_contents($lock, getmypid());
1382           //  echo "check single: lock : PROC NOT EXIST\n";
1383             return true;
1384         }
1385         // file exists, but process might not be the same..
1386         $ca = explode('_', get_class($class));
1387         $name = array_pop($ca);
1388         $cmd = file_get_contents('/proc/' . $oldpid.'/cmdline');
1389         if (!preg_match('/php/i',$cmd) || !preg_match('/'.$name.'/i',$cmd)) {
1390             file_put_contents($lock, getmypid());
1391             //echo "check single: lock : CMDLINE !have PHP \n";
1392             return true;
1393         }
1394         die("process " . $sig . " already running\n");
1395         
1396     }
1397     /**
1398      * removes the lock for the applicaiton - use with care...
1399      *
1400      *
1401      */
1402     static function ensureSingleClear($sig, $class)
1403     {
1404         $ff = HTML_FlexyFramework2::get();
1405         if (function_exists('posix_getpwuid')) {
1406             $uinfo = posix_getpwuid( posix_getuid () ); 
1407             $user = $uinfo['name'];
1408         } else {
1409             $user = getenv('USERNAME'); // windows.
1410         }
1411         $fdir = ini_get('session.save_path') .'/' . 
1412                 $user . '_cli_' . $ff->project ;
1413      
1414         
1415         if (!file_exists($fdir)) {
1416             mkdir($fdir, 0777);
1417         }
1418         $lock = $fdir.'/'. md5($sig);
1419         if (!file_exists($lock)) {
1420             
1421             return true;
1422         }
1423         unlink($lock);;
1424     }
1425     
1426     
1427     /**
1428     * Debugging 
1429     * 
1430     * @param   string  text to output.
1431     * @access   public
1432     */
1433   
1434     function debug($output) {
1435        
1436         if (empty($this->debug)) {  
1437             return;
1438         }
1439         echo $this->cli ? 
1440               "HTML_FlexyFramework2::debug  - ".$output."\n" 
1441             : "<B>HTML_FlexyFramework2::debug</B> - ".$output."<BR>\n";
1442     
1443     }
1444     /**
1445     * Raises a fatal error. - normally only used when setting up to help get the config right.
1446     * 
1447     * can redirect to fatal Action page.. - hoepfully not issued before basic vars are set up..
1448     * 
1449     * @param   string  text to output.
1450     * @access   public
1451     */
1452     
1453     function fatalError($msg,$showConfig = 0) 
1454     {
1455         
1456         
1457          if ($this->fatalAction) {
1458             HTML_FlexyFramework2::run($this->fatalAction,$msg);
1459             exit;
1460         }
1461         header('HTTP/1.1 503 Service Temporarily Unavailable');
1462         header('Status: 503 Service Temporarily Unavailable');
1463         header('Retry-After: 300');
1464         echo $this->cli ? $msg ."\n" : "<H1>$msg</H1>configuration information<PRE>";
1465         if ($showConfig) {
1466             
1467             print_r($this);
1468         }
1469         $ff = HTML_FlexyFramework2::get();
1470         $ff->debug($msg);
1471         exit;
1472     }    
1473 }
1474