ChamiloRequirements.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. require_once __DIR__.'/SymfonyRequirements.php';
  4. use Symfony\Component\Process\ProcessBuilder;
  5. use Symfony\Component\Intl\Intl;
  6. use Chamilo\InstallerBundle\Process\PhpExecutableFinder;
  7. /**
  8. * This class specifies all requirements and optional recommendations
  9. * that are necessary to run the Chamilo Application.
  10. */
  11. class ChamiloRequirements extends SymfonyRequirements
  12. {
  13. const REQUIRED_PHP_VERSION = '7.1';
  14. const REQUIRED_GD_VERSION = '2.0';
  15. const REQUIRED_CURL_VERSION = '7.0';
  16. const REQUIRED_ICU_VERSION = '3.8';
  17. const EXCLUDE_REQUIREMENTS_MASK = '/5\.3\.(3|4|8|16)|5\.4\.0/';
  18. /**
  19. * @inheritdoc
  20. */
  21. public function __construct()
  22. {
  23. parent::__construct();
  24. $phpVersion = phpversion();
  25. $gdVersion = defined('GD_VERSION') ? GD_VERSION : null;
  26. $curlVersion = function_exists('curl_version') ? curl_version() : null;
  27. $icuVersion = Intl::getIcuVersion();
  28. $this->addChamiloRequirement(
  29. version_compare($phpVersion, self::REQUIRED_PHP_VERSION, '>='),
  30. sprintf(
  31. 'PHP version must be at least %s (%s installed)',
  32. self::REQUIRED_PHP_VERSION,
  33. $phpVersion
  34. ),
  35. sprintf(
  36. 'You are running PHP version "<strong>%s</strong>", but Chamilo needs at least PHP "<strong>%s</strong>" to run.'.
  37. 'Before using Chamilo, upgrade your PHP installation, preferably to the latest version.',
  38. $phpVersion,
  39. self::REQUIRED_PHP_VERSION
  40. ),
  41. sprintf(
  42. 'Install PHP %s or newer (installed version is %s)',
  43. self::REQUIRED_PHP_VERSION,
  44. $phpVersion
  45. )
  46. );
  47. $this->addChamiloRequirement(
  48. null !== $gdVersion && version_compare(
  49. $gdVersion,
  50. self::REQUIRED_GD_VERSION,
  51. '>='
  52. ),
  53. 'GD extension must be at least '.self::REQUIRED_GD_VERSION,
  54. 'Install and enable the <strong>GD</strong> extension at least '.self::REQUIRED_GD_VERSION.' version'
  55. );
  56. $this->addChamiloRequirement(
  57. null !== $curlVersion && version_compare(
  58. $curlVersion['version'],
  59. self::REQUIRED_CURL_VERSION,
  60. '>='
  61. ),
  62. 'cURL extension must be at least '.self::REQUIRED_CURL_VERSION,
  63. 'Install and enable the <strong>cURL</strong> extension at least '.self::REQUIRED_CURL_VERSION.' version'
  64. );
  65. $this->addChamiloRequirement(
  66. function_exists('mb_strlen'),
  67. 'mb_strlen() should be available',
  68. 'Install and enable the <strong>mbstring</strong> extension.'
  69. );
  70. /*$this->addChamiloRequirement(
  71. function_exists('mcrypt_encrypt'),
  72. 'mcrypt_encrypt() should be available',
  73. 'Install and enable the <strong>Mcrypt</strong> extension.'
  74. );*/
  75. $this->addChamiloRequirement(
  76. class_exists('Locale'),
  77. 'intl extension should be available',
  78. 'Install and enable the <strong>intl</strong> extension.'
  79. );
  80. $this->addChamiloRequirement(
  81. null !== $icuVersion && version_compare(
  82. $icuVersion,
  83. self::REQUIRED_ICU_VERSION,
  84. '>='
  85. ),
  86. 'icu library must be at least '.self::REQUIRED_ICU_VERSION,
  87. 'Install and enable the <strong>icu</strong> library at least '.self::REQUIRED_ICU_VERSION.' version'
  88. );
  89. $extensions = $this->getExtensions();
  90. foreach ($extensions as $type) {
  91. $isOptional = $type == 'optional' ? true : false;
  92. foreach ($type as $extension => $url) {
  93. if (extension_loaded($extension)) {
  94. $this->addChamiloRequirement(
  95. extension_loaded($extension),
  96. "$extension extension should be available",
  97. "Install and enable the <strong>$extension</strong>
  98. extension.",
  99. $isOptional
  100. );
  101. }
  102. }
  103. }
  104. $this->addRecommendation(
  105. class_exists('SoapClient'),
  106. 'SOAP extension should be installed (API calls)',
  107. 'Install and enable the <strong>SOAP</strong> extension.'
  108. );
  109. // Windows specific checks
  110. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  111. $this->addRecommendation(
  112. function_exists('finfo_open'),
  113. 'finfo_open() should be available',
  114. 'Install and enable the <strong>Fileinfo</strong> extension.'
  115. );
  116. $this->addRecommendation(
  117. class_exists('COM'),
  118. 'COM extension should be installed',
  119. 'Install and enable the <strong>COM</strong> extension.'
  120. );
  121. }
  122. // Unix specific checks
  123. if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
  124. $this->addRequirement(
  125. $this->checkFileNameLength(),
  126. 'Cache folder should not be inside encrypted directory',
  127. 'Move <strong>app/cache</strong> folder outside encrypted directory.'
  128. );
  129. }
  130. // Web installer specific checks
  131. if ('cli' !== PHP_SAPI) {
  132. $output = $this->checkCliRequirements();
  133. $requirement = new CliRequirement(
  134. !$output,
  135. 'Requirements validation for PHP CLI',
  136. 'If you have multiple PHP versions installed, you need to configure CHAMILO_PHP_PATH variable with PHP binary path used by web server'
  137. );
  138. $requirement->setOutput($output);
  139. $this->add($requirement);
  140. }
  141. $baseDir = realpath(__DIR__.'/..');
  142. $mem = $this->getBytes(ini_get('memory_limit'));
  143. $this->addPhpIniRequirement(
  144. 'memory_limit',
  145. function ($cfgValue) use ($mem) {
  146. return $mem >= 256 * 1024 * 1024 || -1 == $mem;
  147. },
  148. false,
  149. 'memory_limit should be at least 256M',
  150. 'Set the "<strong>memory_limit</strong>" setting in php.ini<a href="#phpini">*</a> to at least "256M".'
  151. );
  152. $this->addChamiloRequirement(
  153. is_writable($baseDir.'/public/uploads'),
  154. 'public/uploads/ directory must be writable',
  155. 'Change the permissions of the "<strong>public/uploads/</strong>" directory so that the web server can write into it.'
  156. );
  157. $this->addChamiloRequirement(
  158. is_writable($baseDir.'/public/assetic'),
  159. 'public/assetic/ directory must be writable',
  160. 'Change the permissions of the "<strong>public/assetic/</strong>" directory so that the web server can write into it.'
  161. );
  162. $this->addChamiloRequirement(
  163. is_writable($baseDir.'/public/bundles'),
  164. 'public/bundles/ directory must be writable',
  165. 'Change the permissions of the "<strong>public/bundles/</strong>" directory so that the web server can write into it.'
  166. );
  167. /*$this->addChamiloRequirement(
  168. is_writable($baseDir . '/web/media'),
  169. 'web/media/ directory must be writable',
  170. 'Change the permissions of the "<strong>web/media/</strong>" directory so that the web server can write into it.'
  171. );
  172. /*$this->addChamiloRequirement(
  173. is_writable($baseDir . '/app/attachment'),
  174. 'app/attachment/ directory must be writable',
  175. 'Change the permissions of the "<strong>app/attachment/</strong>" directory so that the web server can write into it.'
  176. );*/
  177. if (is_dir($baseDir.'/public/js')) {
  178. $this->addChamiloRequirement(
  179. is_writable($baseDir.'/public/js'),
  180. 'public/js directory must be writable',
  181. 'Change the permissions of the "<strong>public/js</strong>" directory so that the web server can write into it.'
  182. );
  183. }
  184. if (is_dir($baseDir.'/public/css')) {
  185. $this->addChamiloRequirement(
  186. is_writable($baseDir.'/public/css'),
  187. 'public/css directory must be writable',
  188. 'Change the permissions of the "<strong>public/css</strong>" directory so that the web server can write into it.'
  189. );
  190. }
  191. if (!is_dir($baseDir.'/public/css') || !is_dir($baseDir.'/public/js')) {
  192. $this->addChamiloRequirement(
  193. is_writable($baseDir.'/web'),
  194. 'web directory must be writable',
  195. 'Change the permissions of the "<strong>public</strong>" directory so that the web server can write into it.'
  196. );
  197. }
  198. if (is_file($baseDir.'/app/config/parameters.yml')) {
  199. $this->addChamiloRequirement(
  200. is_writable($baseDir.'/app/config/parameters.yml'),
  201. 'app/config/parameters.yml file must be writable',
  202. 'Change the permissions of the "<strong>app/config/parameters.yml</strong>" file so that the web server can write into it.'
  203. );
  204. }
  205. }
  206. /**
  207. * @return array
  208. */
  209. private function getExtensions()
  210. {
  211. return
  212. array(
  213. 'required' => array(
  214. 'mysql' => array('url' => 'http://php.net/manual/en/book.mysql.php'),
  215. 'curl' => array('url' => 'http://php.net/manual/fr/book.curl.php'),
  216. 'zlib' => array('url' => 'http://php.net/manual/en/book.zlib.php'),
  217. 'pcre' => array('url' => 'http://php.net/manual/en/book.pcre.php'),
  218. 'xml' => array('url' => 'http://php.net/manual/en/book.xml.php'),
  219. 'mbstring' => array('url' => 'http://php.net/manual/en/book.mbstring.php'),
  220. 'iconv' => array('url' => 'http://php.net/manual/en/book.iconv.php'),
  221. 'intl' => array('url' => 'http://php.net/manual/en/book.intl.php'),
  222. 'gd' => array('url' => 'http://php.net/manual/en/book.image.php'),
  223. 'json' => array('url' => 'http://php.net/manual/en/book.json.php'),
  224. ),
  225. 'optional' => array(
  226. 'imagick' => array('url' => 'http://php.net/manual/en/book.imagick.php'),
  227. 'ldap' => array('url' => 'http://php.net/manual/en/book.ldap.php'),
  228. 'xapian' => array('url' => 'http://php.net/manual/en/book.xapian.php'),
  229. ),
  230. );
  231. }
  232. /**
  233. * Adds an Chamilo specific requirement.
  234. *
  235. * @param boolean $fulfilled Whether the requirement is fulfilled
  236. * @param string $testMessage The message for testing the requirement
  237. * @param string $helpHtml The help text formatted in HTML for resolving the problem
  238. * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
  239. * @param bool $optional
  240. */
  241. public function addChamiloRequirement(
  242. $fulfilled,
  243. $testMessage,
  244. $helpHtml,
  245. $helpText = null,
  246. $optional = false
  247. ) {
  248. $this->add(
  249. new ChamiloRequirement(
  250. $fulfilled,
  251. $testMessage,
  252. $helpHtml,
  253. $helpText,
  254. $optional
  255. )
  256. );
  257. }
  258. /**
  259. * Get the list of mandatory requirements (all requirements excluding PhpIniRequirement)
  260. *
  261. * @return array
  262. */
  263. public function getMandatoryRequirements()
  264. {
  265. return array_filter(
  266. $this->getRequirements(),
  267. function ($requirement) {
  268. return !($requirement instanceof PhpIniRequirement)
  269. && !($requirement instanceof ChamiloRequirement)
  270. && !($requirement instanceof CliRequirement);
  271. }
  272. );
  273. }
  274. /**
  275. * Get the list of PHP ini requirements
  276. *
  277. * @return array
  278. */
  279. public function getPhpIniRequirements()
  280. {
  281. return array_filter(
  282. $this->getRequirements(),
  283. function ($requirement) {
  284. return $requirement instanceof PhpIniRequirement;
  285. }
  286. );
  287. }
  288. /**
  289. * Get the list of Chamilo specific requirements
  290. *
  291. * @return array
  292. */
  293. public function getChamiloRequirements()
  294. {
  295. return array_filter(
  296. $this->getRequirements(),
  297. function ($requirement) {
  298. return $requirement instanceof ChamiloRequirement;
  299. }
  300. );
  301. }
  302. /**
  303. * @return array
  304. */
  305. public function getCliRequirements()
  306. {
  307. return array_filter(
  308. $this->getRequirements(),
  309. function ($requirement) {
  310. return $requirement instanceof CliRequirement;
  311. }
  312. );
  313. }
  314. /**
  315. * @param string $val
  316. * @return int
  317. */
  318. protected function getBytes($val)
  319. {
  320. if (empty($val)) {
  321. return 0;
  322. }
  323. preg_match('/([\-0-9]+)[\s]*([a-z]*)$/i', trim($val), $matches);
  324. if (isset($matches[1])) {
  325. $val = (int)$matches[1];
  326. }
  327. switch (strtolower($matches[2])) {
  328. case 'g':
  329. case 'gb':
  330. $val *= 1024;
  331. // no break
  332. case 'm':
  333. case 'mb':
  334. $val *= 1024;
  335. // no break
  336. case 'k':
  337. case 'kb':
  338. $val *= 1024;
  339. // no break
  340. }
  341. return (float)$val;
  342. }
  343. /**
  344. * {@inheritdoc}
  345. */
  346. public function getRequirements()
  347. {
  348. $requirements = parent::getRequirements();
  349. foreach ($requirements as $key => $requirement) {
  350. $testMessage = $requirement->getTestMessage();
  351. if (preg_match_all(
  352. self::EXCLUDE_REQUIREMENTS_MASK,
  353. $testMessage,
  354. $matches
  355. )) {
  356. unset($requirements[$key]);
  357. }
  358. }
  359. return $requirements;
  360. }
  361. /**
  362. * {@inheritdoc}
  363. */
  364. public function getRecommendations()
  365. {
  366. $recommendations = parent::getRecommendations();
  367. foreach ($recommendations as $key => $recommendation) {
  368. $testMessage = $recommendation->getTestMessage();
  369. if (preg_match_all(
  370. self::EXCLUDE_REQUIREMENTS_MASK,
  371. $testMessage,
  372. $matches
  373. )) {
  374. unset($recommendations[$key]);
  375. }
  376. }
  377. return $recommendations;
  378. }
  379. /**
  380. * @return bool
  381. */
  382. protected function checkNodeExists()
  383. {
  384. $nodeExists = new ProcessBuilder(array('node', '--version'));
  385. $nodeExists = $nodeExists->getProcess();
  386. if (isset($_SERVER['PATH'])) {
  387. $nodeExists->setEnv(array('PATH' => $_SERVER['PATH']));
  388. }
  389. $nodeExists->run();
  390. return $nodeExists->getErrorOutput() === null;
  391. }
  392. /**
  393. * @return bool
  394. */
  395. protected function checkFileNameLength()
  396. {
  397. $getConf = new ProcessBuilder(array('getconf', 'NAME_MAX', __DIR__));
  398. $getConf = $getConf->getProcess();
  399. if (isset($_SERVER['PATH'])) {
  400. $getConf->setEnv(array('PATH' => $_SERVER['PATH']));
  401. }
  402. $getConf->run();
  403. if ($getConf->getErrorOutput()) {
  404. // getconf not installed
  405. return true;
  406. }
  407. $fileLength = trim($getConf->getOutput());
  408. return $fileLength == 255;
  409. }
  410. /**
  411. * @return null|string
  412. */
  413. protected function checkCliRequirements()
  414. {
  415. $finder = new PhpExecutableFinder();
  416. $command = sprintf(
  417. '%s %schamilo-check.php',
  418. $finder->find(),
  419. __DIR__.DIRECTORY_SEPARATOR
  420. );
  421. return shell_exec($command);
  422. }
  423. }
  424. class ChamiloRequirement extends Requirement
  425. {
  426. }
  427. class CliRequirement extends Requirement
  428. {
  429. /**
  430. * @var string
  431. */
  432. protected $output;
  433. /**
  434. * @return string
  435. */
  436. public function getOutput()
  437. {
  438. return $this->output;
  439. }
  440. /**
  441. * @param string $output
  442. */
  443. public function setOutput($output)
  444. {
  445. $this->output = $output;
  446. }
  447. }