PageRenderTime 62ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 1ms

/plugins/Installation/Controller.php

https://github.com/CodeYellowBV/piwik
PHP | 718 lines | 478 code | 122 blank | 118 comment | 42 complexity | 07fcd4a0f5dcaad1c5d59b76ff45803d 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\Installation;
  10. use Exception;
  11. use Piwik\Access;
  12. use Piwik\AssetManager;
  13. use Piwik\Common;
  14. use Piwik\Config;
  15. use Piwik\DataAccess\ArchiveTableCreator;
  16. use Piwik\Db;
  17. use Piwik\Db\Adapter;
  18. use Piwik\DbHelper;
  19. use Piwik\Filesystem;
  20. use Piwik\Http;
  21. use Piwik\Option;
  22. use Piwik\Piwik;
  23. use Piwik\Plugin\Manager;
  24. use Piwik\Plugins\CoreUpdater\CoreUpdater;
  25. use Piwik\Plugins\LanguagesManager\LanguagesManager;
  26. use Piwik\Plugins\SitesManager\API as APISitesManager;
  27. use Piwik\Plugins\UserCountry\LocationProvider;
  28. use Piwik\Plugins\UsersManager\API as APIUsersManager;
  29. use Piwik\ProxyHeaders;
  30. use Piwik\SettingsPiwik;
  31. use Piwik\Updater;
  32. use Piwik\Url;
  33. use Piwik\Version;
  34. use Zend_Db_Adapter_Exception;
  35. /**
  36. * Installation controller
  37. *
  38. */
  39. class Controller extends \Piwik\Plugin\ControllerAdmin
  40. {
  41. public $steps = array(
  42. 'welcome' => 'Installation_Welcome',
  43. 'systemCheck' => 'Installation_SystemCheck',
  44. 'databaseSetup' => 'Installation_DatabaseSetup',
  45. 'tablesCreation' => 'Installation_Tables',
  46. 'setupSuperUser' => 'Installation_SuperUser',
  47. 'firstWebsiteSetup' => 'Installation_SetupWebsite',
  48. 'trackingCode' => 'General_JsTrackingTag',
  49. 'finished' => 'Installation_Congratulations',
  50. );
  51. /**
  52. * Get installation steps
  53. *
  54. * @return array installation steps
  55. */
  56. public function getInstallationSteps()
  57. {
  58. return $this->steps;
  59. }
  60. /**
  61. * Get default action (first installation step)
  62. *
  63. * @return string function name
  64. */
  65. function getDefaultAction()
  66. {
  67. $steps = array_keys($this->steps);
  68. return $steps[0];
  69. }
  70. /**
  71. * Installation Step 1: Welcome
  72. *
  73. * Can also display an error message when there is a failure early (eg. DB connection failed)
  74. *
  75. * @param string Optional error message
  76. */
  77. function welcome($message = false)
  78. {
  79. // Delete merged js/css files to force regenerations based on updated activated plugin list
  80. Filesystem::deleteAllCacheOnUpdate();
  81. if(empty($message)) {
  82. $this->checkPiwikIsNotInstalled();
  83. }
  84. $view = new View(
  85. '@Installation/welcome',
  86. $this->getInstallationSteps(),
  87. __FUNCTION__
  88. );
  89. $view->newInstall = !SettingsPiwik::isPiwikInstalled();
  90. $view->errorMessage = $message;
  91. $view->showNextStep = $view->newInstall;
  92. return $view->render();
  93. }
  94. /**
  95. * Installation Step 2: System Check
  96. */
  97. function systemCheck()
  98. {
  99. $this->checkPiwikIsNotInstalled();
  100. $this->deleteConfigFileIfNeeded();
  101. $view = new View(
  102. '@Installation/systemCheck',
  103. $this->getInstallationSteps(),
  104. __FUNCTION__
  105. );
  106. $view->duringInstall = true;
  107. $this->setupSystemCheckView($view);
  108. $view->showNextStep = !$view->problemWithSomeDirectories
  109. && $view->infos['phpVersion_ok']
  110. && count($view->infos['adapters'])
  111. && !count($view->infos['missing_extensions'])
  112. && !count($view->infos['missing_functions']);
  113. // On the system check page, if all is green, display Next link at the top
  114. $view->showNextStepAtTop = $view->showNextStep;
  115. return $view->render();
  116. }
  117. /**
  118. * Installation Step 3: Database Set-up
  119. * @throws Exception|Zend_Db_Adapter_Exception
  120. */
  121. function databaseSetup()
  122. {
  123. $this->checkPiwikIsNotInstalled();
  124. $view = new View(
  125. '@Installation/databaseSetup',
  126. $this->getInstallationSteps(),
  127. __FUNCTION__
  128. );
  129. $view->showNextStep = false;
  130. $form = new FormDatabaseSetup();
  131. if ($form->validate()) {
  132. try {
  133. $dbInfos = $form->createDatabaseObject();
  134. DbHelper::checkDatabaseVersion();
  135. Db::get()->checkClientVersion();
  136. $this->createConfigFile($dbInfos);
  137. $this->redirectToNextStep(__FUNCTION__);
  138. } catch (Exception $e) {
  139. $view->errorMessage = Common::sanitizeInputValue($e->getMessage());
  140. }
  141. }
  142. $view->addForm($form);
  143. return $view->render();
  144. }
  145. /**
  146. * Installation Step 4: Table Creation
  147. */
  148. function tablesCreation()
  149. {
  150. $this->checkPiwikIsNotInstalled();
  151. $view = new View(
  152. '@Installation/tablesCreation',
  153. $this->getInstallationSteps(),
  154. __FUNCTION__
  155. );
  156. if ($this->getParam('deleteTables')) {
  157. Manager::getInstance()->clearPluginsInstalledConfig();
  158. Db::dropAllTables();
  159. $view->existingTablesDeleted = true;
  160. }
  161. $tablesInstalled = DbHelper::getTablesInstalled();
  162. $view->tablesInstalled = '';
  163. if (count($tablesInstalled) > 0) {
  164. // we have existing tables
  165. $view->tablesInstalled = implode(', ', $tablesInstalled);
  166. $view->someTablesInstalled = true;
  167. Access::getInstance();
  168. Piwik::setUserHasSuperUserAccess();
  169. if ($this->hasEnoughTablesToReuseDb($tablesInstalled) &&
  170. count(APISitesManager::getInstance()->getAllSitesId()) > 0 &&
  171. count(APIUsersManager::getInstance()->getUsers()) > 0
  172. ) {
  173. $view->showReuseExistingTables = true;
  174. }
  175. } else {
  176. DbHelper::createTables();
  177. DbHelper::createAnonymousUser();
  178. $this->updateComponents();
  179. Updater::recordComponentSuccessfullyUpdated('core', Version::VERSION);
  180. $view->tablesCreated = true;
  181. $view->showNextStep = true;
  182. }
  183. return $view->render();
  184. }
  185. function reuseTables()
  186. {
  187. $this->checkPiwikIsNotInstalled();
  188. $steps = $this->getInstallationSteps();
  189. $steps['tablesCreation'] = 'Installation_ReusingTables';
  190. $view = new View(
  191. '@Installation/reuseTables',
  192. $steps,
  193. 'tablesCreation'
  194. );
  195. $result = $this->updateComponents();
  196. if($result === false) {
  197. $this->redirectToNextStep('tablesCreation');
  198. }
  199. $oldVersion = Option::get('version_core');
  200. $view->coreError = $result['coreError'];
  201. $view->warningMessages = $result['warnings'];
  202. $view->errorMessages = $result['errors'];
  203. $view->deactivatedPlugins = $result['deactivatedPlugins'];
  204. $view->currentVersion = Version::VERSION;
  205. $view->oldVersion = $oldVersion;
  206. $view->showNextStep = true;
  207. return $view->render();
  208. }
  209. /**
  210. * Installation Step 5: General Set-up (superuser login/password/email and subscriptions)
  211. */
  212. function setupSuperUser()
  213. {
  214. $this->checkPiwikIsNotInstalled();
  215. $this->initObjectsToCallAPI();
  216. if(count(APIUsersManager::getInstance()->getUsersHavingSuperUserAccess()) > 0) {
  217. $this->redirectToNextStep('setupSuperUser');
  218. }
  219. $view = new View(
  220. '@Installation/setupSuperUser',
  221. $this->getInstallationSteps(),
  222. __FUNCTION__
  223. );
  224. $form = new FormSuperUser();
  225. if ($form->validate()) {
  226. try {
  227. $this->createSuperUser($form->getSubmitValue('login'),
  228. $form->getSubmitValue('password'),
  229. $form->getSubmitValue('email'));
  230. $email = $form->getSubmitValue('email');
  231. $newsletterSecurity = $form->getSubmitValue('subscribe_newsletter_security');
  232. $newsletterCommunity = $form->getSubmitValue('subscribe_newsletter_community');
  233. $this->registerNewsletter($email, $newsletterSecurity, $newsletterCommunity);
  234. $this->redirectToNextStep(__FUNCTION__);
  235. } catch (Exception $e) {
  236. $view->errorMessage = $e->getMessage();
  237. }
  238. }
  239. $view->addForm($form);
  240. return $view->render();
  241. }
  242. /**
  243. * Installation Step 6: Configure first web-site
  244. */
  245. public function firstWebsiteSetup()
  246. {
  247. $this->checkPiwikIsNotInstalled();
  248. $this->initObjectsToCallAPI();
  249. if(count(APISitesManager::getInstance()->getAllSitesId()) > 0) {
  250. // if there is a already a website, skip this step and trackingCode step
  251. $this->redirectToNextStep('trackingCode');
  252. }
  253. $view = new View(
  254. '@Installation/firstWebsiteSetup',
  255. $this->getInstallationSteps(),
  256. __FUNCTION__
  257. );
  258. $form = new FormFirstWebsiteSetup();
  259. if ($form->validate()) {
  260. $name = Common::unsanitizeInputValue($form->getSubmitValue('siteName'));
  261. $url = Common::unsanitizeInputValue($form->getSubmitValue('url'));
  262. $ecommerce = (int)$form->getSubmitValue('ecommerce');
  263. try {
  264. $result = APISitesManager::getInstance()->addSite($name, $url, $ecommerce);
  265. $params = array(
  266. 'site_idSite' => $result,
  267. 'site_name' => urlencode($name)
  268. );
  269. $this->addTrustedHosts($url);
  270. $this->redirectToNextStep(__FUNCTION__, $params);
  271. } catch (Exception $e) {
  272. $view->errorMessage = $e->getMessage();
  273. }
  274. }
  275. // Display previous step success message, when current step form was not submitted yet
  276. if(count($form->getErrorMessages()) == 0) {
  277. $view->displayGeneralSetupSuccess = true;
  278. }
  279. $view->addForm($form);
  280. return $view->render();
  281. }
  282. /**
  283. * Installation Step 7: Display JavaScript tracking code
  284. */
  285. public function trackingCode()
  286. {
  287. $this->checkPiwikIsNotInstalled();
  288. $this->markInstallationAsCompleted();
  289. $view = new View(
  290. '@Installation/trackingCode',
  291. $this->getInstallationSteps(),
  292. __FUNCTION__
  293. );
  294. $siteName = Common::unsanitizeInputValue($this->getParam('site_name'));
  295. $idSite = $this->getParam('site_idSite');
  296. // Load the Tracking code and help text from the SitesManager
  297. $viewTrackingHelp = new \Piwik\View('@SitesManager/_displayJavascriptCode');
  298. $viewTrackingHelp->displaySiteName = $siteName;
  299. $viewTrackingHelp->jsTag = Piwik::getJavascriptCode($idSite, Url::getCurrentUrlWithoutFileName());
  300. $viewTrackingHelp->idSite = $idSite;
  301. $viewTrackingHelp->piwikUrl = Url::getCurrentUrlWithoutFileName();
  302. $view->trackingHelp = $viewTrackingHelp->render();
  303. $view->displaySiteName = $siteName;
  304. $view->displayfirstWebsiteSetupSuccess = true;
  305. $view->showNextStep = true;
  306. return $view->render();
  307. }
  308. /**
  309. * Installation Step 8: Finished!
  310. */
  311. public function finished()
  312. {
  313. $this->markInstallationAsCompleted();
  314. $view = new View(
  315. '@Installation/finished',
  316. $this->getInstallationSteps(),
  317. __FUNCTION__
  318. );
  319. $view->showNextStep = false;
  320. $output = $view->render();
  321. return $output;
  322. }
  323. /**
  324. * Get system information
  325. */
  326. public static function getSystemInformation()
  327. {
  328. $systemCheck = new SystemCheck();
  329. return $systemCheck->getSystemInformation();
  330. }
  331. /**
  332. * This controller action renders an admin tab that runs the installation
  333. * system check, so people can see if there are any issues w/ their running
  334. * Piwik installation.
  335. *
  336. * This admin tab is only viewable by the Super User.
  337. */
  338. public function systemCheckPage()
  339. {
  340. Piwik::checkUserHasSuperUserAccess();
  341. $view = new View(
  342. '@Installation/systemCheckPage',
  343. $this->getInstallationSteps(),
  344. __FUNCTION__
  345. );
  346. $this->setBasicVariablesView($view);
  347. $view->duringInstall = false;
  348. $this->setupSystemCheckView($view);
  349. $infos = $view->infos;
  350. $infos['extra'] = SystemCheck::performAdminPageOnlySystemCheck();
  351. $view->infos = $infos;
  352. return $view->render();
  353. }
  354. /**
  355. * Save language selection in session-store
  356. */
  357. public function saveLanguage()
  358. {
  359. $language = $this->getParam('language');
  360. LanguagesManager::setLanguageForSession($language);
  361. Url::redirectToReferrer();
  362. }
  363. /**
  364. * Prints out the CSS for installer/updater
  365. *
  366. * During installation and update process, we load a minimal Less file.
  367. * At this point Piwik may not be setup yet to write files in tmp/assets/
  368. * so in this case we compile and return the string on every request.
  369. */
  370. public function getBaseCss()
  371. {
  372. @header('Content-Type: text/css');
  373. return AssetManager::getInstance()->getCompiledBaseCss()->getContent();
  374. }
  375. private function getParam($name)
  376. {
  377. return Common::getRequestVar($name, false, 'string');
  378. }
  379. /**
  380. * Instantiate access and log objects
  381. */
  382. private function initObjectsToCallAPI()
  383. {
  384. Piwik::setUserHasSuperUserAccess();
  385. }
  386. /**
  387. * Write configuration file from session-store
  388. */
  389. private function createConfigFile($dbInfos)
  390. {
  391. $config = Config::getInstance();
  392. // make sure DB sessions are used if the filesystem is NFS
  393. if (Filesystem::checkIfFileSystemIsNFS()) {
  394. $config->General['session_save_handler'] = 'dbtable';
  395. }
  396. if (count($headers = ProxyHeaders::getProxyClientHeaders()) > 0) {
  397. $config->General['proxy_client_headers'] = $headers;
  398. }
  399. if (count($headers = ProxyHeaders::getProxyHostHeaders()) > 0) {
  400. $config->General['proxy_host_headers'] = $headers;
  401. }
  402. if (Common::getRequestVar('clientProtocol', 'http', 'string') == 'https') {
  403. $protocol = 'https';
  404. } else {
  405. $protocol = ProxyHeaders::getProtocolInformation();
  406. }
  407. if (!empty($protocol)
  408. && !\Piwik\ProxyHttp::isHttps()) {
  409. $config->General['assume_secure_protocol'] = '1';
  410. }
  411. $config->General['salt'] = Common::generateUniqId();
  412. $config->General['installation_in_progress'] = 1;
  413. $config->database = $dbInfos;
  414. if (!DbHelper::isDatabaseConnectionUTF8()) {
  415. $config->database['charset'] = 'utf8';
  416. }
  417. $config->forceSave();
  418. }
  419. private function checkPiwikIsNotInstalled()
  420. {
  421. if(!SettingsPiwik::isPiwikInstalled()) {
  422. return;
  423. }
  424. \Piwik\Plugins\Login\Controller::clearSession();
  425. $message = Piwik::translate('Installation_InvalidStateError',
  426. array('<br /><strong>',
  427. '</strong>',
  428. '<a href=\'' . Common::sanitizeInputValue(Url::getCurrentUrlWithoutFileName()) . '\'>',
  429. '</a>')
  430. );
  431. Piwik::exitWithErrorMessage($message);
  432. }
  433. /**
  434. * Write configuration file from session-store
  435. */
  436. private function markInstallationAsCompleted()
  437. {
  438. $config = Config::getInstance();
  439. unset($config->General['installation_in_progress']);
  440. $config->forceSave();
  441. }
  442. /**
  443. * Redirect to next step
  444. *
  445. * @param string $currentStep Current step
  446. * @return void
  447. */
  448. private function redirectToNextStep($currentStep, $parameters = array())
  449. {
  450. $steps = array_keys($this->steps);
  451. $nextStep = $steps[1 + array_search($currentStep, $steps)];
  452. Piwik::redirectToModule('Installation', $nextStep, $parameters);
  453. }
  454. /**
  455. * Extract host from URL
  456. *
  457. * @param string $url URL
  458. *
  459. * @return string|false
  460. */
  461. private function extractHost($url)
  462. {
  463. $urlParts = parse_url($url);
  464. if (isset($urlParts['host']) && strlen($host = $urlParts['host'])) {
  465. return $host;
  466. }
  467. return false;
  468. }
  469. /**
  470. * Add trusted hosts
  471. */
  472. private function addTrustedHosts($siteUrl)
  473. {
  474. $trustedHosts = array();
  475. // extract host from the request header
  476. if (($host = $this->extractHost('http://' . Url::getHost())) !== false) {
  477. $trustedHosts[] = $host;
  478. }
  479. // extract host from first web site
  480. if (($host = $this->extractHost(urldecode($siteUrl))) !== false) {
  481. $trustedHosts[] = $host;
  482. }
  483. $trustedHosts = array_unique($trustedHosts);
  484. if (count($trustedHosts)) {
  485. $general = Config::getInstance()->General;
  486. $general['trusted_hosts'] = $trustedHosts;
  487. Config::getInstance()->General = $general;
  488. Config::getInstance()->forceSave();
  489. }
  490. }
  491. /**
  492. * Utility function, sets up a view that will display system check info.
  493. *
  494. * @param View $view
  495. */
  496. private function setupSystemCheckView($view)
  497. {
  498. $view->infos = self::getSystemInformation();
  499. $view->helpMessages = array(
  500. 'zlib' => 'Installation_SystemCheckZlibHelp',
  501. 'SPL' => 'Installation_SystemCheckSplHelp',
  502. 'iconv' => 'Installation_SystemCheckIconvHelp',
  503. 'mbstring' => 'Installation_SystemCheckMbstringHelp',
  504. 'Reflection' => 'Required extension that is built in PHP, see http://www.php.net/manual/en/book.reflection.php',
  505. 'json' => 'Installation_SystemCheckWarnJsonHelp',
  506. 'libxml' => 'Installation_SystemCheckWarnLibXmlHelp',
  507. 'dom' => 'Installation_SystemCheckWarnDomHelp',
  508. 'SimpleXML' => 'Installation_SystemCheckWarnSimpleXMLHelp',
  509. 'set_time_limit' => 'Installation_SystemCheckTimeLimitHelp',
  510. 'mail' => 'Installation_SystemCheckMailHelp',
  511. 'parse_ini_file' => 'Installation_SystemCheckParseIniFileHelp',
  512. 'glob' => 'Installation_SystemCheckGlobHelp',
  513. 'debug_backtrace' => 'Installation_SystemCheckDebugBacktraceHelp',
  514. 'create_function' => 'Installation_SystemCheckCreateFunctionHelp',
  515. 'eval' => 'Installation_SystemCheckEvalHelp',
  516. 'gzcompress' => 'Installation_SystemCheckGzcompressHelp',
  517. 'gzuncompress' => 'Installation_SystemCheckGzuncompressHelp',
  518. 'pack' => 'Installation_SystemCheckPackHelp',
  519. 'php5-json' => 'Installation_SystemCheckJsonHelp',
  520. 'session.auto_start' => 'Installation_SystemCheckSessionAutostart',
  521. );
  522. $view->problemWithSomeDirectories = (false !== array_search(false, $view->infos['directories']));
  523. }
  524. private function createSuperUser($login, $password, $email)
  525. {
  526. $this->initObjectsToCallAPI();
  527. $api = APIUsersManager::getInstance();
  528. $api->addUser($login, $password, $email);
  529. $this->initObjectsToCallAPI();
  530. $api->setSuperUserAccess($login, true);
  531. }
  532. private function hasEnoughTablesToReuseDb($tablesInstalled)
  533. {
  534. if (empty($tablesInstalled) || !is_array($tablesInstalled)) {
  535. return false;
  536. }
  537. $archiveTables = ArchiveTableCreator::getTablesArchivesInstalled();
  538. $baseTablesInstalled = count($tablesInstalled) - count($archiveTables);
  539. $minimumCountPiwikTables = 12;
  540. return $baseTablesInstalled >= $minimumCountPiwikTables;
  541. }
  542. private function deleteConfigFileIfNeeded()
  543. {
  544. $config = Config::getInstance();
  545. if($config->existsLocalConfig()) {
  546. $config->deleteLocalConfig();
  547. }
  548. }
  549. /**
  550. * @param $email
  551. * @param $newsletterSecurity
  552. * @param $newsletterCommunity
  553. */
  554. protected function registerNewsletter($email, $newsletterSecurity, $newsletterCommunity)
  555. {
  556. $url = Config::getInstance()->General['api_service_url'];
  557. $url .= '/1.0/subscribeNewsletter/';
  558. $params = array(
  559. 'email' => $email,
  560. 'security' => $newsletterSecurity,
  561. 'community' => $newsletterCommunity,
  562. 'url' => Url::getCurrentUrlWithoutQueryString(),
  563. );
  564. if ($params['security'] == '1'
  565. || $params['community'] == '1'
  566. ) {
  567. if (!isset($params['security'])) {
  568. $params['security'] = '0';
  569. }
  570. if (!isset($params['community'])) {
  571. $params['community'] = '0';
  572. }
  573. $url .= '?' . http_build_query($params, '', '&');
  574. try {
  575. Http::sendHttpRequest($url, $timeout = 2);
  576. } catch (Exception $e) {
  577. // e.g., disable_functions = fsockopen; allow_url_open = Off
  578. }
  579. }
  580. }
  581. /**
  582. * @return array|bool
  583. */
  584. protected function updateComponents()
  585. {
  586. Access::getInstance();
  587. Piwik::setUserHasSuperUserAccess();
  588. $updater = new Updater();
  589. $componentsWithUpdateFile = CoreUpdater::getComponentUpdates($updater);
  590. if (empty($componentsWithUpdateFile)) {
  591. return false;
  592. }
  593. $result = CoreUpdater::updateComponents($updater, $componentsWithUpdateFile);
  594. return $result;
  595. }
  596. }