PHP8 fix
[Pman.Base] / Pman.php
1 <?php 
2 /**
3  * Pman Base class
4  * 
5  * Provides:
6  *  - base application setup (variables etc to javascript)
7  * 
8  *  - authentication and permission info about user / application
9  *  - json output methods.
10  *  - file upload error checking - checkFileUploadError
11  *  - logging to event table
12  *  - sendTemplate code (normally use the Person version for sending to specific people..)
13  * 
14  *  - doc managment code?? - remarks and tracking??? - MOVEME
15  *  - authentication link checking?? MOVEME?
16  *  - authentication reset password ?? MOVEME?
17  *  ?? arrayClean.. what's it doing here?!? ;)
18  *
19  *
20  *  OPTIONS
21  *  Pman['local_autoauth']   // who to login as when using localhost
22  *  Pman['isDev']  // can the site show develpment info.?
23  *  Pman['uiConfig']  // extra variable to export to front end..
24  *  Pman['auth_comptype'] // -- if set to 'OWNER' then only users with company=OWNER can log in
25  *  Pman['authTable'] // the authentication table (default 'person')
26  *
27  * 
28  * Usefull implemetors
29  * DB_DataObject*:*toEventString (for logging - this is generically prefixed to all database operations.)
30  *   - any data object where this method exists, the result will get prefixed to the log remarks
31  */
32
33  
34      
35  
36 require_once 'Pman/Core/AssetTrait.php';
37
38 class Pman extends HTML_FlexyFramework_Page 
39 {
40     use Pman_Core_AssetTrait;
41     //outputJavascriptDir()
42     //outputCssDir();
43     var $isDev = false;
44     var $appName= "";
45     var $appLogo= "";
46     var $appShortName= "";
47     var $appVersion = "1.8";
48     var $version = 'dev';
49     var $onloadTrack = 0;
50     var $linkFail = "";
51     var $showNewPass = 0;
52     var $logoPrefix = '';
53     var $appModules = '';
54     var $appDisabled = array(); // array of disabled modules..
55                     // (based on config option disable)
56      
57     var $authUser; // always contains the authenticated user..
58     
59     var $disable_jstemplate = false; /// disable inclusion of jstemplate code..
60     var $company = false;
61     
62     var $css_path = ''; // can inject a specific path into the base HTML page.
63     
64     
65     var $transObj = false; // used to rollback or commit in JOK/JERR
66     
67     // these are used somewhere - 
68     var $builderJs = false;//
69     var $serverName = false;
70     var $lang = false;
71     var $allowSignup = false;
72     var $_hasInit;
73     var $appNameShort;
74     var $appDisable;
75     var $uiConfig;
76      
77     /**
78      * ------------- Standard getAuth/get/post methods of framework.
79      * 
80      * 
81      */
82     
83     function getAuth() // everyone allowed in!!!!!
84     {
85         $this->loadOwnerCompany();
86         
87         return true;
88     }
89     
90     function init($base = false) 
91     {
92         
93         if (isset($this->_hasInit)) {
94             return;
95         }
96         $this->_hasInit = true;
97          // move away from doing this ... you can access bootLoader.XXXXXX in the master template..
98         $boot = HTML_FlexyFramework::get();
99         // echo'<PRE>';print_R($boot);exit;
100         $this->appName      = $boot->appName;
101         
102         $this->appNameShort = $boot->appNameShort;
103         
104         $this->appModules   = $boot->enable;
105         
106 //        echo $this->arrayToJsInclude($files);        
107         $this->isDev = empty($boot->Pman['isDev']) ? false : $boot->Pman['isDev'];
108         
109         $this->css_path = empty($boot->Pman['css_path']) ? '' : $boot->Pman['css_path'];
110         
111         $this->appDisable = $boot->disable;
112         $this->appDisabled = explode(',', $boot->disable);
113         $this->version = $boot->version; 
114         $this->uiConfig = empty($boot->Pman['uiConfig']) ? false : $boot->Pman['uiConfig']; 
115         
116         if (!empty($boot->Pman['local_autoauth']) &&
117             !empty($_SERVER['SERVER_ADDR']) &&
118             !empty($_SERVER['REMOTE_ADDR']) &&            
119             ($_SERVER['SERVER_ADDR'] == '127.0.0.1') &&
120             ($_SERVER['REMOTE_ADDR'] == '127.0.0.1') 
121         ) {
122             $this->isDev = true;
123         }
124         
125         if (
126             !empty($_REQUEST['isDev'])
127             &&
128             (
129                 (
130                     !empty($_SERVER['SERVER_ADDR']) &&
131                     (
132                         (($_SERVER['SERVER_ADDR'] == '127.0.0.1') && ($_SERVER['REMOTE_ADDR'] == '127.0.0.1'))
133                         ||
134                         (($_SERVER['SERVER_ADDR'] == '::1') && ($_SERVER['REMOTE_ADDR'] == '::1'))
135                         ||
136                         (preg_match('/^192\.168/', $_SERVER['SERVER_ADDR']) && $_SERVER['SERVER_ADDR'] == $_SERVER['HTTP_HOST'])
137                     )
138                 )
139                 ||
140                 !empty($boot->Pman['enable_isdev_url'])
141             )
142             
143         ) {
144             $boot->Pman['isDev'] = true;
145             $this->isDev = true;
146         }
147         
148         // if a file Pman_{module}_Pman exists.. and it has an init function... - call that..
149         
150         //var_dump($this->appModules);
151         
152         
153         
154     }
155     /*
156      * call a method on {module}/Pman.php
157      * * initially used on the main page load to call init();
158      * * also used for ccsIncludes?? 
159      *
160      * // usage: $this->callModules('init', $base)
161      * 
162      */
163      
164     function callModules($fn) 
165     {
166         $args = func_get_args();
167         array_shift($args);
168         foreach(explode(',',$this->appModules) as $m) {
169             $cls = 'Pman_'. $m . '_Pman';
170             if (!file_exists($this->rootDir . '/'.str_replace('_','/', $cls). '.php')) {
171                 continue;
172             }
173             require_once str_replace('_','/', $cls). '.php';
174             $c = new $cls();
175             if (method_exists($c, $fn)) {
176                 call_user_func_array(array($c,$fn),$args);
177             }
178         }
179          
180      }
181     
182     function get($base, $opts=array()) 
183     {
184         $this->init();
185         if (empty($base)) {
186             $this->callModules('init', $this, $base);
187         }
188         
189             //$this->allowSignup= empty($opts['allowSignup']) ? 0 : 1;
190         $bits = explode('/', $base);
191       
192         
193         // should really be moved to Login...
194         /*
195         if ($bits[0] == 'PasswordReset') {
196             $this->linkFail = $this->resetPassword(@$bits[1],@$bits[2],@$bits[3]);
197             header('Content-type: text/html; charset=utf-8');
198             return;
199         }
200         */
201          
202         $au = $this->getAuthUser();
203         if ($au) {
204             $ff= HTML_FlexyFramework::get();
205            
206             if (!empty($ff->Pman['auth_comptype']) && $au->id > 0 &&
207                 ( !$au->company_id || ($ff->Pman['auth_comptype'] != $au->company()->comptype))) {
208          
209                 $au->logout();
210                 
211                 $this->jerr("Login not permited to outside companies - please reload");
212             }
213             $this->addEvent("RELOAD");
214         }
215         
216         
217         if (strlen($base) && $bits[0] != 'PasswordReset') {
218             $this->jerror("BADURL","invalid url: $base");
219         }
220         // deliver template
221         if (isset($_GET['onloadTrack'])) {
222             $this->onloadTrack = (int)$_GET['onloadTrack'];
223         }
224         // getting this to work with xhtml is a nightmare
225         // = nbsp / <img> issues screw everyting up.
226          //var_dump($this->isDev);
227         // force regeneration on load for development enviroments..
228         
229         HTML_FlexyFramework::get()->generateDataobjectsCache($this->isDev && !empty($_REQUEST['isDev']));
230         
231         //header('Content-type: application/xhtml+xml; charset=utf-8');
232         
233         
234         
235         if ($this->company && $this->company->logo_id) {
236             $im = DB_DataObject::Factory('Images');
237             $im->get($this->company->logo_id);
238             $this->appLogo = $this->baseURL . '/Images/Thumb/x100/'. $this->company->logo_id .'/' . $im->filename;
239         }
240         
241         header('Content-type: text/html; charset=utf-8');
242          
243     }
244     function post($base) {
245         return $this->get($base);
246     }
247     
248     
249     // --------------- AUTHENTICATION or  system information
250     /**
251      * loadOwnerCompany:
252      * finds the compay with comptype=='OWNER'
253      * ?? what about comptype_id-name ?????
254      *
255      * @return {Pman_Core_DataObjects_Companies} the owner company
256      */
257     function loadOwnerCompany()
258     {
259         // only applies if authtable is person..
260         $ff = HTML_FlexyFramework::get();
261         if (!empty($ff->Pman['authTable']) && !in_array($ff->Pman['authTable'] , [ 'core_person', 'Person' ])) {
262             return false;
263         }
264         
265         $this->company = DB_DataObject::Factory('core_company');
266         if (!is_a($this->company, 'DB_DataObject')) { // non-core pman projects
267             return false; 
268         }
269         $e = DB_DataObject::Factory('core_enum')->lookupObject('COMPTYPE', 'OWNER');
270
271         $this->company->get('comptype_id', $e->id);
272         return $this->company;
273     }
274     
275     
276     static function staticGetAuthUser($t) {
277         if (!empty($t->authUser)) {
278             return $t->authUser;
279         }
280         $ff = HTML_FlexyFramework::get();
281         $tbl = empty($ff->Pman['authTable']) ? 'core_person' : $ff->Pman['authTable'];
282         
283         $u = DB_DataObject::factory( $tbl );
284         
285         if (is_a($u,'PEAR_Error') || !$u->isAuth()) {
286             return false;
287         }
288         $t->authUser =$u->getAuthUser();
289         return $t->authUser ;
290         
291     }
292     
293     /**
294      * getAuthUser: - get the authenticated user..
295      *
296      * @return {DB_DataObject} of type Pman[authTable] if authenticated.
297      */
298     
299     function getAuthUser()
300     {
301         return self::staticGetAuthUser($this);
302     }
303     /**
304      * hasPerm:
305      * wrapper arround authuser->hasPerm
306      * @see Pman_Core_DataObject_Core_person::hasPerm
307      *
308      * @param {String} $name  The permission name (eg. Projects.List)
309      * @param {String} $lvl   eg. (C)reate (E)dit (D)elete ... etc.
310      * 
311      */
312     function hasPerm($name, $lvl)  // do we have a permission
313     {
314         static $pcache = array();
315         $au = $this->getAuthUser();
316         return $au && $au->hasPerm($name,$lvl);
317         
318     }   
319     /**
320      * modulesList:  List the modules in the application
321      *
322      * @return {Array} list of modules
323      */
324     function modulesList()
325     {
326         $boot = HTML_FlexyFramework::get();
327         // echo'<PRE>';print_R($boot);exit;
328          
329          
330         $mods = explode(',', $boot->enable);
331         if (in_array('Core',$mods)) { // core has to be the first  modules loaded as it contains Pman.js
332             array_unshift($mods,   'Core');
333         }
334         
335         if (in_array($boot->appNameShort,$mods)) { // Project has to be the last  modules loaded as it contains Pman.js
336             unset($mods[array_search($boot->appNameShort, $mods)]);
337             $mods[] = $boot->appNameShort;
338         }
339         
340         $mods = array_unique($mods);
341          
342         $disabled =  explode(',', $boot->disable ? $boot->disable : '');
343         $ret = array();
344         foreach($mods as $mod) {
345             // add the css file..
346             if (in_array($mod, $disabled)) {
347                 continue;
348             }
349             $ret[] = $mod;
350         }
351         return $ret;
352     }
353     
354      
355     
356     
357     function hasModule($name) 
358     {
359         $this->init();
360         if (!strpos( $name,'.') ) {
361             // use enable / disable..
362             return in_array($name, $this->modules()); 
363         }
364         
365         $x = DB_DataObject::factory('core_group_right');
366         $ar = $x->defaultPermData();
367         if (empty($ar[$name]) || empty($ar[$name][0])) {
368             return false;
369         }
370         return true;
371     }
372     
373      
374     
375     function jsencode($v, $header = false)
376     {
377         if ($header) {
378             header("Content-type: text/javascript");
379         }
380         if (function_exists("json_encode")) {
381             $ret=  json_encode($v);
382             if ($ret !== false) {
383                 return $ret;
384             }
385         }
386         require_once 'Services/JSON.php';
387         $js = new Services_JSON();
388         return $js->encodeUnsafe($v);
389         
390         
391         
392     }
393
394      
395         
396     /**
397      * ---------------- Global Tools ---------------   
398      */
399     function checkFileUploadError()  // check for file upload errors.
400     {    
401         if (
402             empty($_FILES['File']) 
403             || empty($_FILES['File']['name']) 
404             || empty($_FILES['File']['tmp_name']) 
405             || empty($_FILES['File']['type']) 
406             || !empty($_FILES['File']['error']) 
407             || empty($_FILES['File']['size']) 
408         ) {
409             $this->jerr("File upload error: <PRE>" . print_r($_FILES,true) . print_r($_POST,true) . "</PRE>");
410         }
411     }
412     
413     static $deleteOnExit = false;
414     /**
415      * generate a tempory file with an extension (dont forget to delete it)
416      */
417     
418     function deleteOnExitAdd($name)
419     {
420         if (self::$deleteOnExit === false) {
421             register_shutdown_function(array('Pman','deleteOnExit'));
422             self::$deleteOnExit  = array();
423         }
424         self::$deleteOnExit[] = $name;
425     }
426     
427     function tempName($ext, $deleteOnExit=false)
428     {
429         
430         $x = tempnam(ini_get('session.save_path'), HTML_FlexyFramework::get()->appNameShort.'TMP');
431         unlink($x);
432         $ret = $x .'.'. $ext;
433         if ($deleteOnExit) {
434             $this->deleteOnExitAdd($ret);
435         }
436         return $ret;
437     
438     }
439    
440      static function deleteOnExit()
441     {
442         
443         foreach(self::$deleteOnExit as $fn) {
444             if (file_exists($fn)) {
445                 unlink($fn);
446             }
447         }
448     }
449     
450     /**
451      * ------------- Authentication password reset ------ ??? MOVEME?
452      * 
453      * 
454      */
455     
456     
457     
458     /**
459      * jerrAuth: standard auth failure - with data that let's the UI know..
460      */
461     function jerrAuth()
462     {
463         $au = $this->authUser;
464         if ($au) {
465             // is it an authfailure?
466             $this->jerr("Permission denied to view this resource", array('authFailure' => true));
467         }
468         $this->jerr("Not authenticated", array('authFailure' => true));
469     }
470      
471      
472      
473     /**
474      * ---------------- Standard JSON outputers. - used everywhere
475      */
476       /**
477      * ---------------- Standard JSON outputers. - used everywhere
478      * JSON error - simple error with logging.
479      * @see Pman::jerror
480      */
481     
482     function jerr($str, $errors=array(), $content_type = false) // standard error reporting..
483     {
484         return $this->jerror('ERROR', $str,$errors,$content_type);
485     }
486     /**
487      * Recomended JSON error indicator
488      *
489      * 
490      * @param string $type  - normally 'ERROR' - you can use this to track error types.
491      * @param string $message - error message displayed to user.
492      * @param array $errors - optioanl data to pass to front end.
493      * @param string $content_type - use text/plain to return plan text - ?? not sure why...
494      *
495      */
496     
497     function jerror($type, $str, $errors=array(), $content_type = false) // standard error reporting..
498     {
499         if ($this->transObj) {
500             $this->transObj->query('ROLLBACK');
501         }
502         
503         $cli = HTML_FlexyFramework::get()->cli;
504         if ($cli) {
505             echo "ERROR: " .$str . "\n"; // print the error first, as DB might fail..
506         }
507         
508         if ($type !== false) {
509             
510             if(!empty($errors)){
511                 DB_DataObject::factory('Events')->writeEventLogExtra($errors);
512             }
513             
514             $this->addEvent($type, false, $str);
515             
516         }
517          
518         $cli = HTML_FlexyFramework::get()->cli;
519         if ($cli) {
520             exit(1); // cli --- exit code to stop shell execution if necessary.
521         }
522         
523         
524         if ($content_type == 'text/plain') {
525             header('Content-Disposition: attachment; filename="error.txt"');
526             header('Content-type: '. $content_type);
527             echo "ERROR: " .$str . "\n";
528             exit;
529         } 
530         
531      // log all errors!!!
532         
533         $retHTML = isset($_SERVER['CONTENT_TYPE']) && 
534                 preg_match('#multipart/form-data#i', $_SERVER['CONTENT_TYPE']);
535         
536         if ($retHTML){
537             if (isset($_REQUEST['returnHTML']) && $_REQUEST['returnHTML'] == 'NO') {
538                 $retHTML = false;
539             }
540         } else {
541             $retHTML = isset($_REQUEST['returnHTML']) && $_REQUEST['returnHTML'] !='NO';
542         }
543         
544         
545         if ($retHTML) {
546             header('Content-type: text/html');
547             echo "<HTML><HEAD></HEAD><BODY>";
548             echo  $this->jsencode(array(
549                     'success'=> false, 
550                     'errorMsg' => $str,
551                     'message' => $str, // compate with exeption / loadexception.
552
553                     'errors' => $errors ? $errors : true, // used by forms to flag errors.
554                     'authFailure' => !empty($errors['authFailure']),
555                 ), false);
556             echo "</BODY></HTML>";
557             exit;
558         }
559         
560         if (isset($_REQUEST['_debug'])) {
561             echo '<PRE>'.htmlspecialchars(print_r(array(
562                 'success'=> false, 
563                 'data'=> array(), 
564                 'errorMsg' => $str,
565                 'message' => $str, // compate with exeption / loadexception.
566                 'errors' => $errors ? $errors : true, // used by forms to flag errors.
567                 'authFailure' => !empty($errors['authFailure']),
568             ),true));
569             exit;
570                 
571         }
572         
573         echo $this->jsencode(array(
574             'success'=> false, 
575             'data'=> array(),
576             'code' => $type,
577             'errorMsg' => $str,
578             'message' => $str, // compate with exeption / loadexception.
579             'errors' => $errors ? $errors : true, // used by forms to flag errors.
580             'authFailure' => !empty($errors['authFailure']),
581         ),true);
582         
583         
584         exit;
585         
586     }
587     function jok($str)
588     {
589         if ($this->transObj ) {
590             $this->transObj->query( connection_aborted() ? 'ROLLBACK' :  'COMMIT');
591         }
592         
593         $cli = HTML_FlexyFramework::get()->cli;
594         if ($cli) {
595             echo "OK: " .$str . "\n";
596             exit;
597         }
598         
599         $retHTML = isset($_SERVER['CONTENT_TYPE']) && 
600                 preg_match('#multipart/form-data#i', $_SERVER['CONTENT_TYPE']);
601         
602         if ($retHTML){
603             if (isset($_REQUEST['returnHTML']) && $_REQUEST['returnHTML'] == 'NO') {
604                 $retHTML = false;
605             }
606         } else {
607             $retHTML = isset($_REQUEST['returnHTML']) && $_REQUEST['returnHTML'] !='NO';
608         }
609         
610         if ($retHTML) {
611             header('Content-type: text/html');
612             echo "<HTML><HEAD></HEAD><BODY>";
613             // encode html characters so they can be read..
614             echo  str_replace(array('<','>'), array('\u003c','\u003e'),
615                         $this->jsencode(array('success'=> true, 'data' => $str), false));
616             echo "</BODY></HTML>";
617             exit;
618         }
619         
620         
621         echo  $this->jsencode(array('success'=> true, 'data' => $str),true);
622         
623         exit;
624         
625     }
626     /**
627      * output data for grids or tree
628      * @ar {Array} ar Array of data
629      * @total {Number|false} total number of records (or false to return count(ar)
630      * @extra {Array} extra key value list of data to pass as extra data.
631      * 
632      */
633     function jdata($ar,$total=false, $extra=array(), $cachekey = false)
634     {
635         // should do mobile checking???
636         if ($total == false) {
637             $total = count($ar);
638         }
639         $extra=  $extra ? $extra : array();
640         
641         
642         $retHTML = isset($_SERVER['CONTENT_TYPE']) && 
643                 preg_match('#multipart/form-data#i', $_SERVER['CONTENT_TYPE']);
644         
645         if ($retHTML){
646             if (isset($_REQUEST['returnHTML']) && $_REQUEST['returnHTML'] == 'NO') {
647                 $retHTML = false;
648             }
649         } else {
650             $retHTML = isset($_REQUEST['returnHTML']) && $_REQUEST['returnHTML'] !='NO';
651         }
652         
653         if ($retHTML) {
654             
655             header('Content-type: text/html');
656             echo "<HTML><HEAD></HEAD><BODY><![CDATA[";
657             // encode html characters so they can be read..
658             echo  str_replace(array('<','>'), array('\u003c','\u003e'),
659                         $this->jsencode(array('success' =>  true, 'total'=> $total, 'data' => $ar) + $extra, false));
660             echo "]]></BODY></HTML>";
661             exit;
662         }
663         
664         
665         // see if trimming will help...
666         if (!empty($_REQUEST['_pman_short'])) {
667             $nar = array();
668             
669             foreach($ar as $as) {
670                 $add = array();
671                 foreach($as as $k=>$v) {
672                     if (is_string($v) && !strlen(trim($v))) {
673                         continue;
674                     }
675                     $add[$k] = $v;
676                 }
677                 $nar[] = $add;
678             }
679             $ar = $nar;
680               
681         }
682         
683       
684         $ret =  $this->jsencode(array('success' =>  true, 'total'=> $total, 'data' => $ar) + $extra,true);  
685         
686         if (!empty($cachekey)) {
687             
688             $fn = ini_get('session.save_path') . '/json-cache'.date('/Y/m/d').'.'. $cachekey . '.cache.json';
689             if (!file_exists(dirname($fn))) {
690                 mkdir(dirname($fn), 0777,true);
691             }
692             file_put_contents($fn, $ret);
693         }
694         
695         echo $ret;
696         exit;
697     }
698     
699     
700     
701     /** a daily cache **/
702     function jdataCache($cachekey)
703     {
704         $fn = ini_get('session.save_path') . '/json-cache'.date('/Y/m/d').'.'. $cachekey . '.cache.json';
705         if (file_exists($fn)) {
706             header('Content-type: application/json');
707             echo file_get_contents($fn);
708             exit;
709         }
710         return false;
711         
712     }
713     
714    
715     
716     /**
717      * ---------------- OUTPUT
718      */
719     function hasBg($fn) // used on front page to check if logos exist..
720     {
721         return file_exists($this->rootDir.'/Pman/'.$this->appNameShort.'/templates/images/'.  $fn);
722     }
723      /**
724      * outputJavascriptIncludes:
725      *
726      * output <script....> for all the modules in the applcaiton
727      *
728      */
729     function outputJavascriptIncludes()  
730     {
731         // BC support - currently 1 project still relies on this.. (MO portal)
732         $ff = HTML_FlexyFramework::get();
733         $o = isset($ff->Pman_Core)  ? $ff->Pman_Core : array();
734         if (isset($o['packseed'])) {
735             return $this->outputJavascriptIncludesBC();
736         }
737         
738         
739         $mods = $this->modulesList();
740         
741         $is_bootstrap = in_array('BAdmin', $mods);
742         
743         foreach($mods as $mod) {
744             // add the css file..
745             
746             if ($is_bootstrap) {
747                 if (!file_exists($this->rootDir."/Pman/$mod/is_bootstrap")) {
748                     echo '<!-- missing '. $this->rootDir."/Pman/$mod/is_bootstrap  - skipping -->";
749                     continue;
750                 }
751                 
752             }
753         
754             $this->outputJavascriptDir("Pman/$mod/widgets", "*.js");
755             $this->outputJavascriptDir("Pman/$mod", "*.js");
756             
757         }
758         
759         if (empty($this->disable_jstemplate)) {
760         // and finally the JsTemplate...
761             echo '<script type="text/javascript" src="'. $this->baseURL. '/Core/JsTemplate"></script>'."\n";
762         }
763         
764         $this->callModules('outputJavascriptIncludes', $this);
765         return '';
766     }
767     var $css_includes = array();
768      /**
769      * outputCSSIncludes:
770      *
771      * output <link rel=stylesheet......> for all the modules in the applcaiton
772      *
773      *
774      * This could css minify as well.
775      */
776     function outputCSSIncludes() // includes on CSS links.
777     {
778        
779         
780         $mods = $this->modulesList();
781         $is_bootstrap = in_array('BAdmin', $mods);
782
783         $this->callModules('applyCSSIncludes', $this);
784         foreach($this->css_includes as $module => $ar) {
785             
786             if ($ar) {
787                 $this->assetArrayToHtml( $ar , 'css');
788             }
789         }
790         
791         // old style... - probably remove this...
792         $this->callModules('outputCSSIncludes', $this);
793         
794         foreach($mods as $mod) {
795             // add the css file..
796             if ($is_bootstrap  && !file_exists($this->rootDir."/Pman/$mod/is_bootstrap")) {
797                 echo '<!-- missing '. $this->rootDir."/Pman/$mod/is_bootstrap  - skipping -->";
798                 continue;
799             }
800             $this->outputCSSDir("Pman/$mod","*.css");
801            
802             $this->outputSCSS($mod);
803             
804             
805         }
806         return ''; // needs to return something as we output it..
807         
808     }
809     
810     
811     
812     
813     
814     
815     
816     
817      
818     
819     // --- OLD CODE - in for BC on MO project.... - needs removing...
820     
821     // used on old versions.....
822     function outputJavascriptIncludesBC()  
823     {
824         
825         $mods = $this->modulesList();
826         
827         foreach($mods as $mod) {
828             // add the css file..
829         
830              
831             $files = $this->moduleJavascriptList($mod.'/widgets');
832             foreach($files as $f) {
833                 echo '<script type="text/javascript" src="'. $f. '"></script>'."\n";
834             }
835             
836             $files = $this->moduleJavascriptList($mod);
837             foreach($files as $f) {
838                 echo '<script type="text/javascript" src="'. $f. '"></script>'."\n";
839             }
840             
841         }
842         if (empty($this->disable_jstemplate)) {
843         // and finally the JsTemplate...
844             echo '<script type="text/javascript" src="'. $this->baseURL. '/Core/JsTemplate"></script>'."\n";
845         }
846         return '';
847     }
848     /**
849      * Gather infor for javascript files..
850      *
851      * @param {String} $mod the module to get info about.
852      * @return {StdClass}  details about module.
853      */
854     function moduleJavascriptFilesInfo($mod)
855     {
856         
857         static $cache = array();
858         
859         if (isset($cache[$mod])) {
860             return $cache[$mod];
861         }
862         
863         
864         $ff = HTML_FlexyFramework::get();
865         
866         $base = dirname($_SERVER['SCRIPT_FILENAME']);
867         $dir =   $this->rootDir.'/Pman/'. $mod;
868         $path = $this->rootURL ."/Pman/$mod/";
869         
870         $ar = glob($dir . '/*.js');
871         
872         $files = array();
873         $arfiles = array();
874         $maxtime = 0;
875         $mtime = 0;
876         foreach($ar as $fn) {
877             $f = basename($fn);
878             // got the 'module file..'
879             $mtime = filemtime($dir . '/'. $f);
880             $maxtime = max($mtime, $maxtime);
881             $arfiles[$fn] = $mtime;
882             $files[] = $path . $f . '?ts='.$mtime;
883         }
884         
885         ksort($arfiles); // just sort by name so it's consistant for serialize..
886         
887         // The original idea of this was to serve the files direct from a publicly available 'cache' directory.
888         // but that doesnt really make sense - as we can just serve it from the session directory where we stick
889         // cached data anyway.
890         
891         /*
892         $compile  = empty($ff->Pman['public_cache_dir']) ? 0 : 1;
893         $basedir = $compile ? $ff->Pman['public_cache_dir'] : false;
894         $baseurl = $compile ? $ff->Pman['public_cache_url'] : false;
895         */
896        
897         $compile = 1;
898         $basedir = session_save_path().   '/translate-cache/';
899         if (!file_exists($basedir)) {
900             mkdir($basedir,0755);
901         }
902         $baseurl = $this->baseURL .  '/Admin/Translations';
903         
904         if (PHP_VERSION_ID < 70000 ) {
905             $lsort = create_function('$a,$b','return strlen($a) > strlen($b) ? 1 : -1;');
906             usort($files, $lsort);
907         } else {
908             usort($files, function($a,$b) { return strlen($a) > strlen($b) ? 1 : -1; });
909         }
910         
911         $smod = str_replace('/','.',$mod);
912         
913         $output = date('Y-m-d-H-i-s-', $maxtime). $smod .'-'.md5(serialize($arfiles)) .'.js';
914         
915         
916         // why are translations done like this - we just build them on the fly frmo the database..
917         $tmtime = file_exists($this->rootDir.'/_translations_/'. $smod.'.js')
918             ? filemtime($this->rootDir.'/_translations_/'. $smod.'.js') : 0;
919         
920         $cache[$mod]  = (object) array(
921             'smod' =>               $smod, // module name without '/'
922             'files' =>              $files, // list of all files.
923             'filesmtime' =>         $arfiles,  // map of mtime=>file
924             'maxtime' =>            $maxtime, // max mtime
925             'compile' =>            $this->isDev ? false : $compile,
926             'translation_file' =>   $base .'/_translations_/' . $smod .  '.js',
927             'translation_mtime' =>  $tmtime,
928             'output' =>             $output,
929             'translation_data' =>   preg_replace('/\.js$/', '.__translation__.js', $output),
930             'translation_base' =>   $dir .'/', //prefix of filename (without moudle name))
931             'basedir' =>            $basedir,   
932             'baseurl' =>            $baseurl,
933             'module_dir' =>         $dir,  
934         );
935         return $cache[$mod];
936     }
937      
938     
939     /**
940      *  moduleJavascriptList: list the javascript files in a module
941      *
942      *  The original version of this.. still needs more thought...
943      *
944      *  Compiled is in Pman/_compiled_/{$mod}/{LATEST...}.js
945      *  Translations are in Pman/_translations_/{$mod}.js
946      *  
947      *  if that stuff does not exist just list files in  Pman/{$mod}/*.js
948      *
949      *  Compiled could be done on the fly..
950      * 
951      *
952      *
953      *  @param {String} $mod  the module to look at - eg. Pman/{$mod}/*.js
954      *  @return {Array} list of include paths (either compiled or raw)
955      *
956      */
957
958     
959     
960     function moduleJavascriptList($mod)
961     {
962         
963         
964         $dir =   $this->rootDir.'/Pman/'. $mod;
965         
966         
967         if (!file_exists($dir)) {
968             echo '<!-- missing directory '. htmlspecialchars($dir) .' -->';
969             return array();
970         }
971         
972         $info = $this->moduleJavascriptFilesInfo($mod);
973        
974         
975           
976         if (empty($info->files)) {
977             return array();
978         }
979         // finally sort the files, so they are in the right order..
980         
981         // only compile this stuff if public_cache is set..
982         
983          
984         // suggestions...
985         //  public_cache_dir =   /var/www/myproject_cache
986         //  public_cache_url =   /myproject_cache    (with Alias apache /myproject_cache/ /var/www/myproject_cache/)
987         
988         // bit of debugging
989         if (!$info->compile) {
990             echo "<!-- Javascript compile turned off (isDev on, or public_cache_dir not set) -->\n";
991             return $info->files;
992         }
993         
994         // where are we going to write all of this..
995         // This has to be done via a 
996         if (!file_exists($info->basedir.'/'.$info->output) || !filesize($info->basedir.'/'.$info->output)) {
997             require_once 'Pman/Core/JsCompile.php';
998             $x = new Pman_Core_JsCompile();
999             
1000             $x->pack($info->filesmtime,$info->basedir.'/'.$info->output, $info->translation_base);
1001         } else {
1002             echo "<!-- file exists not exist: {$info->basedir}/{$info->output} -->\n";
1003         }
1004         
1005         if (file_exists($info->basedir.'/'.$info->output) &&
1006                 filesize($info->basedir.'/'.$info->output)) {
1007             
1008             $ret =array(
1009                 $info->baseurl.'/'. $info->output,
1010               
1011             );
1012             // output all the ava
1013             // fixme  - this needs the max datetime for the translation file..
1014             $ret[] = $this->baseURL."/Admin/InterfaceTranslations/".$mod.".js"; //?ts=".$info->translation_mtime;
1015             
1016             //if ($info->translation_mtime) {
1017             //    $ret[] = $this->rootURL."/_translations_/". $info->smod.".js?ts=".$info->translation_mtime;
1018             //}
1019             return $ret;
1020         }
1021         
1022         
1023         
1024         // give up and output original files...
1025         
1026          
1027         return $info->files;
1028
1029         
1030     }
1031     
1032     /**
1033      * Error handling...
1034      *  PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, array($this, 'onPearError'));
1035      */
1036     function initErrorHandling()
1037     {
1038         if (!class_exists('HTML_FlexyFramework2')) {
1039             // what about older code that still users PEAR?
1040             PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, array($this, 'onPearError'));
1041         }
1042         set_exception_handler(array($this,'onException'));
1043         
1044     }
1045     
1046     
1047     static $permitError = false; // static why?
1048     
1049     var $showErrorToUser = true;
1050     
1051     function onPearError($err)
1052     {
1053         return $this->onException($err);
1054         
1055     }
1056     
1057     
1058     function onException($ex)
1059     {
1060          static $reported = false;
1061         if ($reported) {
1062             return;
1063         }
1064         
1065         if (Pman::$permitError) {
1066             return;
1067         }
1068         
1069         
1070         $reported = true;
1071         
1072         $out = is_a($ex,'Exception') || is_a($ex, 'Error') ? $ex->getMessage() : $ex->toString();
1073         
1074         
1075         //print_R($bt); exit;
1076         $ret = array();
1077         $n = 0;
1078         $bt = is_a($ex,'Exception')|| is_a($ex, 'Error')  ? $ex->getTrace() : $ex->backtrace;
1079         if (is_a($ex,'Exception')|| is_a($ex, 'Error') ) {
1080             $ret[] = $ex->getFile() . '('. $ex->getLine() . ')';
1081         }
1082         foreach( $bt as $b) {
1083             $ret[] = @$b['file'] . '(' . @$b['line'] . ')@' .   @$b['class'] . '::' . @$b['function'];
1084             if ($n > 20) {
1085                 break;
1086             }
1087             $n++;
1088         }
1089         //convert the huge backtrace into something that is readable..
1090         $out .= "\n" . implode("\n",  $ret);
1091         
1092         $this->addEvent("EXCEPTION", false, $out);
1093         
1094         if ($this->showErrorToUser) {
1095             print_R($out);exit;
1096         }
1097         // not sure why this is here... - perhaps doing a jerr() was actually caught by the UI, and hidden from the user..?
1098         $this->jerror(false,"An error Occured, please contact the website owner");
1099         
1100         //$this->jerr($out);
1101         
1102         
1103     }
1104     
1105     
1106     /**
1107      * ---------------- Logging ---------------   
1108      */
1109     
1110     /**
1111      * addEventOnce:
1112      * Log an action (only if it has not been logged already.
1113      * 
1114      * @param {String} action  - group/name of event
1115      * @param {DataObject|false} obj - dataobject action occured on.
1116      * @param {String} any remarks
1117      * @return {false|DB_DataObject} Event object.,
1118      */
1119     
1120     function addEventOnce($act, $obj = false, $remarks = '') 
1121     {
1122         if (!empty(HTML_FlexyFramework::get()->Pman['disable_events'])) {
1123             return;
1124         }
1125         $e = DB_DataObject::factory('Events');
1126         $e->init($act,$obj,$remarks); 
1127         if ($e->find(true)) {
1128             return false;
1129         }
1130         return $this->addEvent($act, $obj, $remarks);
1131     }
1132     /**
1133      * addEvent:
1134      * Log an action.
1135      * 
1136      * @param {String} action  - group/name of event
1137      * @param {DataObject|false} obj - dataobject action occured on.
1138      * @param {String} any remarks
1139      * @return {DB_DataObject} Event object.,
1140      */
1141     
1142     function addEvent($act, $obj = false, $remarks = '') 
1143     {
1144         
1145         if (!empty(HTML_FlexyFramework::get()->Pman['disable_events'])) {
1146             return;
1147         }
1148         $au = $this->getAuthUser();
1149        
1150         $e = DB_DataObject::factory('Events');
1151         $e->init($act,$obj,$remarks); 
1152          
1153         $e->event_when = $e->sqlValue('NOW()');
1154         
1155         $eid = $e->insert();
1156         
1157         // fixme - this should be in onInsert..
1158         $wa = DB_DataObject::factory('core_watch');
1159         if (method_exists($wa,'notifyEvent')) {
1160             $wa->notifyEvent($e); // trigger any actions..
1161         }
1162         
1163         
1164         $e->onInsert(isset($_REQUEST) ? $_REQUEST : array() , $this);
1165         
1166        
1167         return $e;
1168         
1169     }
1170     
1171     function addEventNotifyOnly($act, $obj = false, $remarks = '')
1172     {
1173          $au = $this->getAuthUser();
1174        
1175         $e = DB_DataObject::factory('Events');
1176         $e->init($act,$obj,$remarks); 
1177          
1178         $e->event_when = $e->sqlValue('NOW()');
1179         $wa = DB_DataObject::factory('core_watch');
1180         if (method_exists($wa,'notifyEvent')) {
1181             $wa->notifyEvent($e); // trigger any actions..
1182         }
1183     }
1184     
1185     
1186     // ------------------ DEPERCIATED ----------------------------
1187      
1188     // DEPRECITAED - use moduleslist
1189     function modules()  { return $this->modulesList();  }
1190     
1191    
1192     // DEPRICATED  USE Pman_Core_Mailer
1193     
1194     function emailTemplate($templateFile, $args)
1195     {
1196     
1197         require_once 'Pman/Core/Mailer.php';
1198         $r = new Pman_Core_Mailer(array(
1199             'template'=>$templateFile,
1200             'contents' => $args,
1201             'page' => $this
1202         ));
1203         return $r->toData();
1204          
1205     }
1206     // DEPRICATED - USE Pman_Core_Mailer 
1207     // WHAT Part about DEPRICATED Does no one understand??
1208     function sendTemplate($templateFile, $args)
1209     {
1210         require_once 'Pman/Core/Mailer.php';
1211         $r = new Pman_Core_Mailer(array(
1212             'template'=>$templateFile,
1213             'contents' => array(),
1214             'page' => $this
1215         ));
1216         return $r->send();
1217         
1218     
1219     }
1220 }