Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

280 lines
8.9KB

  1. <?php
  2. namespace Friendica\Core;
  3. use Friendica\Database\DBA;
  4. use Friendica\Database\DBStructure;
  5. use Friendica\Util\Strings;
  6. class Update
  7. {
  8. const SUCCESS = 0;
  9. const FAILED = 1;
  10. /**
  11. * @brief Function to check if the Database structure needs an update.
  12. *
  13. * @param string $basePath The base path of this application
  14. * @param boolean $via_worker boolean Is the check run via the worker?
  15. * @throws \Friendica\Network\HTTPException\InternalServerErrorException
  16. */
  17. public static function check($basePath, $via_worker)
  18. {
  19. if (!DBA::connected()) {
  20. return;
  21. }
  22. $build = Config::get('system', 'build');
  23. if (empty($build)) {
  24. Config::set('system', 'build', DB_UPDATE_VERSION - 1);
  25. $build = DB_UPDATE_VERSION - 1;
  26. }
  27. // We don't support upgrading from very old versions anymore
  28. if ($build < NEW_UPDATE_ROUTINE_VERSION) {
  29. die('You try to update from a version prior to database version 1170. The direct upgrade path is not supported. Please update to version 3.5.4 before updating to this version.');
  30. }
  31. if ($build < DB_UPDATE_VERSION) {
  32. // When we cannot execute the database update via the worker, we will do it directly
  33. if (!Worker::add(PRIORITY_CRITICAL, 'DBUpdate') && $via_worker) {
  34. self::run($basePath);
  35. }
  36. }
  37. }
  38. /**
  39. * Automatic database updates
  40. *
  41. * @param string $basePath The base path of this application
  42. * @param bool $force Force the Update-Check even if the lock is set
  43. * @param bool $verbose Run the Update-Check verbose
  44. * @param bool $sendMail Sends a Mail to the administrator in case of success/failure
  45. *
  46. * @return string Empty string if the update is successful, error messages otherwise
  47. * @throws \Friendica\Network\HTTPException\InternalServerErrorException
  48. */
  49. public static function run($basePath, $force = false, $verbose = false, $sendMail = true)
  50. {
  51. // In force mode, we release the dbupdate lock first
  52. // Necessary in case of an stuck update
  53. if ($force) {
  54. Lock::release('dbupdate');
  55. }
  56. $build = Config::get('system', 'build');
  57. if (empty($build) || ($build > DB_UPDATE_VERSION)) {
  58. $build = DB_UPDATE_VERSION - 1;
  59. Config::set('system', 'build', $build);
  60. }
  61. if ($build != DB_UPDATE_VERSION) {
  62. require_once 'update.php';
  63. $stored = intval($build);
  64. $current = intval(DB_UPDATE_VERSION);
  65. if ($stored < $current) {
  66. Config::load('database');
  67. Logger::log('Update from \'' . $stored . '\' to \'' . $current . '\' - starting', Logger::DEBUG);
  68. // Compare the current structure with the defined structure
  69. // If the Lock is acquired, never release it automatically to avoid double updates
  70. if (Lock::acquire('dbupdate', 120, Cache::INFINITE)) {
  71. // run the pre_update_nnnn functions in update.php
  72. for ($x = $stored + 1; $x <= $current; $x++) {
  73. $r = self::runUpdateFunction($x, 'pre_update');
  74. if (!$r) {
  75. break;
  76. }
  77. }
  78. // update the structure in one call
  79. $retval = DBStructure::update($basePath, $verbose, true);
  80. if ($retval) {
  81. if ($sendMail) {
  82. self::updateFailed(
  83. DB_UPDATE_VERSION,
  84. $retval
  85. );
  86. }
  87. Logger::log('ERROR: Update from \'' . $stored . '\' to \'' . $current . '\' - failed: ' - $retval, Logger::ALL);
  88. Lock::release('dbupdate');
  89. return $retval;
  90. } else {
  91. Config::set('database', 'last_successful_update', $current);
  92. Config::set('database', 'last_successful_update_time', time());
  93. Logger::log('Update from \'' . $stored . '\' to \'' . $current . '\' - finished', Logger::DEBUG);
  94. }
  95. // run the update_nnnn functions in update.php
  96. for ($x = $stored + 1; $x <= $current; $x++) {
  97. $r = self::runUpdateFunction($x, 'update');
  98. if (!$r) {
  99. break;
  100. }
  101. }
  102. Logger::log('Update from \'' . $stored . '\' to \'' . $current . '\' - successful', Logger::DEBUG);
  103. if ($sendMail) {
  104. self::updateSuccessfull($stored, $current);
  105. }
  106. Lock::release('dbupdate');
  107. }
  108. }
  109. } elseif ($force) {
  110. DBStructure::update($basePath, $verbose, true);
  111. }
  112. return '';
  113. }
  114. /**
  115. * Executes a specific update function
  116. *
  117. * @param int $x the DB version number of the function
  118. * @param string $prefix the prefix of the function (update, pre_update)
  119. *
  120. * @return bool true, if the update function worked
  121. * @throws \Friendica\Network\HTTPException\InternalServerErrorException
  122. */
  123. public static function runUpdateFunction($x, $prefix)
  124. {
  125. $funcname = $prefix . '_' . $x;
  126. Logger::log('Update function \'' . $funcname . '\' - start', Logger::DEBUG);
  127. if (function_exists($funcname)) {
  128. // There could be a lot of processes running or about to run.
  129. // We want exactly one process to run the update command.
  130. // So store the fact that we're taking responsibility
  131. // after first checking to see if somebody else already has.
  132. // If the update fails or times-out completely you may need to
  133. // delete the config entry to try again.
  134. if (Lock::acquire('dbupdate_function', 120,Cache::INFINITE)) {
  135. // call the specific update
  136. $retval = $funcname();
  137. if ($retval) {
  138. //send the administrator an e-mail
  139. self::updateFailed(
  140. $x,
  141. L10n::t('Update %s failed. See error logs.', $x)
  142. );
  143. Logger::log('ERROR: Update function \'' . $funcname . '\' - failed: ' . $retval, Logger::ALL);
  144. Lock::release('dbupdate_function');
  145. return false;
  146. } else {
  147. Config::set('database', 'last_successful_update_function', $funcname);
  148. Config::set('database', 'last_successful_update_function_time', time());
  149. if ($prefix == 'update') {
  150. Config::set('system', 'build', $x);
  151. }
  152. Lock::release('dbupdate_function');
  153. Logger::log('Update function \'' . $funcname . '\' - finished', Logger::DEBUG);
  154. return true;
  155. }
  156. }
  157. } else {
  158. Logger::log('Skipping \'' . $funcname . '\' without executing', Logger::DEBUG);
  159. Config::set('database', 'last_successful_update_function', $funcname);
  160. Config::set('database', 'last_successful_update_function_time', time());
  161. if ($prefix == 'update') {
  162. Config::set('system', 'build', $x);
  163. }
  164. return true;
  165. }
  166. }
  167. /**
  168. * send the email and do what is needed to do on update fails
  169. *
  170. * @param int $update_id number of failed update
  171. * @param string $error_message error message
  172. * @throws \Friendica\Network\HTTPException\InternalServerErrorException
  173. */
  174. private static function updateFailed($update_id, $error_message) {
  175. //send the administrators an e-mail
  176. $admin_mail_list = "'".implode("','", array_map(['Friendica\Database\DBA', 'escape'], explode(",", str_replace(" ", "", Config::get('config', 'admin_email')))))."'";
  177. $adminlist = DBA::select('user', ['uid', 'language', 'email'], ['`email` IN (%s)', $admin_mail_list]);
  178. // No valid result?
  179. if (!DBA::isResult($adminlist)) {
  180. Logger::log(sprintf('Cannot notify administrators about update_id=%d, error_message=%s', $update_id, $error_message), Logger::INFO);
  181. // Don't continue
  182. return;
  183. }
  184. // every admin could had different language
  185. foreach ($adminlist as $admin) {
  186. $lang = (($admin['language'])?$admin['language']:'en');
  187. L10n::pushLang($lang);
  188. $preamble = Strings::deindent(L10n::t("
  189. The friendica developers released update %s recently,
  190. but when I tried to install it, something went terribly wrong.
  191. This needs to be fixed soon and I can't do it alone. Please contact a
  192. friendica developer if you can not help me on your own. My database might be invalid.",
  193. $update_id));
  194. $body = L10n::t("The error message is\n[pre]%s[/pre]", $error_message);
  195. notification([
  196. 'uid' => $admin['uid'],
  197. 'type' => SYSTEM_EMAIL,
  198. 'to_email' => $admin['email'],
  199. 'preamble' => $preamble,
  200. 'body' => $body,
  201. 'language' => $lang]
  202. );
  203. L10n::popLang();
  204. }
  205. //try the logger
  206. Logger::log("CRITICAL: Database structure update failed: " . $error_message);
  207. }
  208. private static function updateSuccessfull($from_build, $to_build)
  209. {
  210. //send the administrators an e-mail
  211. $admin_mail_list = "'".implode("','", array_map(['Friendica\Database\DBA', 'escape'], explode(",", str_replace(" ", "", Config::get('config', 'admin_email')))))."'";
  212. $adminlist = DBA::select('user', ['uid', 'language', 'email'], ['`email` IN (%s)', $admin_mail_list]);
  213. if (DBA::isResult($adminlist)) {
  214. // every admin could had different language
  215. foreach ($adminlist as $admin) {
  216. $lang = (($admin['language']) ? $admin['language'] : 'en');
  217. L10n::pushLang($lang);
  218. $preamble = Strings::deindent(L10n::t("
  219. The friendica database was successfully updated from %s to %s.",
  220. $from_build, $to_build));
  221. notification([
  222. 'uid' => $admin['uid'],
  223. 'type' => SYSTEM_EMAIL,
  224. 'to_email' => $admin['email'],
  225. 'preamble' => $preamble,
  226. 'body' => $preamble,
  227. 'language' => $lang]
  228. );
  229. L10n::popLang();
  230. }
  231. }
  232. //try the logger
  233. Logger::log("Database structure update successful.", Logger::TRACE);
  234. }
  235. }