]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - ModuleInstall/ModuleScanner.php
Release 6.5.9
[Github/sugarcrm.git] / ModuleInstall / ModuleScanner.php
1 <?php
2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4  * SugarCRM Community Edition is a customer relationship management program developed by
5  * SugarCRM, Inc. Copyright (C) 2004-2012 SugarCRM Inc.
6  * 
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU Affero General Public License version 3 as published by the
9  * Free Software Foundation with the addition of the following permission added
10  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13  * 
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17  * details.
18  * 
19  * You should have received a copy of the GNU Affero General Public License along with
20  * this program; if not, see http://www.gnu.org/licenses or write to the Free
21  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  * 02110-1301 USA.
23  * 
24  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
26  * 
27  * The interactive user interfaces in modified source and object code versions
28  * of this program must display Appropriate Legal Notices, as required under
29  * Section 5 of the GNU Affero General Public License version 3.
30  * 
31  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32  * these Appropriate Legal Notices must retain the display of the "Powered by
33  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34  * technical reasons, the Appropriate Legal Notices must display the words
35  * "Powered by SugarCRM".
36  ********************************************************************************/
37
38 class ModuleScanner{
39         private $manifestMap = array(
40                         'pre_execute'=>'pre_execute',
41                         'install_mkdirs'=>'mkdir',
42                         'install_copy'=>'copy',
43                         'install_images'=>'image_dir',
44                         'install_menus'=>'menu',
45                         'install_userpage'=>'user_page',
46                         'install_dashlets'=>'dashlets',
47                         'install_administration'=>'administration',
48                         'install_connectors'=>'connectors',
49                         'install_vardefs'=>'vardefs',
50                         'install_layoutdefs'=>'layoutdefs',
51                         'install_layoutfields'=>'layoutfields',
52                         'install_relationships'=>'relationships',
53                         'install_languages'=>'language',
54             'install_logichooks'=>'logic_hooks',
55                         'post_execute'=>'post_execute',
56
57         );
58
59         /**
60          * config settings
61          * @var array
62          */
63         private $config = array();
64         private $config_hash;
65
66         private $blackListExempt = array();
67         private $classBlackListExempt = array();
68
69         private $validExt = array('png', 'gif', 'jpg', 'css', 'js', 'php', 'txt', 'html', 'htm', 'tpl', 'pdf', 'md5', 'xml');
70         private $classBlackList = array(
71         // Class names specified here must be in lowercase as the implementation
72         // of the tokenizer converts all tokens to lowercase.
73         'reflection',
74         'reflectionclass',
75         'reflectionzendextension',
76         'reflectionextension',
77         'reflectionfunction',
78         'reflectionfunctionabstract',
79         'reflectionmethod',
80         'reflectionobject',
81         'reflectionparameter',
82         'reflectionproperty',
83         'reflector',
84         'reflectionexception',
85         'lua',
86     );
87         private $blackList = array(
88     'popen',
89     'proc_open',
90     'escapeshellarg',
91     'escapeshellcmd',
92     'proc_close',
93     'proc_get_status',
94     'proc_nice',
95         'passthru',
96     'clearstatcache',
97     'disk_free_space',
98     'disk_total_space',
99     'diskfreespace',
100     'fclose',
101     'feof',
102     'fflush',
103     'fgetc',
104     'fgetcsv',
105     'fgets',
106     'fgetss',
107     'file_exists',
108     'file_get_contents',
109     'filesize',
110     'filetype',
111     'flock',
112     'fnmatch',
113     'fpassthru',
114     'fputcsv',
115     'fputs',
116     'fread',
117     'fscanf',
118     'fseek',
119     'fstat',
120     'ftell',
121     'ftruncate',
122     'fwrite',
123     'glob',
124     'is_dir',
125     'is_file',
126     'is_link',
127     'is_readable',
128     'is_uploaded_file',
129     'parse_ini_string',
130     'pathinfo',
131     'pclose',
132     'readfile',
133     'readlink',
134     'realpath_cache_get',
135     'realpath_cache_size',
136     'realpath',
137     'rewind',
138     'set_file_buffer',
139     'tmpfile',
140     'umask',
141     'ini_set',
142         'eval',
143         'exec',
144         'system',
145         'shell_exec',
146         'passthru',
147         'chgrp',
148         'chmod',
149         'chwown',
150         'file_put_contents',
151         'file',
152         'fileatime',
153         'filectime',
154         'filegroup',
155         'fileinode',
156         'filemtime',
157         'fileowner',
158         'fileperms',
159         'fopen',
160         'is_executable',
161         'is_writable',
162         'is_writeable',
163         'lchgrp',
164         'lchown',
165         'linkinfo',
166         'lstat',
167         'mkdir',
168     'mkdir_recursive',
169         'parse_ini_file',
170         'rmdir',
171     'rmdir_recursive',
172         'stat',
173         'tempnam',
174         'touch',
175         'unlink',
176         'getimagesize',
177         'call_user_func',
178         'call_user_func_array',
179         'create_function',
180
181
182         //mutliple files per function call
183         'copy',
184     'copy_recursive',
185         'link',
186         'rename',
187         'symlink',
188         'move_uploaded_file',
189         'chdir',
190         'chroot',
191         'create_cache_directory',
192         'mk_temp_dir',
193         'write_array_to_file',
194         'write_encoded_file',
195         'create_custom_directory',
196         'sugar_rename',
197         'sugar_chown',
198         'sugar_fopen',
199         'sugar_mkdir',
200         'sugar_file_put_contents',
201         'sugar_chgrp',
202         'sugar_chmod',
203         'sugar_touch',
204
205         // Functions that have callbacks can circumvent our security measures.
206         // List retrieved through PHP's XML documentation, and running the
207         // following script in the reference directory:
208
209         // grep -R callable . | grep -v \.svn | grep methodparam | cut -d: -f1 | sort -u | cut -d"." -f2 | sed 's/\-/\_/g' | cut -d"/" -f4
210
211         // AMQPQueue
212         'consume',
213
214         // PHP internal - arrays
215         'array_diff_uassoc',
216         'array_diff_ukey',
217         'array_filter',
218         'array_intersect_uassoc',
219         'array_intersect_ukey',
220         'array_map',
221         'array_reduce',
222         'array_udiff_assoc',
223         'array_udiff_uassoc',
224         'array_udiff',
225         'array_uintersect_assoc',
226         'array_uintersect_uassoc',
227         'array_uintersect',
228         'array_walk_recursive',
229         'array_walk',
230         'uasort',
231         'uksort',
232         'usort',
233
234         // EIO functions that accept callbacks.
235         'eio_busy',
236         'eio_chmod',
237         'eio_chown',
238         'eio_close',
239         'eio_custom',
240         'eio_dup2',
241         'eio_fallocate',
242         'eio_fchmod',
243         'eio_fchown',
244         'eio_fdatasync',
245         'eio_fstat',
246         'eio_fstatvfs',
247         'eio_fsync',
248         'eio_ftruncate',
249         'eio_futime',
250         'eio_grp',
251         'eio_link',
252         'eio_lstat',
253         'eio_mkdir',
254         'eio_mknod',
255         'eio_nop',
256         'eio_open',
257         'eio_read',
258         'eio_readahead',
259         'eio_readdir',
260         'eio_readlink',
261         'eio_realpath',
262         'eio_rename',
263         'eio_rmdir',
264         'eio_sendfile',
265         'eio_stat',
266         'eio_statvfs',
267         'eio_symlink',
268         'eio_sync_file_range',
269         'eio_sync',
270         'eio_syncfs',
271         'eio_truncate',
272         'eio_unlink',
273         'eio_utime',
274         'eio_write',
275
276         // PHP internal - error functions
277         'set_error_handler',
278         'set_exception_handler',
279
280         // Forms Data Format functions
281         'fdf_enum_values',
282
283         // PHP internal - function handling
284         'call_user_func_array',
285         'call_user_func',
286         'forward_static_call_array',
287         'forward_static_call',
288         'register_shutdown_function',
289         'register_tick_function',
290
291         // Gearman
292         'setclientcallback',
293         'setcompletecallback',
294         'setdatacallback',
295         'setexceptioncallback',
296         'setfailcallback',
297         'setstatuscallback',
298         'setwarningcallback',
299         'setworkloadcallback',
300         'addfunction',
301
302         // Firebird/InterBase
303         'ibase_set_event_handler',
304
305         // LDAP
306         'ldap_set_rebind_proc',
307
308         // LibXML
309         'libxml_set_external_entity_loader',
310
311         // Mailparse functions
312         'mailparse_msg_extract_part_file',
313         'mailparse_msg_extract_part',
314         'mailparse_msg_extract_whole_part_file',
315
316         // Memcache(d) functions
317         'addserver',
318         'setserverparams',
319         'get',
320         'getbykey',
321         'getdelayed',
322         'getdelayedbykey',
323
324         // MySQLi
325         'set_local_infile_handler',
326
327         // PHP internal - network functions
328         'header_register_callback',
329
330         // Newt
331         'newt_entry_set_filter',
332         'newt_set_suspend_callback',
333
334         // OAuth
335         'consumerhandler',
336         'timestampnoncehandler',
337         'tokenhandler',
338
339         // PHP internal - output control
340         'ob_start',
341
342         // PHP internal - PCNTL
343         'pcntl_signal',
344
345         // PHP internal - PCRE
346         'preg_replace_callback',
347
348         // SQLite
349         'sqlitecreateaggregate',
350         'sqlitecreatefunction',
351         'sqlite_create_aggregate',
352         'sqlite_create_function',
353
354         // RarArchive
355         'open',
356
357         // Readline
358         'readline_callback_handler_install',
359         'readline_completion_function',
360
361         // PHP internal - session handling
362         'session_set_save_handler',
363
364         // PHP internal - SPL
365         'construct',
366         'iterator_apply',
367         'spl_autoload_register',
368
369         // Sybase
370         'sybase_set_message_handler',
371
372         // PHP internal - variable handling
373         'is_callable',
374
375         // XML Parser
376         'xml_set_character_data_handler',
377         'xml_set_default_handler',
378         'xml_set_element_handler',
379         'xml_set_end_namespace_decl_handler',
380         'xml_set_external_entity_ref_handler',
381         'xml_set_notation_decl_handler',
382         'xml_set_processing_instruction_handler',
383         'xml_set_start_namespace_decl_handler',
384         'xml_set_unparsed_entity_decl_handler',
385 );
386     private $methodsBlackList = array('setlevel');
387
388         public function printToWiki(){
389                 echo "'''Default Extensions'''<br>";
390                 foreach($this->validExt as $b){
391                         echo '#' . $b . '<br>';
392
393                 }
394                 echo "'''Default Black Listed Functions'''<br>";
395                 foreach($this->blackList as $b){
396                         echo '#' . $b . '<br>';
397
398                 }
399
400         }
401
402         public function __construct(){
403             if(!empty($GLOBALS['sugar_config']['moduleInstaller'])) {
404                 $this->config = $GLOBALS['sugar_config']['moduleInstaller'];
405             }
406
407                 if(!empty($this->config['blackListExempt'])){
408                         $this->blackListExempt = array_merge($this->blackListExempt, $this->config['blackListExempt']);
409                 }
410                 if(!empty($this->config['blackList'])){
411                         $this->blackList = array_merge($this->blackList, $this->config['blackList']);
412                 }
413         if(!empty($this->config['classBlackListExempt'])){
414             $this->classBlackListExempt = array_merge($this->classBlackListExempt, $this->config['classBlackListExempt']);
415         }
416         if(!empty($this->config['classBlackList'])){
417             $this->classBlackList = array_merge($this->classBlackList, $this->config['classBlackList']);
418         }
419           if(!empty($this->config['validExt'])){
420                         $this->validExt = array_merge($this->validExt, $this->config['validExt']);
421                 }
422
423         }
424
425         private $issues = array();
426         private $pathToModule = '';
427
428         /**
429          *returns a list of issues
430          */
431         public function getIssues(){
432                 return $this->issues;
433         }
434
435         /**
436          *returns true or false if any issues were found
437          */
438         public function hasIssues(){
439                 return !empty($this->issues);
440         }
441
442         /**
443          *Ensures that a file has a valid extension
444          */
445         private function isValidExtension($file){
446                 $file = strtolower($file);
447
448                 $extPos = strrpos($file, '.');
449                 //make sure they don't override the files.md5
450                 if($extPos === false || $file == 'files.md5')return false;
451                 $ext = substr($file, $extPos + 1);
452                 return in_array($ext, $this->validExt);
453
454         }
455
456         /**
457          *Scans a directory and calls on scan file for each file
458          **/
459         public function scanDir($path){
460                 static $startPath = '';
461                 if(empty($startPath))$startPath = $path;
462                 if(!is_dir($path))return false;
463                 $d = dir($path);
464                 while($e = $d->read()){
465                 $next = $path . '/' . $e;
466                 if(is_dir($next)){
467                         if(substr($e, 0, 1) == '.')continue;
468                         $this->scanDir($next);
469                 }else{
470                         $issues = $this->scanFile($next);
471
472
473                 }
474                 }
475         return true;
476         }
477
478         /**
479          * Check if the file contents looks like PHP
480          * @param string $contents File contents
481          * @return boolean
482          */
483         public function isPHPFile($contents)
484         {
485             if(stripos($contents, '<?php') !== false) return true;
486             for($tag=0;($tag = stripos($contents, '<?', $tag)) !== false;$tag++) {
487             if(strncasecmp(substr($contents, $tag, 13), '<?xml version', 13) == 0) {
488                 // <?xml version is OK, skip it
489                 $tag++;
490                 continue;
491             }
492             // found <?, it's PHP
493             return true;
494             }
495             return false;
496         }
497
498         /**
499          * Given a file it will open it's contents and check if it is a PHP file (not safe to just rely on extensions) if it finds <?php tags it will use the tokenizer to scan the file
500          * $var()  and ` are always prevented then whatever is in the blacklist.
501          * It will also ensure that all files are of valid extension types
502          *
503          */
504         public function scanFile($file){
505                 $issues = array();
506                 if(!$this->isValidExtension($file)){
507                         $issues[] = translate('ML_INVALID_EXT');
508                         $this->issues['file'][$file] = $issues;
509                         return $issues;
510                 }
511                 $contents = file_get_contents($file);
512                 if(!$this->isPHPFile($contents)) return $issues;
513                 $tokens = @token_get_all($contents);
514                 $checkFunction = false;
515                 $possibleIssue = '';
516                 $lastToken = false;
517                 foreach($tokens as $index=>$token){
518                         if(is_string($token[0])){
519                                 switch($token[0]){
520                                         case '`':
521                                                 $issues['backtick'] = translate('ML_INVALID_FUNCTION') . " '`'";
522                                         case '(':
523                                                 if($checkFunction)$issues[] = $possibleIssue;
524                                                 break;
525                                 }
526                                 $checkFunction = false;
527                                 $possibleIssue = '';
528                         }else{
529                                 $token['_msi'] = token_name($token[0]);
530                                 switch($token[0]){
531                                         case T_WHITESPACE: continue;
532                                         case T_EVAL:
533                                                 if(in_array('eval', $this->blackList) && !in_array('eval', $this->blackListExempt))
534                                                 $issues[]= translate('ML_INVALID_FUNCTION') . ' eval()';
535                                                 break;
536                                         case T_STRING:
537                                                 $token[1] = strtolower($token[1]);
538                                                 if($lastToken !== false && $lastToken[0] == T_NEW) {
539                             if(!in_array($token[1], $this->classBlackList))break;
540                             if(in_array($token[1], $this->classBlackListExempt))break;
541                         } elseif ($token[0] == T_DOUBLE_COLON) {
542                             if(!in_array($lastToken[1], $this->classBlackList))break;
543                             if(in_array($lastToken[1], $this->classBlackListExempt))break;
544                         } else {
545                             //if nothing else fit, lets check the last token to see if this is a possible method call
546                             if ($lastToken !== false &&
547                             ($lastToken[0] == T_OBJECT_OPERATOR ||  $lastToken[0] == T_DOUBLE_COLON))
548                             {
549                                 //this is a method call, check the black list
550                                 if(in_array($token[1], $this->methodsBlackList)){
551                                     $issues[]= translate('ML_INVALID_METHOD') . ' ' .$token[1].  '()';
552                                 }
553                                 break;
554                             }
555
556
557                             if(!in_array($token[1], $this->blackList))break;
558                             if(in_array($token[1], $this->blackListExempt))break;
559
560                         }
561                                         case T_VARIABLE:
562                                                 $checkFunction = true;
563                                                 $possibleIssue = translate('ML_INVALID_FUNCTION') . ' ' .  $token[1] . '()';
564                                                 break;
565
566                                         default:
567                                                 $checkFunction = false;
568                                                 $possibleIssue = '';
569
570                                 }
571                                 if ($token[0] != T_WHITESPACE)
572                                 {
573                                         $lastToken = $token;
574                                 }
575                         }
576
577                 }
578                 if(!empty($issues)){
579                         $this->issues['file'][$file] = $issues;
580                 }
581
582                 return $issues;
583         }
584
585
586         /*
587          * checks files.md5 file to see if the file is from sugar
588          * ONLY WORKS ON FILES
589          */
590         public function sugarFileExists($path){
591                 static $md5 = array();
592                 if(empty($md5) && file_exists('files.md5'))
593                 {
594                         include('files.md5');
595                         $md5 = $md5_string;
596                 }
597                 if(isset($md5['./' . $path]))return true;
598
599
600         }
601
602
603         /**
604          *This function will scan the Manifest for disabled actions specified in $GLOBALS['sugar_config']['moduleInstaller']['disableActions']
605          *if $GLOBALS['sugar_config']['moduleInstaller']['disableRestrictedCopy'] is set to false or not set it will call on scanCopy to ensure that it is not overriding files
606          */
607         public function scanManifest($manifestPath){
608                 $issues = array();
609                 if(!file_exists($manifestPath)){
610                         $this->issues['manifest'][$manifestPath] = translate('ML_NO_MANIFEST');
611                         return $issues;
612                 }
613                 $fileIssues = $this->scanFile($manifestPath);
614                 //if the manifest contains malicious code do not open it
615                 if(!empty($fileIssues)){
616                         return $fileIssues;
617                 }
618                 $this->lockConfig();
619                 list($manifest, $installdefs) = MSLoadManifest($manifestPath);
620                 $fileIssues = $this->checkConfig($manifestPath);
621                 if(!empty($fileIssues)){
622                         return $fileIssues;
623                 }
624
625                 //scan for disabled actions
626                 if(isset($this->config['disableActions'])){
627                         foreach($this->config['disableActions'] as $action){
628                                 if(isset($installdefs[$this->manifestMap[$action]])){
629                                         $issues[] = translate('ML_INVALID_ACTION_IN_MANIFEST') . $this->manifestMap[$action];
630                                 }
631                         }
632                 }
633
634                 //now lets scan for files that will override our files
635                 if(empty($this->config['disableRestrictedCopy']) && isset($installdefs['copy'])){
636                         foreach($installdefs['copy'] as $copy){
637                                 $from = str_replace('<basepath>', $this->pathToModule, $copy['from']);
638                                 $to = $copy['to'];
639                                 if(substr_count($from, '..')){
640                                         $this->issues['copy'][$from] = translate('ML_PATH_MAY_NOT_CONTAIN').' ".." -' . $from;
641                                 }
642                                 if(substr_count($to, '..')){
643                                         $this->issues['copy'][$to] = translate('ML_PATH_MAY_NOT_CONTAIN'). ' ".." -' . $to;
644                                 }
645                                 while(substr_count($from, '//')){
646                                         $from = str_replace('//', '/', $from);
647                                 }
648                                 while(substr_count($to, '//')){
649                                         $to = str_replace('//', '/', $to);
650                                 }
651                                 $this->scanCopy($from, $to);
652                         }
653                 }
654                 if(!empty($issues)){
655                         $this->issues['manifest'][$manifestPath] = $issues;
656                 }
657
658
659
660         }
661
662
663
664         /**
665          * Takes in where the file will is specified to be copied from and to
666          * and ensures that there is no official sugar file there. If the file exists it will check
667          * against the MD5 file list to see if Sugar Created the file
668          *
669          */
670         function scanCopy($from, $to){
671                                 //if the file doesn't exist for the $to then it is not overriding anything
672                                 if(!file_exists($to))return;
673                                 //if $to is a dir and $from is a file then make $to a full file path as well
674                                 if(is_dir($to) && is_file($from)){
675                                         if(substr($to,-1) === '/'){
676                                                 $to = substr($to, 0 , strlen($to) - 1);
677                                         }
678                                         $to .= '/'. basename($from);
679                                 }
680                                 //if the $to is a file and it is found in sugarFileExists then don't allow overriding it
681                                 if(is_file($to) && $this->sugarFileExists($to)){
682                                         $this->issues['copy'][$from] = translate('ML_OVERRIDE_CORE_FILES') . '(' . $to . ')';
683                                 }
684
685                                 if(is_dir($from)){
686                                         $d = dir($from);
687                                         while($e = $d->read()){
688                                                 if($e == '.' || $e == '..')continue;
689                                                 $this->scanCopy($from .'/'. $e, $to .'/' . $e);
690                                         }
691                                 }
692
693
694
695
696
697                         }
698
699
700         /**
701          *Main external function that takes in a path to a package and then scans
702          *that package's manifest for disabled actions and then it scans the PHP files
703          *for restricted function calls
704          *
705          */
706         public function scanPackage($path){
707                 $this->pathToModule = $path;
708                 $this->scanManifest($path . '/manifest.php');
709                 if(empty($this->config['disableFileScan'])){
710                         $this->scanDir($path);
711                 }
712         }
713
714         /**
715          *This function will take all issues of the current instance and print them to the screen
716          **/
717         public function displayIssues($package='Package'){
718                 echo '<h2>'.str_replace('{PACKAGE}' , $package ,translate('ML_PACKAGE_SCANNING')). '</h2><BR><h2 class="error">' . translate('ML_INSTALLATION_FAILED') . '</h2><br><p>' .str_replace('{PACKAGE}' , $package ,translate('ML_PACKAGE_NOT_CONFIRM')). '</p><ul><li>'. translate('ML_OBTAIN_NEW_PACKAGE') . '<li>' . translate('ML_RELAX_LOCAL').
719 '</ul></p><br>' . translate('ML_SUGAR_LOADING_POLICY') .  ' <a href=" http://kb.sugarcrm.com/custom/module-loader-restrictions-for-sugar-open-cloud/">' . translate('ML_SUGAR_KB') . '</a>.'.
720 '<br>' . translate('ML_AVAIL_RESTRICTION'). ' <a href=" http://developers.sugarcrm.com/wordpress/2009/08/14/module-loader-restrictions/">' . translate('ML_SUGAR_DZ') .  '</a>.<br><br>';
721
722
723                 foreach($this->issues as $type=>$issues){
724                         echo '<div class="error"><h2>'. ucfirst($type) .' ' .  translate('ML_ISSUES') . '</h2> </div>';
725                         echo '<div id="details' . $type . '" >';
726                         foreach($issues as $file=>$issue){
727                                 $file = str_replace($this->pathToModule . '/', '', $file);
728                                 echo '<div style="position:relative;left:10px"><b>' . $file . '</b></div><div style="position:relative;left:20px">';
729                                 if(is_array($issue)){
730                                         foreach($issue as $i){
731                                                 echo "$i<br>";
732                                         }
733                                 }else{
734                                         echo "$issue<br>";
735                                 }
736                                 echo "</div>";
737                         }
738                         echo '</div>';
739
740                 }
741                 echo "<br><input class='button' onclick='document.location.href=\"index.php?module=Administration&action=UpgradeWizard&view=module\"' type='button' value=\"" . translate('LBL_UW_BTN_BACK_TO_MOD_LOADER') . "\" />";
742
743         }
744
745         /**
746          * Lock config settings
747          */
748         public function lockConfig()
749         {
750             if(empty($this->config_hash)) {
751                 $this->config_hash = md5(serialize($GLOBALS['sugar_config']));
752             }
753         }
754
755         /**
756          * Check if config was modified. Return
757          * @param string $file
758          * @return array Errors if something wrong, false if no problems
759          */
760         public function checkConfig($file)
761         {
762             $config_hash_after = md5(serialize($GLOBALS['sugar_config']));
763             if($config_hash_after != $this->config_hash) {
764                 $this->issues['file'][$file] = array(translate('ML_CONFIG_OVERRIDE'));
765                 return $this->issues;
766             }
767             return false;
768         }
769
770 }
771
772 /**
773  * Load manifest file
774  * Outside of the class to isolate the context
775  * @param string $manifest_file
776  * @return array
777  */
778 function MSLoadManifest($manifest_file)
779 {
780         include( $manifest_file );
781         return array($manifest, $installdefs);
782 }
783
784 ?>