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