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