PageRenderTime 47ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/plugins/CoreUpdater/Controller.php

https://github.com/CodeYellowBV/piwik
PHP | 443 lines | 332 code | 75 blank | 36 comment | 40 complexity | 5d19af62ed773e777063b6d72288df5b MD5 | raw file
Possible License(s): LGPL-3.0, JSON, MIT, GPL-3.0, LGPL-2.1, GPL-2.0, AGPL-1.0, BSD-2-Clause, BSD-3-Clause
  1. <?php
  2. /**
  3. * Piwik - free/libre analytics platform
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. *
  8. */
  9. namespace Piwik\Plugins\CoreUpdater;
  10. use Exception;
  11. use Piwik\ArchiveProcessor\Rules;
  12. use Piwik\Common;
  13. use Piwik\Config;
  14. use Piwik\DbHelper;
  15. use Piwik\Filechecks;
  16. use Piwik\Filesystem;
  17. use Piwik\Http;
  18. use Piwik\Option;
  19. use Piwik\Piwik;
  20. use Piwik\Plugin\Manager as PluginManager;
  21. use Piwik\Plugin;
  22. use Piwik\Plugins\CorePluginsAdmin\Marketplace;
  23. use Piwik\Plugins\LanguagesManager\LanguagesManager;
  24. use Piwik\SettingsPiwik;
  25. use Piwik\SettingsServer;
  26. use Piwik\Unzip;
  27. use Piwik\UpdateCheck;
  28. use Piwik\Updater;
  29. use Piwik\Version;
  30. use Piwik\View\OneClickDone;
  31. use Piwik\View;
  32. /**
  33. *
  34. */
  35. class Controller extends \Piwik\Plugin\Controller
  36. {
  37. const CONFIG_FILE_BACKUP = '/config/global.ini.auto-backup-before-update.php';
  38. const PATH_TO_EXTRACT_LATEST_VERSION = '/tmp/latest/';
  39. private $coreError = false;
  40. private $warningMessages = array();
  41. private $errorMessages = array();
  42. private $deactivatedPlugins = array();
  43. private $pathPiwikZip = false;
  44. private $newVersion;
  45. static protected function getLatestZipUrl($newVersion)
  46. {
  47. if (@Config::getInstance()->Debug['allow_upgrades_to_beta']) {
  48. return 'http://builds.piwik.org/piwik-' . $newVersion . '.zip';
  49. }
  50. return Config::getInstance()->General['latest_version_url'];
  51. }
  52. public function newVersionAvailable()
  53. {
  54. Piwik::checkUserHasSuperUserAccess();
  55. $newVersion = $this->checkNewVersionIsAvailableOrDie();
  56. $view = new View('@CoreUpdater/newVersionAvailable');
  57. $this->addCustomLogoInfo($view);
  58. $view->piwik_version = Version::VERSION;
  59. $view->piwik_new_version = $newVersion;
  60. $incompatiblePlugins = $this->getIncompatiblePlugins($newVersion);
  61. $marketplacePlugins = array();
  62. try {
  63. if (!empty($incompatiblePlugins)) {
  64. $marketplace = new Marketplace();
  65. $marketplacePlugins = $marketplace->getAllAvailablePluginNames();
  66. }
  67. } catch (\Exception $e) {}
  68. $view->marketplacePlugins = $marketplacePlugins;
  69. $view->incompatiblePlugins = $incompatiblePlugins;
  70. $view->piwik_latest_version_url = self::getLatestZipUrl($newVersion);
  71. $view->can_auto_update = Filechecks::canAutoUpdate();
  72. $view->makeWritableCommands = Filechecks::getAutoUpdateMakeWritableMessage();
  73. return $view->render();
  74. }
  75. public function oneClickUpdate()
  76. {
  77. Piwik::checkUserHasSuperUserAccess();
  78. $this->newVersion = $this->checkNewVersionIsAvailableOrDie();
  79. SettingsServer::setMaxExecutionTime(0);
  80. $url = self::getLatestZipUrl($this->newVersion);
  81. $steps = array(
  82. array('oneClick_Download', Piwik::translate('CoreUpdater_DownloadingUpdateFromX', $url)),
  83. array('oneClick_Unpack', Piwik::translate('CoreUpdater_UnpackingTheUpdate')),
  84. array('oneClick_Verify', Piwik::translate('CoreUpdater_VerifyingUnpackedFiles')),
  85. array('oneClick_CreateConfigFileBackup', Piwik::translate('CoreUpdater_CreatingBackupOfConfigurationFile', self::CONFIG_FILE_BACKUP))
  86. );
  87. $incompatiblePlugins = $this->getIncompatiblePlugins($this->newVersion);
  88. if (!empty($incompatiblePlugins)) {
  89. $namesToDisable = array();
  90. foreach ($incompatiblePlugins as $incompatiblePlugin) {
  91. $namesToDisable[] = $incompatiblePlugin->getPluginName();
  92. }
  93. $steps[] = array('oneClick_DisableIncompatiblePlugins', Piwik::translate('CoreUpdater_DisablingIncompatiblePlugins', implode(', ', $namesToDisable)));
  94. }
  95. $steps[] = array('oneClick_Copy', Piwik::translate('CoreUpdater_InstallingTheLatestVersion'));
  96. $steps[] = array('oneClick_Finished', Piwik::translate('CoreUpdater_PiwikUpdatedSuccessfully'));
  97. $errorMessage = false;
  98. $messages = array();
  99. foreach ($steps as $step) {
  100. try {
  101. $method = $step[0];
  102. $message = $step[1];
  103. $this->$method();
  104. $messages[] = $message;
  105. } catch (Exception $e) {
  106. $errorMessage = $e->getMessage();
  107. break;
  108. }
  109. }
  110. $view = new OneClickDone(Piwik::getCurrentUserTokenAuth());
  111. $view->coreError = $errorMessage;
  112. $view->feedbackMessages = $messages;
  113. $this->addCustomLogoInfo($view);
  114. return $view->render();
  115. }
  116. public function oneClickResults()
  117. {
  118. $view = new View('@CoreUpdater/oneClickResults');
  119. $view->coreError = Common::getRequestVar('error', '', 'string', $_POST);
  120. $view->feedbackMessages = safe_unserialize(Common::unsanitizeInputValue(Common::getRequestVar('messages', '', 'string', $_POST)));
  121. $this->addCustomLogoInfo($view);
  122. return $view->render();
  123. }
  124. protected function redirectToDashboardWhenNoError($updater)
  125. {
  126. if (count($updater->getSqlQueriesToExecute()) == 1
  127. && !$this->coreError
  128. && empty($this->warningMessages)
  129. && empty($this->errorMessages)
  130. && empty($this->deactivatedPlugins)
  131. ) {
  132. Piwik::redirectToModule('CoreHome');
  133. }
  134. }
  135. protected static function clearPhpCaches()
  136. {
  137. if (function_exists('apc_clear_cache')) {
  138. apc_clear_cache(); // clear the system (aka 'opcode') cache
  139. }
  140. if (function_exists('opcache_reset')) {
  141. opcache_reset(); // reset the opcode cache (php 5.5.0+)
  142. }
  143. }
  144. private function checkNewVersionIsAvailableOrDie()
  145. {
  146. $newVersion = UpdateCheck::isNewestVersionAvailable();
  147. if (!$newVersion) {
  148. throw new Exception(Piwik::translate('CoreUpdater_ExceptionAlreadyLatestVersion', Version::VERSION));
  149. }
  150. return $newVersion;
  151. }
  152. private function oneClick_Download()
  153. {
  154. $pathPiwikZip = PIWIK_USER_PATH . self::PATH_TO_EXTRACT_LATEST_VERSION . 'latest.zip';
  155. $this->pathPiwikZip = SettingsPiwik::rewriteTmpPathWithInstanceId($pathPiwikZip);
  156. Filechecks::dieIfDirectoriesNotWritable(array(self::PATH_TO_EXTRACT_LATEST_VERSION));
  157. // we catch exceptions in the caller (i.e., oneClickUpdate)
  158. $url = self::getLatestZipUrl($this->newVersion) . '?cb=' . $this->newVersion;
  159. Http::fetchRemoteFile($url, $this->pathPiwikZip);
  160. }
  161. private function oneClick_Unpack()
  162. {
  163. $pathExtracted = PIWIK_USER_PATH . self::PATH_TO_EXTRACT_LATEST_VERSION;
  164. $pathExtracted = SettingsPiwik::rewriteTmpPathWithInstanceId($pathExtracted);
  165. $this->pathRootExtractedPiwik = $pathExtracted . 'piwik';
  166. if (file_exists($this->pathRootExtractedPiwik)) {
  167. Filesystem::unlinkRecursive($this->pathRootExtractedPiwik, true);
  168. }
  169. $archive = Unzip::factory('PclZip', $this->pathPiwikZip);
  170. if (0 == ($archive_files = $archive->extract($pathExtracted))) {
  171. throw new Exception(Piwik::translate('CoreUpdater_ExceptionArchiveIncompatible', $archive->errorInfo()));
  172. }
  173. if (0 == count($archive_files)) {
  174. throw new Exception(Piwik::translate('CoreUpdater_ExceptionArchiveEmpty'));
  175. }
  176. unlink($this->pathPiwikZip);
  177. }
  178. private function oneClick_Verify()
  179. {
  180. $someExpectedFiles = array(
  181. '/config/global.ini.php',
  182. '/index.php',
  183. '/core/Piwik.php',
  184. '/piwik.php',
  185. '/plugins/API/API.php'
  186. );
  187. foreach ($someExpectedFiles as $file) {
  188. if (!is_file($this->pathRootExtractedPiwik . $file)) {
  189. throw new Exception(Piwik::translate('CoreUpdater_ExceptionArchiveIncomplete', $file));
  190. }
  191. }
  192. }
  193. private function oneClick_CreateConfigFileBackup()
  194. {
  195. $configFileBefore = PIWIK_USER_PATH . '/config/global.ini.php';
  196. $configFileAfter = PIWIK_USER_PATH . self::CONFIG_FILE_BACKUP;
  197. Filesystem::copy($configFileBefore, $configFileAfter);
  198. }
  199. private function oneClick_DisableIncompatiblePlugins()
  200. {
  201. $plugins = $this->getIncompatiblePlugins($this->newVersion);
  202. foreach ($plugins as $plugin) {
  203. PluginManager::getInstance()->deactivatePlugin($plugin->getPluginName());
  204. }
  205. }
  206. private function oneClick_Copy()
  207. {
  208. /*
  209. * Make sure the execute bit is set for this shell script
  210. */
  211. if (!Rules::isBrowserTriggerEnabled()) {
  212. @chmod($this->pathRootExtractedPiwik . '/misc/cron/archive.sh', 0755);
  213. }
  214. /*
  215. * Copy all files to PIWIK_INCLUDE_PATH.
  216. * These files are accessed through the dispatcher.
  217. */
  218. Filesystem::copyRecursive($this->pathRootExtractedPiwik, PIWIK_INCLUDE_PATH);
  219. /*
  220. * These files are visible in the web root and are generally
  221. * served directly by the web server. May be shared.
  222. */
  223. if (PIWIK_INCLUDE_PATH !== PIWIK_DOCUMENT_ROOT) {
  224. /*
  225. * Copy PHP files that expect to be in the document root
  226. */
  227. $specialCases = array(
  228. '/index.php',
  229. '/piwik.php',
  230. '/js/index.php',
  231. );
  232. foreach ($specialCases as $file) {
  233. Filesystem::copy($this->pathRootExtractedPiwik . $file, PIWIK_DOCUMENT_ROOT . $file);
  234. }
  235. /*
  236. * Copy the non-PHP files (e.g., images, css, javascript)
  237. */
  238. Filesystem::copyRecursive($this->pathRootExtractedPiwik, PIWIK_DOCUMENT_ROOT, true);
  239. }
  240. /*
  241. * Config files may be user (account) specific
  242. */
  243. if (PIWIK_INCLUDE_PATH !== PIWIK_USER_PATH) {
  244. Filesystem::copyRecursive($this->pathRootExtractedPiwik . '/config', PIWIK_USER_PATH . '/config');
  245. }
  246. Filesystem::unlinkRecursive($this->pathRootExtractedPiwik, true);
  247. self::clearPhpCaches();
  248. }
  249. private function oneClick_Finished()
  250. {
  251. }
  252. public function index()
  253. {
  254. $language = Common::getRequestVar('language', '');
  255. if (!empty($language)) {
  256. LanguagesManager::setLanguageForSession($language);
  257. }
  258. try {
  259. return $this->runUpdaterAndExit();
  260. } catch(NoUpdatesFoundException $e) {
  261. Piwik::redirectToModule('CoreHome');
  262. }
  263. }
  264. public function runUpdaterAndExit($doDryRun = null)
  265. {
  266. $updater = new Updater();
  267. $componentsWithUpdateFile = CoreUpdater::getComponentUpdates($updater);
  268. if (empty($componentsWithUpdateFile)) {
  269. throw new NoUpdatesFoundException("Everything is already up to date.");
  270. }
  271. SettingsServer::setMaxExecutionTime(0);
  272. $cli = Common::isPhpCliMode() ? '_cli' : '';
  273. $welcomeTemplate = '@CoreUpdater/runUpdaterAndExit_welcome' . $cli;
  274. $doneTemplate = '@CoreUpdater/runUpdaterAndExit_done' . $cli;
  275. $viewWelcome = new View($welcomeTemplate);
  276. $this->addCustomLogoInfo($viewWelcome);
  277. $viewDone = new View($doneTemplate);
  278. $this->addCustomLogoInfo($viewDone);
  279. $doExecuteUpdates = Common::getRequestVar('updateCorePlugins', 0, 'integer') == 1;
  280. if(is_null($doDryRun)) {
  281. $doDryRun = !$doExecuteUpdates;
  282. }
  283. if($doDryRun) {
  284. $viewWelcome->queries = $updater->getSqlQueriesToExecute();
  285. $viewWelcome->isMajor = $updater->hasMajorDbUpdate();
  286. $this->doWelcomeUpdates($viewWelcome, $componentsWithUpdateFile);
  287. return $viewWelcome->render();
  288. }
  289. // CLI
  290. if (Common::isPhpCliMode()) {
  291. $this->doWelcomeUpdates($viewWelcome, $componentsWithUpdateFile);
  292. $output = $viewWelcome->render();
  293. // Proceed with upgrade in CLI only if user specifically asked for it, or if running console command
  294. $isUpdateRequested = Common::isRunningConsoleCommand() || Piwik::getModule() == 'CoreUpdater';
  295. if (!$this->coreError && $isUpdateRequested) {
  296. $this->doExecuteUpdates($viewDone, $updater, $componentsWithUpdateFile);
  297. $output .= $viewDone->render();
  298. }
  299. return $output;
  300. }
  301. // Web
  302. if ($doExecuteUpdates) {
  303. $this->warningMessages = array();
  304. $this->doExecuteUpdates($viewDone, $updater, $componentsWithUpdateFile);
  305. $this->redirectToDashboardWhenNoError($updater);
  306. return $viewDone->render();
  307. }
  308. exit;
  309. }
  310. private function doWelcomeUpdates($view, $componentsWithUpdateFile)
  311. {
  312. $view->new_piwik_version = Version::VERSION;
  313. $view->commandUpgradePiwik = "<br /><code>php " . Filesystem::getPathToPiwikRoot() . "/console core:update </code>";
  314. $pluginNamesToUpdate = array();
  315. $coreToUpdate = false;
  316. // handle case of existing database with no tables
  317. if (!DbHelper::isInstalled()) {
  318. $this->errorMessages[] = Piwik::translate('CoreUpdater_EmptyDatabaseError', Config::getInstance()->database['dbname']);
  319. $this->coreError = true;
  320. $currentVersion = 'N/A';
  321. } else {
  322. $this->errorMessages = array();
  323. try {
  324. $currentVersion = Option::get('version_core');
  325. } catch (Exception $e) {
  326. $currentVersion = '<= 0.2.9';
  327. }
  328. foreach ($componentsWithUpdateFile as $name => $filenames) {
  329. if ($name == 'core') {
  330. $coreToUpdate = true;
  331. } else {
  332. $pluginNamesToUpdate[] = $name;
  333. }
  334. }
  335. }
  336. // check file integrity
  337. $integrityInfo = Filechecks::getFileIntegrityInformation();
  338. if (isset($integrityInfo[1])) {
  339. if ($integrityInfo[0] == false) {
  340. $this->warningMessages[] = Piwik::translate('General_FileIntegrityWarningExplanation');
  341. }
  342. $this->warningMessages = array_merge($this->warningMessages, array_slice($integrityInfo, 1));
  343. }
  344. Filesystem::deleteAllCacheOnUpdate();
  345. $view->coreError = $this->coreError;
  346. $view->warningMessages = $this->warningMessages;
  347. $view->errorMessages = $this->errorMessages;
  348. $view->current_piwik_version = $currentVersion;
  349. $view->pluginNamesToUpdate = $pluginNamesToUpdate;
  350. $view->coreToUpdate = $coreToUpdate;
  351. }
  352. private function doExecuteUpdates($view, $updater, $componentsWithUpdateFile)
  353. {
  354. $result = CoreUpdater::updateComponents($updater, $componentsWithUpdateFile);
  355. $this->coreError = $result['coreError'];
  356. $this->warningMessages = $result['warnings'];
  357. $this->errorMessages = $result['errors'];
  358. $this->deactivatedPlugins = $result['deactivatedPlugins'];
  359. $view->coreError = $this->coreError;
  360. $view->warningMessages = $this->warningMessages;
  361. $view->errorMessages = $this->errorMessages;
  362. $view->deactivatedPlugins = $this->deactivatedPlugins;
  363. }
  364. private function getIncompatiblePlugins($piwikVersion)
  365. {
  366. return PluginManager::getInstance()->getIncompatiblePlugins($piwikVersion);
  367. }
  368. }