PageRenderTime 466ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 1ms

/SugarCRM/ModuleInstall/ModuleScanner.php

https://github.com/guolong/ggxw
PHP | 726 lines | 514 code | 89 blank | 123 comment | 68 complexity | ca20e34ae03ca921c68eb4195d4efe4b MD5 | raw file
Possible License(s): AGPL-3.0, LGPL-2.1, MPL-2.0-no-copyleft-exception
  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. class ModuleScanner{
  38. private $manifestMap = array(
  39. 'pre_execute'=>'pre_execute',
  40. 'install_mkdirs'=>'mkdir',
  41. 'install_copy'=>'copy',
  42. 'install_images'=>'image_dir',
  43. 'install_menus'=>'menu',
  44. 'install_userpage'=>'user_page',
  45. 'install_dashlets'=>'dashlets',
  46. 'install_administration'=>'administration',
  47. 'install_connectors'=>'connectors',
  48. 'install_vardefs'=>'vardefs',
  49. 'install_layoutdefs'=>'layoutdefs',
  50. 'install_layoutfields'=>'layoutfields',
  51. 'install_relationships'=>'relationships',
  52. 'install_languages'=>'language',
  53. 'install_logichooks'=>'logic_hooks',
  54. 'post_execute'=>'post_execute',
  55. );
  56. private $blackListExempt = array();
  57. private $classBlackListExempt = array();
  58. private $validExt = array('png', 'gif', 'jpg', 'css', 'js', 'php', 'txt', 'html', 'htm', 'tpl', 'pdf', 'md5', 'xml');
  59. private $classBlackList = array(
  60. // Class names specified here must be in lowercase as the implementation
  61. // of the tokenizer converts all tokens to lowercase.
  62. 'reflection',
  63. 'reflectionclass',
  64. 'reflectionzendextension',
  65. 'reflectionextension',
  66. 'reflectionfunction',
  67. 'reflectionfunctionabstract',
  68. 'reflectionmethod',
  69. 'reflectionobject',
  70. 'reflectionparameter',
  71. 'reflectionproperty',
  72. 'reflector',
  73. 'reflectionexception',
  74. 'lua',
  75. );
  76. private $blackList = array(
  77. 'popen',
  78. 'proc_open',
  79. 'escapeshellarg',
  80. 'escapeshellcmd',
  81. 'proc_close',
  82. 'proc_get_status',
  83. 'proc_nice',
  84. 'passthru',
  85. 'clearstatcache',
  86. 'disk_free_space',
  87. 'disk_total_space',
  88. 'diskfreespace',
  89. 'fclose',
  90. 'feof',
  91. 'fflush',
  92. 'fgetc',
  93. 'fgetcsv',
  94. 'fgets',
  95. 'fgetss',
  96. 'file_exists',
  97. 'file_get_contents',
  98. 'filesize',
  99. 'filetype',
  100. 'flock',
  101. 'fnmatch',
  102. 'fpassthru',
  103. 'fputcsv',
  104. 'fputs',
  105. 'fread',
  106. 'fscanf',
  107. 'fseek',
  108. 'fstat',
  109. 'ftell',
  110. 'ftruncate',
  111. 'fwrite',
  112. 'glob',
  113. 'is_dir',
  114. 'is_file',
  115. 'is_link',
  116. 'is_readable',
  117. 'is_uploaded_file',
  118. 'parse_ini_string',
  119. 'pathinfo',
  120. 'pclose',
  121. 'readfile',
  122. 'readlink',
  123. 'realpath_cache_get',
  124. 'realpath_cache_size',
  125. 'realpath',
  126. 'rewind',
  127. 'set_file_buffer',
  128. 'tmpfile',
  129. 'umask',
  130. 'ini_set',
  131. 'eval',
  132. 'exec',
  133. 'system',
  134. 'shell_exec',
  135. 'passthru',
  136. 'chgrp',
  137. 'chmod',
  138. 'chwown',
  139. 'file_put_contents',
  140. 'file',
  141. 'fileatime',
  142. 'filectime',
  143. 'filegroup',
  144. 'fileinode',
  145. 'filemtime',
  146. 'fileowner',
  147. 'fileperms',
  148. 'fopen',
  149. 'is_executable',
  150. 'is_writable',
  151. 'is_writeable',
  152. 'lchgrp',
  153. 'lchown',
  154. 'linkinfo',
  155. 'lstat',
  156. 'mkdir',
  157. 'mkdir_recursive',
  158. 'parse_ini_file',
  159. 'rmdir',
  160. 'rmdir_recursive',
  161. 'stat',
  162. 'tempnam',
  163. 'touch',
  164. 'unlink',
  165. 'getimagesize',
  166. 'call_user_func',
  167. 'call_user_func_array',
  168. 'create_function',
  169. //mutliple files per function call
  170. 'copy',
  171. 'copy_recursive',
  172. 'link',
  173. 'rename',
  174. 'symlink',
  175. 'move_uploaded_file',
  176. 'chdir',
  177. 'chroot',
  178. 'create_cache_directory',
  179. 'mk_temp_dir',
  180. 'write_array_to_file',
  181. 'write_encoded_file',
  182. 'create_custom_directory',
  183. 'sugar_rename',
  184. 'sugar_chown',
  185. 'sugar_fopen',
  186. 'sugar_mkdir',
  187. 'sugar_file_put_contents',
  188. 'sugar_chgrp',
  189. 'sugar_chmod',
  190. 'sugar_touch',
  191. // Functions that have callbacks can circumvent our security measures.
  192. // List retrieved through PHP's XML documentation, and running the
  193. // following script in the reference directory:
  194. // grep -R callable . | grep -v \.svn | grep methodparam | cut -d: -f1 | sort -u | cut -d"." -f2 | sed 's/\-/\_/g' | cut -d"/" -f4
  195. // AMQPQueue
  196. 'consume',
  197. // PHP internal - arrays
  198. 'array_diff_uassoc',
  199. 'array_diff_ukey',
  200. 'array_filter',
  201. 'array_intersect_uassoc',
  202. 'array_intersect_ukey',
  203. 'array_map',
  204. 'array_reduce',
  205. 'array_udiff_assoc',
  206. 'array_udiff_uassoc',
  207. 'array_udiff',
  208. 'array_uintersect_assoc',
  209. 'array_uintersect_uassoc',
  210. 'array_uintersect',
  211. 'array_walk_recursive',
  212. 'array_walk',
  213. 'uasort',
  214. 'uksort',
  215. 'usort',
  216. // EIO functions that accept callbacks.
  217. 'eio_busy',
  218. 'eio_chmod',
  219. 'eio_chown',
  220. 'eio_close',
  221. 'eio_custom',
  222. 'eio_dup2',
  223. 'eio_fallocate',
  224. 'eio_fchmod',
  225. 'eio_fchown',
  226. 'eio_fdatasync',
  227. 'eio_fstat',
  228. 'eio_fstatvfs',
  229. 'eio_fsync',
  230. 'eio_ftruncate',
  231. 'eio_futime',
  232. 'eio_grp',
  233. 'eio_link',
  234. 'eio_lstat',
  235. 'eio_mkdir',
  236. 'eio_mknod',
  237. 'eio_nop',
  238. 'eio_open',
  239. 'eio_read',
  240. 'eio_readahead',
  241. 'eio_readdir',
  242. 'eio_readlink',
  243. 'eio_realpath',
  244. 'eio_rename',
  245. 'eio_rmdir',
  246. 'eio_sendfile',
  247. 'eio_stat',
  248. 'eio_statvfs',
  249. 'eio_symlink',
  250. 'eio_sync_file_range',
  251. 'eio_sync',
  252. 'eio_syncfs',
  253. 'eio_truncate',
  254. 'eio_unlink',
  255. 'eio_utime',
  256. 'eio_write',
  257. // PHP internal - error functions
  258. 'set_error_handler',
  259. 'set_exception_handler',
  260. // Forms Data Format functions
  261. 'fdf_enum_values',
  262. // PHP internal - function handling
  263. 'call_user_func_array',
  264. 'call_user_func',
  265. 'forward_static_call_array',
  266. 'forward_static_call',
  267. 'register_shutdown_function',
  268. 'register_tick_function',
  269. // Gearman
  270. 'setclientcallback',
  271. 'setcompletecallback',
  272. 'setdatacallback',
  273. 'setexceptioncallback',
  274. 'setfailcallback',
  275. 'setstatuscallback',
  276. 'setwarningcallback',
  277. 'setworkloadcallback',
  278. 'addfunction',
  279. // Firebird/InterBase
  280. 'ibase_set_event_handler',
  281. // LDAP
  282. 'ldap_set_rebind_proc',
  283. // LibXML
  284. 'libxml_set_external_entity_loader',
  285. // Mailparse functions
  286. 'mailparse_msg_extract_part_file',
  287. 'mailparse_msg_extract_part',
  288. 'mailparse_msg_extract_whole_part_file',
  289. // Memcache(d) functions
  290. 'addserver',
  291. 'setserverparams',
  292. 'get',
  293. 'getbykey',
  294. 'getdelayed',
  295. 'getdelayedbykey',
  296. // MySQLi
  297. 'set_local_infile_handler',
  298. // PHP internal - network functions
  299. 'header_register_callback',
  300. // Newt
  301. 'newt_entry_set_filter',
  302. 'newt_set_suspend_callback',
  303. // OAuth
  304. 'consumerhandler',
  305. 'timestampnoncehandler',
  306. 'tokenhandler',
  307. // PHP internal - output control
  308. 'ob_start',
  309. // PHP internal - PCNTL
  310. 'pcntl_signal',
  311. // PHP internal - PCRE
  312. 'preg_replace_callback',
  313. // SQLite
  314. 'sqlitecreateaggregate',
  315. 'sqlitecreatefunction',
  316. 'sqlite_create_aggregate',
  317. 'sqlite_create_function',
  318. // RarArchive
  319. 'open',
  320. // Readline
  321. 'readline_callback_handler_install',
  322. 'readline_completion_function',
  323. // PHP internal - session handling
  324. 'session_set_save_handler',
  325. // PHP internal - SPL
  326. 'construct',
  327. 'iterator_apply',
  328. 'spl_autoload_register',
  329. // Sybase
  330. 'sybase_set_message_handler',
  331. // PHP internal - variable handling
  332. 'is_callable',
  333. // XML Parser
  334. 'xml_set_character_data_handler',
  335. 'xml_set_default_handler',
  336. 'xml_set_element_handler',
  337. 'xml_set_end_namespace_decl_handler',
  338. 'xml_set_external_entity_ref_handler',
  339. 'xml_set_notation_decl_handler',
  340. 'xml_set_processing_instruction_handler',
  341. 'xml_set_start_namespace_decl_handler',
  342. 'xml_set_unparsed_entity_decl_handler',
  343. );
  344. public function printToWiki(){
  345. echo "'''Default Extensions'''<br>";
  346. foreach($this->validExt as $b){
  347. echo '#' . $b . '<br>';
  348. }
  349. echo "'''Default Black Listed Functions'''<br>";
  350. foreach($this->blackList as $b){
  351. echo '#' . $b . '<br>';
  352. }
  353. }
  354. public function __construct(){
  355. if(!empty($GLOBALS['sugar_config']['moduleInstaller']['blackListExempt'])){
  356. $this->blackListExempt = array_merge($this->blackListExempt, $GLOBALS['sugar_config']['moduleInstaller']['blackListExempt']);
  357. }
  358. if(!empty($GLOBALS['sugar_config']['moduleInstaller']['blackList'])){
  359. $this->blackList = array_merge($this->blackList, $GLOBALS['sugar_config']['moduleInstaller']['blackList']);
  360. }
  361. if(!empty($GLOBALS['sugar_config']['moduleInstaller']['classBlackListExempt'])){
  362. $this->classBlackListExempt = array_merge($this->classBlackListExempt, $GLOBALS['sugar_config']['moduleInstaller']['classBlackListExempt']);
  363. }
  364. if(!empty($GLOBALS['sugar_config']['moduleInstaller']['classBlackList'])){
  365. $this->classBlackList = array_merge($this->classBlackList, $GLOBALS['sugar_config']['moduleInstaller']['classBlackList']);
  366. }
  367. if(!empty($GLOBALS['sugar_config']['moduleInstaller']['validExt'])){
  368. $this->validExt = array_merge($this->validExt, $GLOBALS['sugar_config']['moduleInstaller']['validExt']);
  369. }
  370. }
  371. private $issues = array();
  372. private $pathToModule = '';
  373. /**
  374. *returns a list of issues
  375. */
  376. public function getIssues(){
  377. return $this->issues;
  378. }
  379. /**
  380. *returns true or false if any issues were found
  381. */
  382. public function hasIssues(){
  383. return !empty($this->issues);
  384. }
  385. /**
  386. *Ensures that a file has a valid extension
  387. */
  388. private function isValidExtension($file){
  389. $file = strtolower($file);
  390. $extPos = strrpos($file, '.');
  391. //make sure they don't override the files.md5
  392. if($extPos === false || $file == 'files.md5')return false;
  393. $ext = substr($file, $extPos + 1);
  394. return in_array($ext, $this->validExt);
  395. }
  396. /**
  397. *Scans a directory and calls on scan file for each file
  398. **/
  399. public function scanDir($path){
  400. static $startPath = '';
  401. if(empty($startPath))$startPath = $path;
  402. if(!is_dir($path))return false;
  403. $d = dir($path);
  404. while($e = $d->read()){
  405. $next = $path . '/' . $e;
  406. if(is_dir($next)){
  407. if(substr($e, 0, 1) == '.')continue;
  408. $this->scanDir($next);
  409. }else{
  410. $issues = $this->scanFile($next);
  411. }
  412. }
  413. return true;
  414. }
  415. /**
  416. * Check if the file contents looks like PHP
  417. * @param string $contents File contents
  418. * @return boolean
  419. */
  420. public function isPHPFile($contents)
  421. {
  422. if(stripos($contents, '<?php') !== false) return true;
  423. for($tag=0;($tag = stripos($contents, '<?', $tag)) !== false;$tag++) {
  424. if(strncasecmp(substr($contents, $tag, 13), '<?xml version', 13) == 0) {
  425. // <?xml version is OK, skip it
  426. $tag++;
  427. continue;
  428. }
  429. // found <?, it's PHP
  430. return true;
  431. }
  432. return false;
  433. }
  434. /**
  435. * 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
  436. * $var() and ` are always prevented then whatever is in the blacklist.
  437. * It will also ensure that all files are of valid extension types
  438. *
  439. */
  440. public function scanFile($file){
  441. $issues = array();
  442. if(!$this->isValidExtension($file)){
  443. $issues[] = translate('ML_INVALID_EXT');
  444. $this->issues['file'][$file] = $issues;
  445. return $issues;
  446. }
  447. $contents = file_get_contents($file);
  448. if(!$this->isPHPFile($contents)) return $issues;
  449. $tokens = @token_get_all($contents);
  450. $checkFunction = false;
  451. $possibleIssue = '';
  452. $lastToken = false;
  453. foreach($tokens as $index=>$token){
  454. if(is_string($token[0])){
  455. switch($token[0]){
  456. case '`':
  457. $issues['backtick'] = translate('ML_INVALID_FUNCTION') . " '`'";
  458. case '(':
  459. if($checkFunction)$issues[] = $possibleIssue;
  460. break;
  461. }
  462. $checkFunction = false;
  463. $possibleIssue = '';
  464. }else{
  465. $token['_msi'] = token_name($token[0]);
  466. switch($token[0]){
  467. case T_WHITESPACE: continue;
  468. case T_EVAL:
  469. if(in_array('eval', $this->blackList) && !in_array('eval', $this->blackListExempt))
  470. $issues[]= translate('ML_INVALID_FUNCTION') . ' eval()';
  471. break;
  472. case T_STRING:
  473. $token[1] = strtolower($token[1]);
  474. if($lastToken !== false && $lastToken[0] == T_NEW) {
  475. if(!in_array($token[1], $this->classBlackList))break;
  476. if(in_array($token[1], $this->classBlackListExempt))break;
  477. } elseif ($token[0] == T_DOUBLE_COLON) {
  478. if(!in_array($lastToken[1], $this->classBlackList))break;
  479. if(in_array($lastToken[1], $this->classBlackListExempt))break;
  480. } else {
  481. if(!in_array($token[1], $this->blackList))break;
  482. if(in_array($token[1], $this->blackListExempt))break;
  483. if ($lastToken !== false &&
  484. ($lastToken[0] == T_OBJECT_OPERATOR || $lastToken[0] == T_DOUBLE_COLON))
  485. {
  486. break;
  487. }
  488. }
  489. case T_VARIABLE:
  490. $checkFunction = true;
  491. $possibleIssue = translate('ML_INVALID_FUNCTION') . ' ' . $token[1] . '()';
  492. break;
  493. default:
  494. $checkFunction = false;
  495. $possibleIssue = '';
  496. }
  497. if ($token[0] != T_WHITESPACE)
  498. {
  499. $lastToken = $token;
  500. }
  501. }
  502. }
  503. if(!empty($issues)){
  504. $this->issues['file'][$file] = $issues;
  505. }
  506. return $issues;
  507. }
  508. /*
  509. * checks files.md5 file to see if the file is from sugar
  510. * ONLY WORKS ON FILES
  511. */
  512. public function sugarFileExists($path){
  513. static $md5 = array();
  514. if(empty($md5) && file_exists('files.md5'))
  515. {
  516. include('files.md5');
  517. $md5 = $md5_string;
  518. }
  519. if(isset($md5['./' . $path]))return true;
  520. }
  521. /**
  522. *This function will scan the Manifest for disabled actions specified in $GLOBALS['sugar_config']['moduleInstaller']['disableActions']
  523. *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
  524. */
  525. public function scanManifest($manifestPath){
  526. $issues = array();
  527. if(!file_exists($manifestPath)){
  528. $this->issues['manifest'][$manifestPath] = translate('ML_NO_MANIFEST');
  529. return $issues;
  530. }
  531. $fileIssues = $this->scanFile($manifestPath);
  532. //if the manifest contains malicious code do not open it
  533. if(!empty($fileIssues)){
  534. return $fileIssues;
  535. }
  536. include($manifestPath);
  537. //scan for disabled actions
  538. if(isset($GLOBALS['sugar_config']['moduleInstaller']['disableActions'])){
  539. foreach($GLOBALS['sugar_config']['moduleInstaller']['disableActions'] as $action){
  540. if(isset($installdefs[$this->manifestMap[$action]])){
  541. $issues[] = translate('ML_INVALID_ACTION_IN_MANIFEST') . $this->manifestMap[$action];
  542. }
  543. }
  544. }
  545. //now lets scan for files that will override our files
  546. if(empty($GLOBALS['sugar_config']['moduleInstaller']['disableRestrictedCopy']) && isset($installdefs['copy'])){
  547. foreach($installdefs['copy'] as $copy){
  548. $from = str_replace('<basepath>', $this->pathToModule, $copy['from']);
  549. $to = $copy['to'];
  550. if(substr_count($from, '..')){
  551. $this->issues['copy'][$from] = translate('ML_PATH_MAY_NOT_CONTAIN').' ".." -' . $from;
  552. }
  553. if(substr_count($to, '..')){
  554. $this->issues['copy'][$to] = translate('ML_PATH_MAY_NOT_CONTAIN'). ' ".." -' . $to;
  555. }
  556. while(substr_count($from, '//')){
  557. $from = str_replace('//', '/', $from);
  558. }
  559. while(substr_count($to, '//')){
  560. $to = str_replace('//', '/', $to);
  561. }
  562. $this->scanCopy($from, $to);
  563. }
  564. }
  565. if(!empty($issues)){
  566. $this->issues['manifest'][$manifestPath] = $issues;
  567. }
  568. }
  569. /**
  570. * Takes in where the file will is specified to be copied from and to
  571. * and ensures that there is no official sugar file there. If the file exists it will check
  572. * against the MD5 file list to see if Sugar Created the file
  573. *
  574. */
  575. function scanCopy($from, $to){
  576. //if the file doesn't exist for the $to then it is not overriding anything
  577. if(!file_exists($to))return;
  578. //if $to is a dir and $from is a file then make $to a full file path as well
  579. if(is_dir($to) && is_file($from)){
  580. if(substr($to,-1) === '/'){
  581. $to = substr($to, 0 , strlen($to) - 1);
  582. }
  583. $to .= '/'. basename($from);
  584. }
  585. //if the $to is a file and it is found in sugarFileExists then don't allow overriding it
  586. if(is_file($to) && $this->sugarFileExists($to)){
  587. $this->issues['copy'][$from] = translate('ML_OVERRIDE_CORE_FILES') . '(' . $to . ')';
  588. }
  589. if(is_dir($from)){
  590. $d = dir($from);
  591. while($e = $d->read()){
  592. if($e == '.' || $e == '..')continue;
  593. $this->scanCopy($from .'/'. $e, $to .'/' . $e);
  594. }
  595. }
  596. }
  597. /**
  598. *Main external function that takes in a path to a package and then scans
  599. *that package's manifest for disabled actions and then it scans the PHP files
  600. *for restricted function calls
  601. *
  602. */
  603. public function scanPackage($path){
  604. $this->pathToModule = $path;
  605. $this->scanManifest($path . '/manifest.php');
  606. if(empty($GLOBALS['sugar_config']['moduleInstaller']['disableFileScan'])){
  607. $this->scanDir($path);
  608. }
  609. }
  610. /**
  611. *This function will take all issues of the current instance and print them to the screen
  612. **/
  613. public function displayIssues($package='Package'){
  614. 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').
  615. '</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>.'.
  616. '<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>';
  617. foreach($this->issues as $type=>$issues){
  618. echo '<div class="error"><h2>'. ucfirst($type) .' ' . translate('ML_ISSUES') . '</h2> </div>';
  619. echo '<div id="details' . $type . '" >';
  620. foreach($issues as $file=>$issue){
  621. $file = str_replace($this->pathToModule . '/', '', $file);
  622. echo '<div style="position:relative;left:10px"><b>' . $file . '</b></div><div style="position:relative;left:20px">';
  623. if(is_array($issue)){
  624. foreach($issue as $i){
  625. echo "$i<br>";
  626. }
  627. }else{
  628. echo "$issue<br>";
  629. }
  630. echo "</div>";
  631. }
  632. echo '</div>';
  633. }
  634. 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') . "\" />";
  635. }
  636. }
  637. ?>