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