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