]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - ModuleInstall/ModuleScanner.php
Release 6.5.15
[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-2013 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             'ziparchive',
87             'splfileinfo',
88             'splfileobject',
89             'pclzip',
90
91     );
92         private $blackList = array(
93     'popen',
94     'proc_open',
95     'escapeshellarg',
96     'escapeshellcmd',
97     'proc_close',
98     'proc_get_status',
99     'proc_nice',
100         'passthru',
101     'clearstatcache',
102     'disk_free_space',
103     'disk_total_space',
104     'diskfreespace',
105         'dir',
106     'fclose',
107     'feof',
108     'fflush',
109     'fgetc',
110     'fgetcsv',
111     'fgets',
112     'fgetss',
113     'file_exists',
114     'file_get_contents',
115     'filesize',
116     'filetype',
117     'flock',
118     'fnmatch',
119     'fpassthru',
120     'fputcsv',
121     'fputs',
122     'fread',
123     'fscanf',
124     'fseek',
125     'fstat',
126     'ftell',
127     'ftruncate',
128     'fwrite',
129     'glob',
130     'is_dir',
131     'is_file',
132     'is_link',
133     'is_readable',
134     'is_uploaded_file',
135         'opendir',
136     'parse_ini_string',
137     'pathinfo',
138     'pclose',
139     'readfile',
140     'readlink',
141     'realpath_cache_get',
142     'realpath_cache_size',
143     'realpath',
144     'rewind',
145         'readdir',
146     'set_file_buffer',
147     'tmpfile',
148     'umask',
149     'ini_set',
150     'set_time_limit',
151         'eval',
152         'exec',
153         'system',
154         'shell_exec',
155         'passthru',
156         'chgrp',
157         'chmod',
158         'chwown',
159         'file_put_contents',
160         'file',
161         'fileatime',
162         'filectime',
163         'filegroup',
164         'fileinode',
165         'filemtime',
166         'fileowner',
167         'fileperms',
168         'fopen',
169         'is_executable',
170         'is_writable',
171         'is_writeable',
172         'lchgrp',
173         'lchown',
174         'linkinfo',
175         'lstat',
176         'mkdir',
177     'mkdir_recursive',
178         'parse_ini_file',
179         'rmdir',
180     'rmdir_recursive',
181         'stat',
182         'tempnam',
183         'touch',
184         'unlink',
185         'getimagesize',
186         'call_user_func',
187         'call_user_func_array',
188         'create_function',
189
190
191         //mutliple files per function call
192         'copy',
193     'copy_recursive',
194         'link',
195         'rename',
196         'symlink',
197         'move_uploaded_file',
198         'chdir',
199         'chroot',
200         'create_cache_directory',
201         'mk_temp_dir',
202         'write_array_to_file',
203         'write_encoded_file',
204         'create_custom_directory',
205         'sugar_rename',
206         'sugar_chown',
207         'sugar_fopen',
208         'sugar_mkdir',
209         'sugar_file_put_contents',
210         'sugar_chgrp',
211         'sugar_chmod',
212         'sugar_touch',
213
214         // Functions that have callbacks can circumvent our security measures.
215         // List retrieved through PHP's XML documentation, and running the
216         // following script in the reference directory:
217
218         // grep -R callable . | grep -v \.svn | grep methodparam | cut -d: -f1 | sort -u | cut -d"." -f2 | sed 's/\-/\_/g' | cut -d"/" -f4
219
220         // AMQPQueue
221         'consume',
222
223         // PHP internal - arrays
224         'array_diff_uassoc',
225         'array_diff_ukey',
226         'array_filter',
227         'array_intersect_uassoc',
228         'array_intersect_ukey',
229         'array_map',
230         'array_reduce',
231         'array_udiff_assoc',
232         'array_udiff_uassoc',
233         'array_udiff',
234         'array_uintersect_assoc',
235         'array_uintersect_uassoc',
236         'array_uintersect',
237         'array_walk_recursive',
238         'array_walk',
239         'uasort',
240         'uksort',
241         'usort',
242
243         // EIO functions that accept callbacks.
244         'eio_busy',
245         'eio_chmod',
246         'eio_chown',
247         'eio_close',
248         'eio_custom',
249         'eio_dup2',
250         'eio_fallocate',
251         'eio_fchmod',
252         'eio_fchown',
253         'eio_fdatasync',
254         'eio_fstat',
255         'eio_fstatvfs',
256         'eio_fsync',
257         'eio_ftruncate',
258         'eio_futime',
259         'eio_grp',
260         'eio_link',
261         'eio_lstat',
262         'eio_mkdir',
263         'eio_mknod',
264         'eio_nop',
265         'eio_open',
266         'eio_read',
267         'eio_readahead',
268         'eio_readdir',
269         'eio_readlink',
270         'eio_realpath',
271         'eio_rename',
272         'eio_rmdir',
273         'eio_sendfile',
274         'eio_stat',
275         'eio_statvfs',
276         'eio_symlink',
277         'eio_sync_file_range',
278         'eio_sync',
279         'eio_syncfs',
280         'eio_truncate',
281         'eio_unlink',
282         'eio_utime',
283         'eio_write',
284
285         // PHP internal - error functions
286         'set_error_handler',
287         'set_exception_handler',
288
289         // Forms Data Format functions
290         'fdf_enum_values',
291
292         // PHP internal - function handling
293         'call_user_func_array',
294         'call_user_func',
295         'forward_static_call_array',
296         'forward_static_call',
297         'register_shutdown_function',
298         'register_tick_function',
299
300         // Gearman
301         'setclientcallback',
302         'setcompletecallback',
303         'setdatacallback',
304         'setexceptioncallback',
305         'setfailcallback',
306         'setstatuscallback',
307         'setwarningcallback',
308         'setworkloadcallback',
309         'addfunction',
310
311         // Firebird/InterBase
312         'ibase_set_event_handler',
313
314         // LDAP
315         'ldap_set_rebind_proc',
316
317         // LibXML
318         'libxml_set_external_entity_loader',
319
320         // Mailparse functions
321         'mailparse_msg_extract_part_file',
322         'mailparse_msg_extract_part',
323         'mailparse_msg_extract_whole_part_file',
324
325         // Memcache(d) functions
326         'addserver',
327         'setserverparams',
328         'get',
329         'getbykey',
330         'getdelayed',
331         'getdelayedbykey',
332
333         // MySQLi
334         'set_local_infile_handler',
335
336         // PHP internal - network functions
337         'header_register_callback',
338
339         // Newt
340         'newt_entry_set_filter',
341         'newt_set_suspend_callback',
342
343         // OAuth
344         'consumerhandler',
345         'timestampnoncehandler',
346         'tokenhandler',
347
348         // PHP internal - output control
349         'ob_start',
350
351         // PHP internal - PCNTL
352         'pcntl_signal',
353
354         // PHP internal - PCRE
355         'preg_replace_callback',
356
357         // SQLite
358         'sqlitecreateaggregate',
359         'sqlitecreatefunction',
360         'sqlite_create_aggregate',
361         'sqlite_create_function',
362
363         // RarArchive
364         'open',
365
366         // Readline
367         'readline_callback_handler_install',
368         'readline_completion_function',
369
370         // PHP internal - session handling
371         'session_set_save_handler',
372
373         // PHP internal - SPL
374         'construct',
375         'iterator_apply',
376         'spl_autoload_register',
377
378         // Sybase
379         'sybase_set_message_handler',
380
381         // PHP internal - variable handling
382         'is_callable',
383
384         // XML Parser
385         'xml_set_character_data_handler',
386         'xml_set_default_handler',
387         'xml_set_element_handler',
388         'xml_set_end_namespace_decl_handler',
389         'xml_set_external_entity_ref_handler',
390         'xml_set_notation_decl_handler',
391         'xml_set_processing_instruction_handler',
392         'xml_set_start_namespace_decl_handler',
393         'xml_set_unparsed_entity_decl_handler',
394
395             // unzip
396             'unzip',
397             'unzip_file',
398 );
399     private $methodsBlackList = array('setlevel', 'put' => array('sugarautoloader'), 'unlink' => array('sugarautoloader'));
400
401         public function printToWiki(){
402                 echo "'''Default Extensions'''<br>";
403                 foreach($this->validExt as $b){
404                         echo '#' . $b . '<br>';
405
406                 }
407                 echo "'''Default Black Listed Functions'''<br>";
408                 foreach($this->blackList as $b){
409                         echo '#' . $b . '<br>';
410
411                 }
412
413         }
414
415     public function __construct()
416     {
417         $params = array(
418             'blackListExempt'      => 'MODULE_INSTALLER_PACKAGE_SCAN_BLACK_LIST_EXEMPT',
419             'blackList'            => 'MODULE_INSTALLER_PACKAGE_SCAN_BLACK_LIST',
420             'classBlackListExempt' => 'MODULE_INSTALLER_PACKAGE_SCAN_CLASS_BLACK_LIST_EXEMPT',
421             'classBlackList'       => 'MODULE_INSTALLER_PACKAGE_SCAN_CLASS_BLACK_LIST',
422             'validExt'             => 'MODULE_INSTALLER_PACKAGE_SCAN_VALID_EXT',
423             'methodsBlackList'     => 'MODULE_INSTALLER_PACKAGE_SCAN_METHOD_LIST',
424         );
425
426         $disableConfigOverride = defined('MODULE_INSTALLER_DISABLE_CONFIG_OVERRIDE')
427             && MODULE_INSTALLER_DISABLE_CONFIG_OVERRIDE;
428
429         $disableDefineOverride = defined('MODULE_INSTALLER_DISABLE_DEFINE_OVERRIDE')
430             && MODULE_INSTALLER_DISABLE_DEFINE_OVERRIDE;
431
432         if (!$disableConfigOverride && !empty($GLOBALS['sugar_config']['moduleInstaller'])) {
433             $this->config = $GLOBALS['sugar_config']['moduleInstaller'];
434         }
435
436         foreach ($params as $param => $constName) {
437
438             if (!$disableConfigOverride && isset($this->config[$param]) && is_array($this->config[$param])) {
439                 $this->{$param} = array_merge($this->{$param}, $this->config[$param]);
440             }
441
442             if (!$disableDefineOverride && defined($constName)) {
443                 $value = constant($constName);
444                 $value = explode(',', $value);
445                 $value = array_map('trim', $value);
446                 $value = array_filter($value, 'strlen');
447                 $this->{$param} = array_merge($this->{$param}, $value);
448             }
449         }
450         }
451
452         private $issues = array();
453         private $pathToModule = '';
454
455         /**
456          *returns a list of issues
457          */
458         public function getIssues(){
459                 return $this->issues;
460         }
461
462         /**
463          *returns true or false if any issues were found
464          */
465         public function hasIssues(){
466                 return !empty($this->issues);
467         }
468
469         /**
470          *Ensures that a file has a valid extension
471          */
472         private function isValidExtension($file){
473                 $file = strtolower($file);
474
475                 $extPos = strrpos($file, '.');
476                 //make sure they don't override the files.md5
477                 if($extPos === false || $file == 'files.md5')return false;
478                 $ext = substr($file, $extPos + 1);
479                 return in_array($ext, $this->validExt);
480
481         }
482
483         /**
484          *Scans a directory and calls on scan file for each file
485          **/
486         public function scanDir($path){
487                 static $startPath = '';
488                 if(empty($startPath))$startPath = $path;
489                 if(!is_dir($path))return false;
490                 $d = dir($path);
491                 while($e = $d->read()){
492                 $next = $path . '/' . $e;
493                 if(is_dir($next)){
494                         if(substr($e, 0, 1) == '.')continue;
495                         $this->scanDir($next);
496                 }else{
497                         $issues = $this->scanFile($next);
498
499
500                 }
501                 }
502         return true;
503         }
504
505         /**
506          * Check if the file contents looks like PHP
507          * @param string $contents File contents
508          * @return boolean
509          */
510         public function isPHPFile($contents)
511         {
512             if(stripos($contents, '<?php') !== false) return true;
513             for($tag=0;($tag = stripos($contents, '<?', $tag)) !== false;$tag++) {
514             if(strncasecmp(substr($contents, $tag, 13), '<?xml version', 13) == 0) {
515                 // <?xml version is OK, skip it
516                 $tag++;
517                 continue;
518             }
519             // found <?, it's PHP
520             return true;
521             }
522             return false;
523         }
524
525         /**
526          * 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
527          * $var()  and ` are always prevented then whatever is in the blacklist.
528          * It will also ensure that all files are of valid extension types
529          *
530          */
531         public function scanFile($file){
532                 $issues = array();
533                 if(!$this->isValidExtension($file)){
534                         $issues[] = translate('ML_INVALID_EXT');
535                         $this->issues['file'][$file] = $issues;
536                         return $issues;
537                 }
538                 $contents = file_get_contents($file);
539                 if(!$this->isPHPFile($contents)) return $issues;
540                 $tokens = @token_get_all($contents);
541                 $checkFunction = false;
542                 $possibleIssue = '';
543                 $lastToken = false;
544                 foreach($tokens as $index=>$token){
545                         if(is_string($token[0])){
546                                 switch($token[0]){
547                                         case '`':
548                                                 $issues['backtick'] = translate('ML_INVALID_FUNCTION') . " '`'";
549                                         case '(':
550                                                 if($checkFunction)$issues[] = $possibleIssue;
551                                                 break;
552                                 }
553                                 $checkFunction = false;
554                                 $possibleIssue = '';
555                         }else{
556                                 $token['_msi'] = token_name($token[0]);
557                                 switch($token[0]){
558                                         case T_WHITESPACE: continue;
559                                         case T_EVAL:
560                                                 if(in_array('eval', $this->blackList) && !in_array('eval', $this->blackListExempt))
561                                                 $issues[]= translate('ML_INVALID_FUNCTION') . ' eval()';
562                                                 break;
563                                         case T_STRING:
564                                                 $token[1] = strtolower($token[1]);
565                                                 if($lastToken !== false && $lastToken[0] == T_NEW) {
566                             if(!in_array($token[1], $this->classBlackList))break;
567                             if(in_array($token[1], $this->classBlackListExempt))break;
568                         } elseif ($token[0] == T_DOUBLE_COLON) {
569                             if(!in_array($lastToken[1], $this->classBlackList))break;
570                             if(in_array($lastToken[1], $this->classBlackListExempt))break;
571                         } else {
572                             //if nothing else fit, lets check the last token to see if this is a possible method call
573                             if ($lastToken !== false &&
574                             ($lastToken[0] == T_OBJECT_OPERATOR ||  $lastToken[0] == T_DOUBLE_COLON))
575                             {
576                                 // check static blacklist for methods
577                                 if(!empty($this->methodsBlackList[$token[1]])) {
578                                     if($this->methodsBlackList[$token[1]] == '*') {
579                                         $issues[]= translate('ML_INVALID_METHOD') . ' ' .$token[1].  '()';
580                                         break;
581                                     } else {
582                                         if($lastToken[0] == T_DOUBLE_COLON && $index > 2 && $tokens[$index-2][0] == T_STRING) {
583                                             $classname = strtolower($tokens[$index-2][1]);
584                                             if(in_array($classname, $this->methodsBlackList[$token[1]])) {
585                                                 $issues[]= translate('ML_INVALID_METHOD') . ' ' .$classname . '::' . $token[1]. '()';
586                                                 break;
587                                             }
588                                         }
589                                     }
590                                 }
591                                 //this is a method call, check the black list
592                                 if(in_array($token[1], $this->methodsBlackList)){
593                                     $issues[]= translate('ML_INVALID_METHOD') . ' ' .$token[1].  '()';
594                                 }
595                                 break;
596                             }
597
598
599                             if(!in_array($token[1], $this->blackList))break;
600                             if(in_array($token[1], $this->blackListExempt))break;
601
602                         }
603                                         case T_VARIABLE:
604                                                 $checkFunction = true;
605                                                 $possibleIssue = translate('ML_INVALID_FUNCTION') . ' ' .  $token[1] . '()';
606                                                 break;
607
608                                         default:
609                                                 $checkFunction = false;
610                                                 $possibleIssue = '';
611
612                                 }
613                                 if ($token[0] != T_WHITESPACE)
614                                 {
615                                         $lastToken = $token;
616                                 }
617                         }
618
619                 }
620                 if(!empty($issues)){
621                         $this->issues['file'][$file] = $issues;
622                 }
623
624                 return $issues;
625         }
626
627
628         /*
629          * checks files.md5 file to see if the file is from sugar
630          * ONLY WORKS ON FILES
631          */
632         public function sugarFileExists($path){
633                 static $md5 = array();
634                 if(empty($md5) && file_exists('files.md5'))
635                 {
636                         include('files.md5');
637                         $md5 = $md5_string;
638                 }
639                 if(isset($md5['./' . $path]))return true;
640
641
642         }
643
644
645         /**
646          *This function will scan the Manifest for disabled actions specified in $GLOBALS['sugar_config']['moduleInstaller']['disableActions']
647          *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
648          */
649         public function scanManifest($manifestPath){
650                 $issues = array();
651                 if(!file_exists($manifestPath)){
652                         $this->issues['manifest'][$manifestPath] = translate('ML_NO_MANIFEST');
653                         return $issues;
654                 }
655                 $fileIssues = $this->scanFile($manifestPath);
656                 //if the manifest contains malicious code do not open it
657                 if(!empty($fileIssues)){
658                         return $fileIssues;
659                 }
660                 $this->lockConfig();
661                 list($manifest, $installdefs) = MSLoadManifest($manifestPath);
662                 $fileIssues = $this->checkConfig($manifestPath);
663                 if(!empty($fileIssues)){
664                         return $fileIssues;
665                 }
666
667                 //scan for disabled actions
668                 if(isset($this->config['disableActions'])){
669                         foreach($this->config['disableActions'] as $action){
670                                 if(isset($installdefs[$this->manifestMap[$action]])){
671                                         $issues[] = translate('ML_INVALID_ACTION_IN_MANIFEST') . $this->manifestMap[$action];
672                                 }
673                         }
674                 }
675
676                 //now lets scan for files that will override our files
677                 if(empty($this->config['disableRestrictedCopy']) && isset($installdefs['copy'])){
678                         foreach($installdefs['copy'] as $copy){
679                                 $from = str_replace('<basepath>', $this->pathToModule, $copy['from']);
680                                 $to = $copy['to'];
681                                 if(substr_count($from, '..')){
682                                         $this->issues['copy'][$from] = translate('ML_PATH_MAY_NOT_CONTAIN').' ".." -' . $from;
683                                 }
684                                 if(substr_count($to, '..')){
685                                         $this->issues['copy'][$to] = translate('ML_PATH_MAY_NOT_CONTAIN'). ' ".." -' . $to;
686                                 }
687                                 while(substr_count($from, '//')){
688                                         $from = str_replace('//', '/', $from);
689                                 }
690                                 while(substr_count($to, '//')){
691                                         $to = str_replace('//', '/', $to);
692                                 }
693                                 $this->scanCopy($from, $to);
694                         }
695                 }
696                 if(!empty($issues)){
697                         $this->issues['manifest'][$manifestPath] = $issues;
698                 }
699
700
701
702         }
703
704
705
706         /**
707          * Takes in where the file will is specified to be copied from and to
708          * and ensures that there is no official sugar file there. If the file exists it will check
709          * against the MD5 file list to see if Sugar Created the file
710          *
711          */
712         function scanCopy($from, $to){
713                                 //if the file doesn't exist for the $to then it is not overriding anything
714                                 if(!file_exists($to))return;
715                                 //if $to is a dir and $from is a file then make $to a full file path as well
716                                 if(is_dir($to) && is_file($from)){
717                                         if(substr($to,-1) === '/'){
718                                                 $to = substr($to, 0 , strlen($to) - 1);
719                                         }
720                                         $to .= '/'. basename($from);
721                                 }
722                                 //if the $to is a file and it is found in sugarFileExists then don't allow overriding it
723                                 if(is_file($to) && $this->sugarFileExists($to)){
724                                         $this->issues['copy'][$from] = translate('ML_OVERRIDE_CORE_FILES') . '(' . $to . ')';
725                                 }
726
727                                 if(is_dir($from)){
728                                         $d = dir($from);
729                                         while($e = $d->read()){
730                                                 if($e == '.' || $e == '..')continue;
731                                                 $this->scanCopy($from .'/'. $e, $to .'/' . $e);
732                                         }
733                                 }
734
735
736
737
738
739                         }
740
741
742         /**
743          *Main external function that takes in a path to a package and then scans
744          *that package's manifest for disabled actions and then it scans the PHP files
745          *for restricted function calls
746          *
747          */
748         public function scanPackage($path){
749                 $this->pathToModule = $path;
750                 $this->scanManifest($path . '/manifest.php');
751                 if(empty($this->config['disableFileScan'])){
752                         $this->scanDir($path);
753                 }
754         }
755
756         /**
757          *This function will take all issues of the current instance and print them to the screen
758          **/
759         public function displayIssues($package='Package'){
760                 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').
761 '</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>.'.
762 '<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>';
763
764
765                 foreach($this->issues as $type=>$issues){
766                         echo '<div class="error"><h2>'. ucfirst($type) .' ' .  translate('ML_ISSUES') . '</h2> </div>';
767                         echo '<div id="details' . $type . '" >';
768                         foreach($issues as $file=>$issue){
769                                 $file = str_replace($this->pathToModule . '/', '', $file);
770                                 echo '<div style="position:relative;left:10px"><b>' . $file . '</b></div><div style="position:relative;left:20px">';
771                                 if(is_array($issue)){
772                                         foreach($issue as $i){
773                                                 echo "$i<br>";
774                                         }
775                                 }else{
776                                         echo "$issue<br>";
777                                 }
778                                 echo "</div>";
779                         }
780                         echo '</div>';
781
782                 }
783                 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') . "\" />";
784
785         }
786
787         /**
788          * Lock config settings
789          */
790         public function lockConfig()
791         {
792             if(empty($this->config_hash)) {
793                 $this->config_hash = md5(serialize($GLOBALS['sugar_config']));
794             }
795         }
796
797         /**
798          * Check if config was modified. Return
799          * @param string $file
800          * @return array Errors if something wrong, false if no problems
801          */
802         public function checkConfig($file)
803         {
804             $config_hash_after = md5(serialize($GLOBALS['sugar_config']));
805             if($config_hash_after != $this->config_hash) {
806                 $this->issues['file'][$file] = array(translate('ML_CONFIG_OVERRIDE'));
807                 return $this->issues;
808             }
809             return false;
810         }
811
812 }
813
814 /**
815  * Load manifest file
816  * Outside of the class to isolate the context
817  * @param string $manifest_file
818  * @return array
819  */
820 function MSLoadManifest($manifest_file)
821 {
822         include( $manifest_file );
823         return array($manifest, $installdefs);
824 }
825
826 ?>