ChamiloRequirements.php 16 KB

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