3 namespace Friendica\Core;
6 use Friendica\Core\Config\Cache\IConfigCache;
7 use Friendica\Database\DBA;
8 use Friendica\Database\DBStructure;
9 use Friendica\Util\BasePath;
10 use Friendica\Util\Config\ConfigFileLoader;
11 use Friendica\Util\Config\ConfigFileSaver;
12 use Friendica\Util\Strings;
20 * @brief Function to check if the Database structure needs an update.
22 * @param string $basePath The base path of this application
23 * @param boolean $via_worker Is the check run via the worker?
24 * @param App\Mode $mode The current app mode
26 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
28 public static function check($basePath, $via_worker, App\Mode $mode)
30 if (!DBA::connected()) {
34 // Check if the config files are set correctly
35 self::checkConfigFile($basePath, $mode);
37 // Don't check the status if the last update was failed
38 if (Config::get('system', 'update', Update::SUCCESS, true) == Update::FAILED) {
42 $build = Config::get('system', 'build');
45 Config::set('system', 'build', DB_UPDATE_VERSION - 1);
46 $build = DB_UPDATE_VERSION - 1;
49 // We don't support upgrading from very old versions anymore
50 if ($build < NEW_UPDATE_ROUTINE_VERSION) {
51 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.');
54 if ($build < DB_UPDATE_VERSION) {
56 // Calling the database update directly via the worker enables us to perform database changes to the workerqueue table itself.
57 // This is a fallback, since normally the database update will be performed by a worker job.
58 // This worker job doesn't work for changes to the "workerqueue" table itself.
61 Worker::add(PRIORITY_CRITICAL, 'DBUpdate');
67 * Automatic database updates
69 * @param string $basePath The base path of this application
70 * @param bool $force Force the Update-Check even if the database version doesn't match
71 * @param bool $override Overrides any running/stuck updates
72 * @param bool $verbose Run the Update-Check verbose
73 * @param bool $sendMail Sends a Mail to the administrator in case of success/failure
75 * @return string Empty string if the update is successful, error messages otherwise
76 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
78 public static function run($basePath, $force = false, $override = false, $verbose = false, $sendMail = true)
80 // In force mode, we release the dbupdate lock first
81 // Necessary in case of an stuck update
83 Lock::release('dbupdate', true);
86 $build = Config::get('system', 'build', null, true);
88 if (empty($build) || ($build > DB_UPDATE_VERSION)) {
89 $build = DB_UPDATE_VERSION - 1;
90 Config::set('system', 'build', $build);
93 if ($build != DB_UPDATE_VERSION || $force) {
94 require_once 'update.php';
96 $stored = intval($build);
97 $current = intval(DB_UPDATE_VERSION);
98 if ($stored < $current || $force) {
99 Config::load('database');
101 Logger::info('Update starting.', ['from' => $stored, 'to' => $current]);
103 // Compare the current structure with the defined structure
104 // If the Lock is acquired, never release it automatically to avoid double updates
105 if (Lock::acquire('dbupdate', 120, Cache::INFINITE)) {
107 // Checks if the build changed during Lock acquiring (so no double update occurs)
108 $retryBuild = Config::get('system', 'build', null, true);
109 if ($retryBuild !== $build) {
110 Logger::info('Update already done.', ['from' => $stored, 'to' => $current]);
111 Lock::release('dbupdate');
115 // run the pre_update_nnnn functions in update.php
116 for ($x = $stored + 1; $x <= $current; $x++) {
117 $r = self::runUpdateFunction($x, 'pre_update');
119 Config::set('system', 'update', Update::FAILED);
120 Lock::release('dbupdate');
125 // update the structure in one call
126 $retval = DBStructure::update($basePath, $verbose, true);
127 if (!empty($retval)) {
134 Logger::error('Update ERROR.', ['from' => $stored, 'to' => $current, 'retval' => $retval]);
135 Config::set('system', 'update', Update::FAILED);
136 Lock::release('dbupdate');
139 Config::set('database', 'last_successful_update', $current);
140 Config::set('database', 'last_successful_update_time', time());
141 Logger::info('Update finished.', ['from' => $stored, 'to' => $current]);
144 // run the update_nnnn functions in update.php
145 for ($x = $stored + 1; $x <= $current; $x++) {
146 $r = self::runUpdateFunction($x, 'update');
148 Config::set('system', 'update', Update::FAILED);
149 Lock::release('dbupdate');
154 Logger::notice('Update success.', ['from' => $stored, 'to' => $current]);
156 self::updateSuccessfull($stored, $current);
159 Config::set('system', 'update', Update::SUCCESS);
160 Lock::release('dbupdate');
169 * Executes a specific update function
171 * @param int $x the DB version number of the function
172 * @param string $prefix the prefix of the function (update, pre_update)
174 * @return bool true, if the update function worked
175 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
177 public static function runUpdateFunction($x, $prefix)
179 $funcname = $prefix . '_' . $x;
181 Logger::info('Update function start.', ['function' => $funcname]);
183 if (function_exists($funcname)) {
184 // There could be a lot of processes running or about to run.
185 // We want exactly one process to run the update command.
186 // So store the fact that we're taking responsibility
187 // after first checking to see if somebody else already has.
188 // If the update fails or times-out completely you may need to
189 // delete the config entry to try again.
191 if (Lock::acquire('dbupdate_function', 120,Cache::INFINITE)) {
193 // call the specific update
194 $retval = $funcname();
197 //send the administrator an e-mail
200 L10n::t('Update %s failed. See error logs.', $x)
202 Logger::error('Update function ERROR.', ['function' => $funcname, 'retval' => $retval]);
203 Lock::release('dbupdate_function');
206 Config::set('database', 'last_successful_update_function', $funcname);
207 Config::set('database', 'last_successful_update_function_time', time());
209 if ($prefix == 'update') {
210 Config::set('system', 'build', $x);
213 Lock::release('dbupdate_function');
214 Logger::info('Update function finished.', ['function' => $funcname]);
219 Logger::info('Update function skipped.', ['function' => $funcname]);
221 Config::set('database', 'last_successful_update_function', $funcname);
222 Config::set('database', 'last_successful_update_function_time', time());
224 if ($prefix == 'update') {
225 Config::set('system', 'build', $x);
233 * Checks the config settings and saves given config values into the config file
235 * @param string $basePath The basepath of Friendica
236 * @param App\Mode $mode The current App mode
238 * @return bool True, if something has been saved
240 public static function checkConfigFile($basePath, App\Mode $mode)
242 if (empty($basePath)) {
243 $basePath = BasePath::create(dirname(__DIR__, 2));
249 'allowEmpty' => false,
255 'allowEmpty' => false,
256 'default' => $basePath,
261 $configFileLoader = new ConfigFileLoader($basePath, $mode);
262 $configCache = new Config\Cache\ConfigCache();
263 $configFileLoader->setupCache($configCache, true);
265 // checks if something is to update, otherwise skip this function at all
266 $missingConfig = $configCache->keyDiff($config);
267 if (empty($missingConfig)) {
271 // We just want one update process
272 if (Lock::acquire('config_update')) {
273 $configFileSaver = new ConfigFileSaver($basePath);
278 foreach ($missingConfig as $category => $keys) {
279 foreach ($keys as $key => $value) {
280 if (self::updateConfigEntry($configCache, $configFileSaver, $category, $key, $value['allowEmpty'], $value['default'])) {
281 $toDelete[] = ['cat' => $category, 'key' => $key];
287 // In case there is nothing to do, skip the update
289 Lock::release('config_update');
293 if (!$configFileSaver->saveToConfigFile()) {
294 Logger::alert('Config entry update failed - maybe wrong permission?');
295 Lock::release('config_update');
299 // After the successful save, remove the db values
300 foreach ($toDelete as $delete) {
301 DBA::delete('config', ['cat' => $delete['cat'], 'k' => $delete['key']]);
304 Lock::release('config_update');
311 * Adds a value to the ConfigFileSave in case it isn't already updated
313 * @param IConfigCache $configCache The cached config file
314 * @param ConfigFileSaver $configFileSaver The config file saver
315 * @param string $cat The config category
316 * @param string $key The config key
317 * @param bool $allowEmpty If true, empty values are valid (Default there has to be a variable)
318 * @param string $default A default value, if none of the settings are valid
320 * @return boolean True, if a value was updated
322 * @throws \Exception if DBA or Logger doesn't work
324 private static function updateConfigEntry(
325 IConfigCache $configCache,
326 ConfigFileSaver $configFileSaver,
333 // check if the config file differs from the whole configuration (= The db contains other values)
334 $fileValue = $configCache->get($cat, $key);
335 $dbConfig = DBA::selectFirst('config', ['v'], ['cat' => $cat, 'k' => $key]);
337 if (DBA::isResult($dbConfig)) {
338 $dbValue = $dbConfig['v'];
343 // If the db contains a config value, check it
345 ($allowEmpty && isset($dbValue)) ||
346 (!$allowEmpty && !empty($dbValue))
348 $fileValue !== $dbValue) {
349 Logger::info('Difference in config found', ['cat' => $cat, 'key' => $key, 'file' => $fileValue, 'db' => $dbValue]);
350 $configFileSaver->addConfigValue($cat, $key, $dbValue);
353 // If both config values are not set, use the default value
355 ($allowEmpty && !isset($fileValue) && !isset($dbValue)) ||
356 (!$allowEmpty && empty($fileValue) && empty($dbValue) && !empty($default))) {
358 Logger::info('Using default for config', ['cat' => $cat, 'key' => $key, 'value' => $default]);
359 $configFileSaver->addConfigValue($cat, $key, $default);
362 // If either the file config value isn't empty or the db value is the same as the
363 // file config value, skip it
365 Logger::debug('No Difference in config found', ['cat' => $cat, 'key' => $key, 'value' => $fileValue, 'db' => $dbValue]);
371 * send the email and do what is needed to do on update fails
373 * @param int $update_id number of failed update
374 * @param string $error_message error message
375 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
377 private static function updateFailed($update_id, $error_message) {
378 //send the administrators an e-mail
379 $condition = ['email' => explode(",", str_replace(" ", "", Config::get('config', 'admin_email'))), 'parent-uid' => 0];
380 $adminlist = DBA::select('user', ['uid', 'language', 'email'], $condition, ['order' => ['uid']]);
383 if (!DBA::isResult($adminlist)) {
384 Logger::warning('Cannot notify administrators .', ['update' => $update_id, 'message' => $error_message]);
392 // every admin could had different language
393 while ($admin = DBA::fetch($adminlist)) {
394 if (in_array($admin['email'], $sent)) {
397 $sent[] = $admin['email'];
399 $lang = (($admin['language'])?$admin['language']:'en');
400 L10n::pushLang($lang);
402 $preamble = Strings::deindent(L10n::t("
403 The friendica developers released update %s recently,
404 but when I tried to install it, something went terribly wrong.
405 This needs to be fixed soon and I can't do it alone. Please contact a
406 friendica developer if you can not help me on your own. My database might be invalid.",
408 $body = L10n::t("The error message is\n[pre]%s[/pre]", $error_message);
411 'uid' => $admin['uid'],
412 'type' => SYSTEM_EMAIL,
413 'to_email' => $admin['email'],
414 'subject' => l10n::t('[Friendica Notify] Database update'),
415 'preamble' => $preamble,
423 Logger::alert('Database structure update FAILED.', ['error' => $error_message]);
426 private static function updateSuccessfull($from_build, $to_build)
428 //send the administrators an e-mail
429 $condition = ['email' => explode(",", str_replace(" ", "", Config::get('config', 'admin_email'))), 'parent-uid' => 0];
430 $adminlist = DBA::select('user', ['uid', 'language', 'email'], $condition, ['order' => ['uid']]);
432 if (DBA::isResult($adminlist)) {
435 // every admin could had different language
436 while ($admin = DBA::fetch($adminlist)) {
437 if (in_array($admin['email'], $sent)) {
440 $sent[] = $admin['email'];
442 $lang = (($admin['language']) ? $admin['language'] : 'en');
443 L10n::pushLang($lang);
445 $preamble = Strings::deindent(L10n::t("
446 The friendica database was successfully updated from %s to %s.",
447 $from_build, $to_build));
450 'uid' => $admin['uid'],
451 'type' => SYSTEM_EMAIL,
452 'to_email' => $admin['email'],
453 'preamble' => $preamble,
462 Logger::debug('Database structure update successful.');