lib_install.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. <?php
  2. declare(strict_types=1);
  3. FreshRSS_SystemConfiguration::register('default_system', join_path(FRESHRSS_PATH, 'config.default.php'));
  4. FreshRSS_UserConfiguration::register('default_user', join_path(FRESHRSS_PATH, 'config-user.default.php'));
  5. /**
  6. * GMP is needed to convert entry identifiers to the Google Reader format on 32-bit PHP.
  7. *
  8. * @return 'ok'|'ko'|null
  9. */
  10. function gmpRequirementStatus(int $integerSize = PHP_INT_SIZE, ?bool $gmpLoaded = null): ?string {
  11. if ($integerSize >= 8) {
  12. return null;
  13. }
  14. return ($gmpLoaded ?? extension_loaded('gmp')) ? 'ok' : 'ko';
  15. }
  16. /**
  17. * @param 'mysql'|'pgsql'|'sqlite'|'' $dbType
  18. * @return array<string,'ok'|'ko'|'warn'>
  19. */
  20. function checkRequirements(string $dbType = '', bool $checkPhp = true, bool $checkFiles = true): array {
  21. $php = version_compare(PHP_VERSION, FRESHRSS_MIN_PHP_VERSION) >= 0;
  22. $curl = extension_loaded('curl'); // TODO: We actually require cURL >= 7.52 for CURLPROXY_HTTPS
  23. $pdo_mysql = extension_loaded('pdo_mysql');
  24. $pdo_sqlite = extension_loaded('pdo_sqlite');
  25. $pdo_pgsql = extension_loaded('pdo_pgsql');
  26. switch ($dbType) {
  27. case 'mysql':
  28. $pdo_sqlite = $pdo_pgsql = true;
  29. $pdo = $pdo_mysql;
  30. break;
  31. case 'sqlite':
  32. $pdo_mysql = $pdo_pgsql = true;
  33. $pdo = $pdo_sqlite;
  34. break;
  35. case 'pgsql':
  36. $pdo_mysql = $pdo_sqlite = true;
  37. $pdo = $pdo_pgsql;
  38. break;
  39. case '':
  40. $pdo = $pdo_mysql || $pdo_sqlite || $pdo_pgsql;
  41. break;
  42. default:
  43. throw new InvalidArgumentException('Invalid database type!');
  44. }
  45. $pdo &= class_exists('PDO');
  46. $pcre = extension_loaded('pcre');
  47. $ctype = extension_loaded('ctype');
  48. $fileinfo = extension_loaded('fileinfo');
  49. $dom = class_exists('DOMDocument');
  50. $xml = function_exists('xml_parser_create');
  51. $json = function_exists('json_encode');
  52. $intl = extension_loaded('intl');
  53. $mbstring = extension_loaded('mbstring');
  54. $zip = extension_loaded('zip');
  55. $gmp = gmpRequirementStatus();
  56. $data = is_dir(DATA_PATH) && touch(DATA_PATH . '/index.html'); // is_writable() is not reliable for a folder on NFS
  57. $cache = is_dir(CACHE_PATH) && touch(CACHE_PATH . '/index.html');
  58. $tmp = is_dir(TMP_PATH) && is_writable(TMP_PATH);
  59. $users = is_dir(USERS_PATH) && touch(USERS_PATH . '/index.html');
  60. $favicons = is_dir(DATA_PATH) && touch(DATA_PATH . '/favicons/index.html');
  61. $tokens = is_dir(DATA_PATH) && touch(DATA_PATH . '/tokens/index.html');
  62. // An unknown or incorrect document root might web-expose folders such as `./data`.
  63. // An empty `DOCUMENT_ROOT` outside of a Web server context (e.g. CLI, cron) is ignored because `realpath('')` resolves to the current directory.
  64. $documentRoot = $_SERVER['DOCUMENT_ROOT'] ?? null;
  65. $docRootPath = is_string($documentRoot) && $documentRoot !== '' ? realpath($documentRoot) : false;
  66. $docRootOk = $documentRoot === '' || ($docRootPath !== false && $docRootPath === realpath(PUBLIC_PATH));
  67. $result = [];
  68. if ($checkPhp) {
  69. $result += [
  70. 'php' => $php ? 'ok' : 'ko',
  71. 'pdo' => $pdo ? 'ok' : 'ko',
  72. 'pdo-sqlite' => $pdo_sqlite ? 'ok' : ($dbType === 'sqlite' ? 'ko' : 'warn'),
  73. 'pdo-pgsql' => ($dbType === 'pgsql' && !$pdo_pgsql) ? 'ko' : null,
  74. 'pdo-mysql' => ($dbType === 'mysql' && !$pdo_mysql) ? 'ko' : null,
  75. 'dom' => $dom ? 'ok' : 'ko',
  76. 'xml' => $xml ? 'ok' : 'ko',
  77. 'curl' => $curl ? 'ok' : 'ko',
  78. 'pcre' => $pcre ? 'ok' : 'ko',
  79. 'ctype' => $ctype ? 'ok' : 'ko',
  80. 'json' => $json ? 'ok' : 'ko',
  81. 'mbstring' => $mbstring ? 'ok' : 'warn',
  82. 'intl' => $intl ? 'ok' : 'warn',
  83. 'zip' => $zip ? 'ok' : 'warn',
  84. 'fileinfo' => $fileinfo ? 'ok' : 'warn',
  85. 'gmp' => $gmp,
  86. ];
  87. }
  88. if ($checkFiles) {
  89. $result += [
  90. 'data' => $data ? 'ok' : 'ko',
  91. 'cache' => $cache ? 'ok' : 'ko',
  92. 'tmp' => $tmp ? 'ok' : 'ko',
  93. 'users' => $users ? 'ok' : 'ko',
  94. 'favicons' => $favicons ? 'ok' : 'ko',
  95. 'tokens' => $tokens ? 'ok' : 'ko',
  96. 'docroot' => $docRootOk ? 'ok' : 'warn',
  97. ];
  98. }
  99. if ($checkPhp && $checkFiles) {
  100. $result['all'] = $php && $curl && $json && $pdo && $pcre && $ctype && $dom && $xml && $gmp !== 'ko' &&
  101. $data && $cache && $tmp && $users && $favicons && $tokens ? 'ok' : 'ko';
  102. }
  103. $result = array_filter($result, static fn($v) => $v !== null);
  104. return $result;
  105. }
  106. function generateSalt(): string {
  107. return hash('sha256', uniqid(more_entropy: true) . implode('', stat(__FILE__) ?: []) . random_bytes(32));
  108. }
  109. /**
  110. * @throws FreshRSS_Context_Exception
  111. */
  112. function initDb(): string {
  113. $db = FreshRSS_Context::systemConf()->db;
  114. if (empty($db['pdo_options'])) {
  115. $db['pdo_options'] = [];
  116. }
  117. $db['pdo_options'][PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION;
  118. FreshRSS_Context::systemConf()->db = $db; //TODO: Remove this Minz limitation "Indirect modification of overloaded property"
  119. if (empty($db['type'])) {
  120. $db['type'] = 'sqlite';
  121. }
  122. //Attempt to auto-create database if it does not already exist
  123. if ($db['type'] !== 'sqlite') {
  124. Minz_ModelPdo::$usesSharedPdo = false;
  125. $dbBase = $db['base'] ?? '';
  126. //For first connection, use default database for PostgreSQL, empty database for MySQL / MariaDB:
  127. $db['base'] = $db['type'] === 'pgsql' ? 'postgres' : '';
  128. FreshRSS_Context::systemConf()->db = $db;
  129. try {
  130. //First connection without database name to create the database
  131. $databaseDAO = FreshRSS_Factory::createDatabaseDAO();
  132. } catch (Exception $ex) {
  133. $databaseDAO = null;
  134. }
  135. //Restore final database parameters for auto-creation and for future connections
  136. $db['base'] = $dbBase;
  137. FreshRSS_Context::systemConf()->db = $db;
  138. if ($databaseDAO != null) {
  139. //Perform database auto-creation
  140. $databaseDAO->create();
  141. }
  142. }
  143. //New connection with the database name
  144. $databaseDAO = FreshRSS_Factory::createDatabaseDAO();
  145. Minz_ModelPdo::$usesSharedPdo = true;
  146. return $databaseDAO->testConnection();
  147. }
  148. function setupMigrations(): bool {
  149. $migrations_path = APP_PATH . '/migrations';
  150. $migrations_version_path = DATA_PATH . '/applied_migrations.txt';
  151. $migrator = new Minz_Migrator($migrations_path);
  152. $versions = implode("\n", $migrator->versions());
  153. return @file_put_contents($migrations_version_path, $versions) !== false;
  154. }