PageRenderTime 58ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/core/Plugin/Controller.php

https://github.com/CodeYellowBV/piwik
PHP | 922 lines | 505 code | 86 blank | 331 comment | 65 complexity | 8ac6a6cd840062043ab2595f8caa0c17 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\Plugin;
  10. use Exception;
  11. use Piwik\Access;
  12. use Piwik\API\Proxy;
  13. use Piwik\API\Request;
  14. use Piwik\Common;
  15. use Piwik\Config as PiwikConfig;
  16. use Piwik\Config;
  17. use Piwik\DataTable\Filter\CalculateEvolutionFilter;
  18. use Piwik\Date;
  19. use Piwik\FrontController;
  20. use Piwik\Menu\MenuTop;
  21. use Piwik\Menu\MenuUser;
  22. use Piwik\NoAccessException;
  23. use Piwik\Notification\Manager as NotificationManager;
  24. use Piwik\Period\Month;
  25. use Piwik\Period;
  26. use Piwik\Period\Range;
  27. use Piwik\Piwik;
  28. use Piwik\Plugins\CoreAdminHome\CustomLogo;
  29. use Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph\Evolution;
  30. use Piwik\Plugins\LanguagesManager\LanguagesManager;
  31. use Piwik\Plugins\UsersManager\UserPreferences;
  32. use Piwik\Registry;
  33. use Piwik\SettingsPiwik;
  34. use Piwik\Site;
  35. use Piwik\Url;
  36. use Piwik\View;
  37. use Piwik\View\ViewInterface;
  38. use Piwik\ViewDataTable\Factory as ViewDataTableFactory;
  39. /**
  40. * Base class of all plugin Controllers.
  41. *
  42. * Plugins that wish to add display HTML should create a Controller that either
  43. * extends from this class or from {@link ControllerAdmin}. Every public method in
  44. * the controller will be exposed as a controller method and can be invoked via
  45. * an HTTP request.
  46. *
  47. * Learn more about Piwik's MVC system [here](/guides/mvc-in-piwik).
  48. *
  49. * ### Examples
  50. *
  51. * **Defining a controller**
  52. *
  53. * class Controller extends \Piwik\Plugin\Controller
  54. * {
  55. * public function index()
  56. * {
  57. * $view = new View("@MyPlugin/index.twig");
  58. * // ... setup view ...
  59. * return $view->render();
  60. * }
  61. * }
  62. *
  63. * **Linking to a controller action**
  64. *
  65. * <a href="?module=MyPlugin&action=index&idSite=1&period=day&date=2013-10-10">Link</a>
  66. *
  67. */
  68. abstract class Controller
  69. {
  70. /**
  71. * The plugin name, eg. `'Referrers'`.
  72. *
  73. * @var string
  74. * @api
  75. */
  76. protected $pluginName;
  77. /**
  78. * The value of the **date** query parameter.
  79. *
  80. * @var string
  81. * @api
  82. */
  83. protected $strDate;
  84. /**
  85. * The Date object created with ($strDate)[#strDate] or null if the requested date is a range.
  86. *
  87. * @var Date|null
  88. * @api
  89. */
  90. protected $date;
  91. /**
  92. * The value of the **idSite** query parameter.
  93. *
  94. * @var int
  95. * @api
  96. */
  97. protected $idSite;
  98. /**
  99. * The Site object created with {@link $idSite}.
  100. *
  101. * @var Site
  102. * @api
  103. */
  104. protected $site = null;
  105. /**
  106. * Constructor.
  107. *
  108. * @api
  109. */
  110. public function __construct()
  111. {
  112. $this->init();
  113. }
  114. protected function init()
  115. {
  116. $aPluginName = explode('\\', get_class($this));
  117. $this->pluginName = $aPluginName[2];
  118. $date = Common::getRequestVar('date', 'yesterday', 'string');
  119. try {
  120. $this->idSite = Common::getRequestVar('idSite', false, 'int');
  121. $this->site = new Site($this->idSite);
  122. $date = $this->getDateParameterInTimezone($date, $this->site->getTimezone());
  123. $this->setDate($date);
  124. } catch (Exception $e) {
  125. // the date looks like YYYY-MM-DD,YYYY-MM-DD or other format
  126. $this->date = null;
  127. }
  128. }
  129. /**
  130. * Helper method that converts `"today"` or `"yesterday"` to the specified timezone.
  131. * If the date is absolute, ie. YYYY-MM-DD, it will not be converted to the timezone.
  132. *
  133. * @param string $date `'today'`, `'yesterday'`, `'YYYY-MM-DD'`
  134. * @param string $timezone The timezone to use.
  135. * @return Date
  136. * @api
  137. */
  138. protected function getDateParameterInTimezone($date, $timezone)
  139. {
  140. $timezoneToUse = null;
  141. // if the requested date is not YYYY-MM-DD, we need to ensure
  142. // it is relative to the website's timezone
  143. if (in_array($date, array('today', 'yesterday'))) {
  144. // today is at midnight; we really want to get the time now, so that
  145. // * if the website is UTC+12 and it is 5PM now in UTC, the calendar will allow to select the UTC "tomorrow"
  146. // * if the website is UTC-12 and it is 5AM now in UTC, the calendar will allow to select the UTC "yesterday"
  147. if ($date == 'today') {
  148. $date = 'now';
  149. } elseif ($date == 'yesterday') {
  150. $date = 'yesterdaySameTime';
  151. }
  152. $timezoneToUse = $timezone;
  153. }
  154. return Date::factory($date, $timezoneToUse);
  155. }
  156. /**
  157. * Sets the date to be used by all other methods in the controller.
  158. * If the date has to be modified, this method should be called just after
  159. * construction.
  160. *
  161. * @param Date $date The new Date.
  162. * @return void
  163. * @api
  164. */
  165. protected function setDate(Date $date)
  166. {
  167. $this->date = $date;
  168. $this->strDate = $date->toString();
  169. }
  170. /**
  171. * Returns values that are enabled for the parameter &period=
  172. * @return array eg. array('day', 'week', 'month', 'year', 'range')
  173. */
  174. protected static function getEnabledPeriodsInUI()
  175. {
  176. $periods = Config::getInstance()->General['enabled_periods_UI'];
  177. $periods = explode(",", $periods);
  178. $periods = array_map('trim', $periods);
  179. return $periods;
  180. }
  181. /**
  182. * @return array
  183. */
  184. private static function getEnabledPeriodsNames()
  185. {
  186. $availablePeriods = self::getEnabledPeriodsInUI();
  187. $periodNames = array(
  188. 'day' => array(
  189. 'singular' => Piwik::translate('CoreHome_PeriodDay'),
  190. 'plural' => Piwik::translate('CoreHome_PeriodDays')
  191. ),
  192. 'week' => array(
  193. 'singular' => Piwik::translate('CoreHome_PeriodWeek'),
  194. 'plural' => Piwik::translate('CoreHome_PeriodWeeks')
  195. ),
  196. 'month' => array(
  197. 'singular' => Piwik::translate('CoreHome_PeriodMonth'),
  198. 'plural' => Piwik::translate('CoreHome_PeriodMonths')
  199. ),
  200. 'year' => array(
  201. 'singular' => Piwik::translate('CoreHome_PeriodYear'),
  202. 'plural' => Piwik::translate('CoreHome_PeriodYears')
  203. ),
  204. // Note: plural is not used for date range
  205. 'range' => array(
  206. 'singular' => Piwik::translate('General_DateRangeInPeriodList'),
  207. 'plural' => Piwik::translate('General_DateRangeInPeriodList')
  208. ),
  209. );
  210. $periodNames = array_intersect_key($periodNames, array_fill_keys($availablePeriods, true));
  211. return $periodNames;
  212. }
  213. /**
  214. * Returns the name of the default method that will be called
  215. * when visiting: index.php?module=PluginName without the action parameter.
  216. *
  217. * @return string
  218. * @api
  219. */
  220. public function getDefaultAction()
  221. {
  222. return 'index';
  223. }
  224. /**
  225. * A helper method that renders a view either to the screen or to a string.
  226. *
  227. * @param ViewInterface $view The view to render.
  228. * @return string|void
  229. */
  230. protected function renderView(ViewInterface $view)
  231. {
  232. return $view->render();
  233. }
  234. /**
  235. * Convenience method that creates and renders a ViewDataTable for a API method.
  236. *
  237. * @param string $apiAction The name of the API action (eg, `'getResolution'`).
  238. * @param bool $controllerAction The name of the Controller action name that is rendering the report. Defaults
  239. * to the `$apiAction`.
  240. * @param bool $fetch If `true`, the rendered string is returned, if `false` it is `echo`'d.
  241. * @throws \Exception if `$pluginName` is not an existing plugin or if `$apiAction` is not an
  242. * existing method of the plugin's API.
  243. * @return string|void See `$fetch`.
  244. * @api
  245. */
  246. protected function renderReport($apiAction, $controllerAction = false)
  247. {
  248. $pluginName = $this->pluginName;
  249. /** @var Proxy $apiProxy */
  250. $apiProxy = Proxy::getInstance();
  251. if (!$apiProxy->isExistingApiAction($pluginName, $apiAction)) {
  252. throw new \Exception("Invalid action name '$apiAction' for '$pluginName' plugin.");
  253. }
  254. $apiAction = $apiProxy->buildApiActionName($pluginName, $apiAction);
  255. if ($controllerAction !== false) {
  256. $controllerAction = $pluginName . '.' . $controllerAction;
  257. }
  258. $view = ViewDataTableFactory::build(null, $apiAction, $controllerAction);
  259. $rendered = $view->render();
  260. return $rendered;
  261. }
  262. /**
  263. * Returns a ViewDataTable object that will render a jqPlot evolution graph
  264. * for the last30 days/weeks/etc. of the current period, relative to the current date.
  265. *
  266. * @param string $currentModuleName The name of the current plugin.
  267. * @param string $currentControllerAction The name of the action that renders the desired
  268. * report.
  269. * @param string $apiMethod The API method that the ViewDataTable will use to get
  270. * graph data.
  271. * @return ViewDataTable
  272. * @api
  273. */
  274. protected function getLastUnitGraph($currentModuleName, $currentControllerAction, $apiMethod)
  275. {
  276. $view = ViewDataTableFactory::build(
  277. Evolution::ID, $apiMethod, $currentModuleName . '.' . $currentControllerAction, $forceDefault = true);
  278. $view->config->show_goals = false;
  279. return $view;
  280. }
  281. /**
  282. * Same as {@link getLastUnitGraph()}, but will set some properties of the ViewDataTable
  283. * object based on the arguments supplied.
  284. *
  285. * @param string $currentModuleName The name of the current plugin.
  286. * @param string $currentControllerAction The name of the action that renders the desired
  287. * report.
  288. * @param array $columnsToDisplay The value to use for the ViewDataTable's columns_to_display config
  289. * property.
  290. * @param array $selectableColumns The value to use for the ViewDataTable's selectable_columns config
  291. * property.
  292. * @param bool|string $reportDocumentation The value to use for the ViewDataTable's documentation config
  293. * property.
  294. * @param string $apiMethod The API method that the ViewDataTable will use to get graph data.
  295. * @return ViewDataTable
  296. * @api
  297. */
  298. protected function getLastUnitGraphAcrossPlugins($currentModuleName, $currentControllerAction, $columnsToDisplay = false,
  299. $selectableColumns = array(), $reportDocumentation = false,
  300. $apiMethod = 'API.get')
  301. {
  302. // load translations from meta data
  303. $idSite = Common::getRequestVar('idSite');
  304. $period = Common::getRequestVar('period');
  305. $date = Common::getRequestVar('date');
  306. $meta = \Piwik\Plugins\API\API::getInstance()->getReportMetadata($idSite, $period, $date);
  307. $columns = array_merge($columnsToDisplay ? $columnsToDisplay : array(), $selectableColumns);
  308. $translations = array_combine($columns, $columns);
  309. foreach ($meta as $reportMeta) {
  310. if ($reportMeta['action'] == 'get' && !isset($reportMeta['parameters'])) {
  311. foreach ($columns as $column) {
  312. if (isset($reportMeta['metrics'][$column])) {
  313. $translations[$column] = $reportMeta['metrics'][$column];
  314. }
  315. }
  316. }
  317. }
  318. // initialize the graph and load the data
  319. $view = $this->getLastUnitGraph($currentModuleName, $currentControllerAction, $apiMethod);
  320. if ($columnsToDisplay !== false) {
  321. $view->config->columns_to_display = $columnsToDisplay;
  322. }
  323. if (property_exists($view->config, 'selectable_columns')) {
  324. $view->config->selectable_columns = array_merge($view->config->selectable_columns ? : array(), $selectableColumns);
  325. }
  326. $view->config->translations += $translations;
  327. if ($reportDocumentation) {
  328. $view->config->documentation = $reportDocumentation;
  329. }
  330. return $view;
  331. }
  332. /**
  333. * Returns the array of new processed parameters once the parameters are applied.
  334. * For example: if you set range=last30 and date=2008-03-10,
  335. * the date element of the returned array will be "2008-02-10,2008-03-10"
  336. *
  337. * Parameters you can set:
  338. * - range: last30, previous10, etc.
  339. * - date: YYYY-MM-DD, today, yesterday
  340. * - period: day, week, month, year
  341. *
  342. * @param array $paramsToSet array( 'date' => 'last50', 'viewDataTable' =>'sparkline' )
  343. * @throws \Piwik\NoAccessException
  344. * @return array
  345. */
  346. protected function getGraphParamsModified($paramsToSet = array())
  347. {
  348. if (!isset($paramsToSet['period'])) {
  349. $period = Common::getRequestVar('period');
  350. } else {
  351. $period = $paramsToSet['period'];
  352. }
  353. if ($period == 'range') {
  354. return $paramsToSet;
  355. }
  356. if (!isset($paramsToSet['range'])) {
  357. $range = 'last30';
  358. } else {
  359. $range = $paramsToSet['range'];
  360. }
  361. if (!isset($paramsToSet['date'])) {
  362. $endDate = $this->strDate;
  363. } else {
  364. $endDate = $paramsToSet['date'];
  365. }
  366. if (is_null($this->site)) {
  367. throw new NoAccessException("Website not initialized, check that you are logged in and/or using the correct token_auth.");
  368. }
  369. $paramDate = Range::getRelativeToEndDate($period, $range, $endDate, $this->site);
  370. $params = array_merge($paramsToSet, array('date' => $paramDate));
  371. return $params;
  372. }
  373. /**
  374. * Returns a numeric value from the API.
  375. * Works only for API methods that originally returns numeric values (there is no cast here)
  376. *
  377. * @param string $methodToCall Name of method to call, eg. Referrers.getNumberOfDistinctSearchEngines
  378. * @param bool|string $date A custom date to use when getting the value. If false, the 'date' query
  379. * parameter is used.
  380. *
  381. * @return int|float
  382. */
  383. protected function getNumericValue($methodToCall, $date = false)
  384. {
  385. $params = $date === false ? array() : array('date' => $date);
  386. $return = Request::processRequest($methodToCall, $params);
  387. $columns = $return->getFirstRow()->getColumns();
  388. return reset($columns);
  389. }
  390. /**
  391. * Returns a URL to a sparkline image for a report served by the current plugin.
  392. *
  393. * The result of this URL should be used with the [sparkline()](/api-reference/Piwik/View#twig) twig function.
  394. *
  395. * The current site ID and period will be used.
  396. *
  397. * @param string $action Method name of the controller that serves the report.
  398. * @param array $customParameters The array of query parameter name/value pairs that
  399. * should be set in result URL.
  400. * @return string The generated URL.
  401. * @api
  402. */
  403. protected function getUrlSparkline($action, $customParameters = array())
  404. {
  405. $params = $this->getGraphParamsModified(
  406. array('viewDataTable' => 'sparkline',
  407. 'action' => $action,
  408. 'module' => $this->pluginName)
  409. + $customParameters
  410. );
  411. // convert array values to comma separated
  412. foreach ($params as &$value) {
  413. if (is_array($value)) {
  414. $value = rawurlencode(implode(',', $value));
  415. }
  416. }
  417. $url = Url::getCurrentQueryStringWithParametersModified($params);
  418. return $url;
  419. }
  420. /**
  421. * Sets the first date available in the period selector's calendar.
  422. *
  423. * @param Date $minDate The min date.
  424. * @param View $view The view that contains the period selector.
  425. * @api
  426. */
  427. protected function setMinDateView(Date $minDate, $view)
  428. {
  429. $view->minDateYear = $minDate->toString('Y');
  430. $view->minDateMonth = $minDate->toString('m');
  431. $view->minDateDay = $minDate->toString('d');
  432. }
  433. /**
  434. * Sets the last date available in the period selector's calendar. Usually this is just the "today" date
  435. * for a site (which varies based on the timezone of a site).
  436. *
  437. * @param Date $maxDate The max date.
  438. * @param View $view The view that contains the period selector.
  439. * @api
  440. */
  441. protected function setMaxDateView(Date $maxDate, $view)
  442. {
  443. $view->maxDateYear = $maxDate->toString('Y');
  444. $view->maxDateMonth = $maxDate->toString('m');
  445. $view->maxDateDay = $maxDate->toString('d');
  446. }
  447. /**
  448. * Assigns variables to {@link Piwik\View} instances that display an entire page.
  449. *
  450. * The following variables assigned:
  451. *
  452. * **date** - The value of the **date** query parameter.
  453. * **idSite** - The value of the **idSite** query parameter.
  454. * **rawDate** - The value of the **date** query parameter.
  455. * **prettyDate** - A pretty string description of the current period.
  456. * **siteName** - The current site's name.
  457. * **siteMainUrl** - The URL of the current site.
  458. * **startDate** - The start date of the current period. A {@link Piwik\Date} instance.
  459. * **endDate** - The end date of the current period. A {@link Piwik\Date} instance.
  460. * **language** - The current language's language code.
  461. * **config_action_url_category_delimiter** - The value of the `[General] action_url_category_delimiter`
  462. * INI config option.
  463. * **topMenu** - The result of `MenuTop::getInstance()->getMenu()`.
  464. *
  465. * As well as the variables set by {@link setPeriodVariablesView()}.
  466. *
  467. * Will exit on error.
  468. *
  469. * @param View $view
  470. * @return void
  471. * @api
  472. */
  473. protected function setGeneralVariablesView($view)
  474. {
  475. $view->date = $this->strDate;
  476. try {
  477. $view->idSite = $this->idSite;
  478. if (empty($this->site) || empty($this->idSite)) {
  479. throw new Exception("The requested website idSite is not found in the request, or is invalid.
  480. Please check that you are logged in Piwik and have permission to access the specified website.");
  481. }
  482. $this->setPeriodVariablesView($view);
  483. $rawDate = Common::getRequestVar('date');
  484. $periodStr = Common::getRequestVar('period');
  485. if ($periodStr != 'range') {
  486. $date = Date::factory($this->strDate);
  487. $period = Period\Factory::build($periodStr, $date);
  488. } else {
  489. $period = new Range($periodStr, $rawDate, $this->site->getTimezone());
  490. }
  491. $view->rawDate = $rawDate;
  492. $view->prettyDate = self::getCalendarPrettyDate($period);
  493. $view->siteName = $this->site->getName();
  494. $view->siteMainUrl = $this->site->getMainUrl();
  495. $datetimeMinDate = $this->site->getCreationDate()->getDatetime();
  496. $minDate = Date::factory($datetimeMinDate, $this->site->getTimezone());
  497. $this->setMinDateView($minDate, $view);
  498. $maxDate = Date::factory('now', $this->site->getTimezone());
  499. $this->setMaxDateView($maxDate, $view);
  500. // Setting current period start & end dates, for pre-setting the calendar when "Date Range" is selected
  501. $dateStart = $period->getDateStart();
  502. if ($dateStart->isEarlier($minDate)) {
  503. $dateStart = $minDate;
  504. }
  505. $dateEnd = $period->getDateEnd();
  506. if ($dateEnd->isLater($maxDate)) {
  507. $dateEnd = $maxDate;
  508. }
  509. $view->startDate = $dateStart;
  510. $view->endDate = $dateEnd;
  511. $language = LanguagesManager::getLanguageForSession();
  512. $view->language = !empty($language) ? $language : LanguagesManager::getLanguageCodeForCurrentUser();
  513. $this->setBasicVariablesView($view);
  514. $view->topMenu = MenuTop::getInstance()->getMenu();
  515. $view->userMenu = MenuUser::getInstance()->getMenu();
  516. $notifications = $view->notifications;
  517. if (empty($notifications)) {
  518. $view->notifications = NotificationManager::getAllNotificationsToDisplay();
  519. NotificationManager::cancelAllNonPersistent();
  520. }
  521. } catch (Exception $e) {
  522. Piwik_ExitWithMessage($e->getMessage(), $e->getTraceAsString());
  523. }
  524. }
  525. /**
  526. * Assigns a set of generally useful variables to a {@link Piwik\View} instance.
  527. *
  528. * The following variables assigned:
  529. *
  530. * **enableMeasurePiwikForSiteId** - The value of the `[Debug] enable_measure_piwik_usage_in_idsite`
  531. * INI config option.
  532. * **isSuperUser** - True if the current user is the Super User, false if otherwise.
  533. * **hasSomeAdminAccess** - True if the current user has admin access to at least one site,
  534. * false if otherwise.
  535. * **isCustomLogo** - The value of the `branding_use_custom_logo` option.
  536. * **logoHeader** - The header logo URL to use.
  537. * **logoLarge** - The large logo URL to use.
  538. * **logoSVG** - The SVG logo URL to use.
  539. * **hasSVGLogo** - True if there is a SVG logo, false if otherwise.
  540. * **enableFrames** - The value of the `[General] enable_framed_pages` INI config option. If
  541. * true, {@link Piwik\View::setXFrameOptions()} is called on the view.
  542. *
  543. * Also calls {@link setHostValidationVariablesView()}.
  544. *
  545. * @param View $view
  546. * @api
  547. */
  548. protected function setBasicVariablesView($view)
  549. {
  550. $view->clientSideConfig = PiwikConfig::getInstance()->getClientSideOptions();
  551. $view->enableMeasurePiwikForSiteId = PiwikConfig::getInstance()->Debug['enable_measure_piwik_usage_in_idsite'];
  552. $view->isSuperUser = Access::getInstance()->hasSuperUserAccess();
  553. $view->hasSomeAdminAccess = Piwik::isUserHasSomeAdminAccess();
  554. $view->hasSomeViewAccess = Piwik::isUserHasSomeViewAccess();
  555. $view->isUserIsAnonymous = Piwik::isUserIsAnonymous();
  556. $view->hasSuperUserAccess = Piwik::hasUserSuperUserAccess();
  557. $this->addCustomLogoInfo($view);
  558. $view->logoHeader = \Piwik\Plugins\API\API::getInstance()->getHeaderLogoUrl();
  559. $view->logoLarge = \Piwik\Plugins\API\API::getInstance()->getLogoUrl();
  560. $view->logoSVG = \Piwik\Plugins\API\API::getInstance()->getSVGLogoUrl();
  561. $view->hasSVGLogo = \Piwik\Plugins\API\API::getInstance()->hasSVGLogo();
  562. $view->superUserEmails = implode(',', Piwik::getAllSuperUserAccessEmailAddresses());
  563. $general = PiwikConfig::getInstance()->General;
  564. $view->enableFrames = $general['enable_framed_pages']
  565. || (isset($general['enable_framed_logins']) && $general['enable_framed_logins']);
  566. if (!$view->enableFrames) {
  567. $view->setXFrameOptions('sameorigin');
  568. }
  569. self::setHostValidationVariablesView($view);
  570. }
  571. protected function addCustomLogoInfo($view)
  572. {
  573. $customLogo = new CustomLogo();
  574. $view->isCustomLogo = $customLogo->isEnabled();
  575. $view->customFavicon = $customLogo->getPathUserFavicon();
  576. }
  577. /**
  578. * Checks if the current host is valid and sets variables on the given view, including:
  579. *
  580. * - **isValidHost** - true if host is valid, false if otherwise
  581. * - **invalidHostMessage** - message to display if host is invalid (only set if host is invalid)
  582. * - **invalidHost** - the invalid hostname (only set if host is invalid)
  583. * - **mailLinkStart** - the open tag of a link to email the Super User of this problem (only set
  584. * if host is invalid)
  585. *
  586. * @param View $view
  587. * @api
  588. */
  589. public static function setHostValidationVariablesView($view)
  590. {
  591. // check if host is valid
  592. $view->isValidHost = Url::isValidHost();
  593. if (!$view->isValidHost) {
  594. // invalid host, so display warning to user
  595. $validHosts = Url::getTrustedHostsFromConfig();
  596. $validHost = $validHosts[0];
  597. $invalidHost = Common::sanitizeInputValue($_SERVER['HTTP_HOST']);
  598. $emailSubject = rawurlencode(Piwik::translate('CoreHome_InjectedHostEmailSubject', $invalidHost));
  599. $emailBody = rawurlencode(Piwik::translate('CoreHome_InjectedHostEmailBody'));
  600. $superUserEmail = implode(',', Piwik::getAllSuperUserAccessEmailAddresses());
  601. $mailToUrl = "mailto:$superUserEmail?subject=$emailSubject&body=$emailBody";
  602. $mailLinkStart = "<a href=\"$mailToUrl\">";
  603. $invalidUrl = Url::getCurrentUrlWithoutQueryString($checkIfTrusted = false);
  604. $validUrl = Url::getCurrentScheme() . '://' . $validHost
  605. . Url::getCurrentScriptName();
  606. $invalidUrl = Common::sanitizeInputValue($invalidUrl);
  607. $validUrl = Common::sanitizeInputValue($validUrl);
  608. $changeTrustedHostsUrl = "index.php"
  609. . Url::getCurrentQueryStringWithParametersModified(array(
  610. 'module' => 'CoreAdminHome',
  611. 'action' => 'generalSettings'
  612. ))
  613. . "#trustedHostsSection";
  614. $warningStart = Piwik::translate('CoreHome_InjectedHostWarningIntro', array(
  615. '<strong>' . $invalidUrl . '</strong>',
  616. '<strong>' . $validUrl . '</strong>'
  617. )) . ' <br/>';
  618. if (Piwik::hasUserSuperUserAccess()) {
  619. $view->invalidHostMessage = $warningStart . ' '
  620. . Piwik::translate('CoreHome_InjectedHostSuperUserWarning', array(
  621. "<a href=\"$changeTrustedHostsUrl\">",
  622. $invalidHost,
  623. '</a>',
  624. "<br/><a href=\"$validUrl\">",
  625. $validHost,
  626. '</a>'
  627. ));
  628. } else if (Piwik::isUserIsAnonymous()) {
  629. $view->invalidHostMessage = $warningStart . ' '
  630. . Piwik::translate('CoreHome_InjectedHostNonSuperUserWarning', array(
  631. "<br/><a href=\"$validUrl\">",
  632. '</a>',
  633. '<span style="display:none">',
  634. '</span>'
  635. ));
  636. } else {
  637. $view->invalidHostMessage = $warningStart . ' '
  638. . Piwik::translate('CoreHome_InjectedHostNonSuperUserWarning', array(
  639. "<br/><a href=\"$validUrl\">",
  640. '</a>',
  641. $mailLinkStart,
  642. '</a>'
  643. ));
  644. }
  645. $view->invalidHostMessageHowToFix = '<p><b>How do I fix this problem and how do I login again?</b><br/> The Piwik Super User can manually edit the file piwik/config/config.ini.php
  646. and add the following lines: <pre>[General]' . "\n" . 'trusted_hosts[] = "' . $invalidHost . '"</pre>After making the change, you will be able to login again.</p>
  647. <p>You may also <i>disable this security feature (not recommended)</i>. To do so edit config/config.ini.php and add:
  648. <pre>[General]' . "\n" . 'enable_trusted_host_check=0</pre>';
  649. $view->invalidHost = $invalidHost; // for UserSettings warning
  650. $view->invalidHostMailLinkStart = $mailLinkStart;
  651. }
  652. }
  653. /**
  654. * Sets general period variables on a view, including:
  655. *
  656. * - **displayUniqueVisitors** - Whether unique visitors should be displayed for the current
  657. * period.
  658. * - **period** - The value of the **period** query parameter.
  659. * - **otherPeriods** - `array('day', 'week', 'month', 'year', 'range')`
  660. * - **periodsNames** - List of available periods mapped to their singular and plural translations.
  661. *
  662. * @param View $view
  663. * @throws Exception if the current period is invalid.
  664. * @api
  665. */
  666. public static function setPeriodVariablesView($view)
  667. {
  668. if (isset($view->period)) {
  669. return;
  670. }
  671. $currentPeriod = Common::getRequestVar('period');
  672. $view->displayUniqueVisitors = SettingsPiwik::isUniqueVisitorsEnabled($currentPeriod);
  673. $availablePeriods = self::getEnabledPeriodsInUI();
  674. if (!in_array($currentPeriod, $availablePeriods)) {
  675. throw new Exception("Period must be one of: " . implode(", ", $availablePeriods));
  676. }
  677. $found = array_search($currentPeriod, $availablePeriods);
  678. unset($availablePeriods[$found]);
  679. $view->period = $currentPeriod;
  680. $view->otherPeriods = $availablePeriods;
  681. $view->periodsNames = self::getEnabledPeriodsNames();
  682. }
  683. /**
  684. * Helper method used to redirect the current HTTP request to another module/action.
  685. *
  686. * This function will exit immediately after executing.
  687. *
  688. * @param string $moduleToRedirect The plugin to redirect to, eg. `"MultiSites"`.
  689. * @param string $actionToRedirect Action, eg. `"index"`.
  690. * @param int|null $websiteId The new idSite query parameter, eg, `1`.
  691. * @param string|null $defaultPeriod The new period query parameter, eg, `'day'`.
  692. * @param string|null $defaultDate The new date query parameter, eg, `'today'`.
  693. * @param array $parameters Other query parameters to append to the URL.
  694. * @api
  695. */
  696. public function redirectToIndex($moduleToRedirect, $actionToRedirect, $websiteId = null, $defaultPeriod = null,
  697. $defaultDate = null, $parameters = array())
  698. {
  699. $userPreferences = new UserPreferences();
  700. if (empty($websiteId)) {
  701. $websiteId = $userPreferences->getDefaultWebsiteId();
  702. }
  703. if (empty($defaultDate)) {
  704. $defaultDate = $userPreferences->getDefaultDate();
  705. }
  706. if (empty($defaultPeriod)) {
  707. $defaultPeriod = $userPreferences->getDefaultPeriod();
  708. }
  709. $parametersString = '';
  710. if (!empty($parameters)) {
  711. $parametersString = '&' . Url::getQueryStringFromParameters($parameters);
  712. }
  713. if ($websiteId) {
  714. $url = "index.php?module=" . $moduleToRedirect
  715. . "&action=" . $actionToRedirect
  716. . "&idSite=" . $websiteId
  717. . "&period=" . $defaultPeriod
  718. . "&date=" . $defaultDate
  719. . $parametersString;
  720. Url::redirectToUrl($url);
  721. exit;
  722. }
  723. if (Piwik::hasUserSuperUserAccess()) {
  724. Piwik_ExitWithMessage("Error: no website was found in this Piwik installation.
  725. <br />Check the table '" . Common::prefixTable('site') . "' in your database, it should contain your Piwik websites.", false, true);
  726. }
  727. $currentLogin = Piwik::getCurrentUserLogin();
  728. if (!empty($currentLogin)
  729. && $currentLogin != 'anonymous'
  730. ) {
  731. $emails = implode(',', Piwik::getAllSuperUserAccessEmailAddresses());
  732. $errorMessage = sprintf(Piwik::translate('CoreHome_NoPrivilegesAskPiwikAdmin'), $currentLogin, "<br/><a href='mailto:" . $emails . "?subject=Access to Piwik for user $currentLogin'>", "</a>");
  733. $errorMessage .= "<br /><br />&nbsp;&nbsp;&nbsp;<b><a href='index.php?module=" . Registry::get('auth')->getName() . "&amp;action=logout'>&rsaquo; " . Piwik::translate('General_Logout') . "</a></b><br />";
  734. Piwik_ExitWithMessage($errorMessage, false, true);
  735. }
  736. echo FrontController::getInstance()->dispatch(Piwik::getLoginPluginName(), false);
  737. exit;
  738. }
  739. /**
  740. * Checks that the token_auth in the URL matches the currently logged-in user's token_auth.
  741. *
  742. * This is a protection against CSRF and should be used in all controller
  743. * methods that modify Piwik or any user settings.
  744. *
  745. * **The token_auth should never appear in the browser's address bar.**
  746. *
  747. * @throws \Piwik\NoAccessException If the token doesn't match.
  748. * @api
  749. */
  750. protected function checkTokenInUrl()
  751. {
  752. if (Common::getRequestVar('token_auth', false) != Piwik::getCurrentUserTokenAuth()) {
  753. throw new NoAccessException(Piwik::translate('General_ExceptionInvalidToken'));
  754. }
  755. }
  756. /**
  757. * Returns a prettified date string for use in period selector widget.
  758. *
  759. * @param Period $period The period to return a pretty string for.
  760. * @return string
  761. * @api
  762. */
  763. public static function getCalendarPrettyDate($period)
  764. {
  765. if ($period instanceof Month) // show month name when period is for a month
  766. {
  767. return $period->getLocalizedLongString();
  768. } else {
  769. return $period->getPrettyString();
  770. }
  771. }
  772. /**
  773. * Returns the pretty date representation
  774. *
  775. * @param $date string
  776. * @param $period string
  777. * @return string Pretty date
  778. */
  779. public static function getPrettyDate($date, $period)
  780. {
  781. return self::getCalendarPrettyDate(Period\Factory::build($period, Date::factory($date)));
  782. }
  783. /**
  784. * Calculates the evolution from one value to another and returns HTML displaying
  785. * the evolution percent. The HTML includes an up/down arrow and is colored red, black or
  786. * green depending on whether the evolution is negative, 0 or positive.
  787. *
  788. * No HTML is returned if the current value and evolution percent are both 0.
  789. *
  790. * @param string $date The date of the current value.
  791. * @param int $currentValue The value to calculate evolution to.
  792. * @param string $pastDate The date of past value.
  793. * @param int $pastValue The value in the past to calculate evolution from.
  794. * @return string|false The HTML or `false` if the evolution is 0 and the current value is 0.
  795. * @api
  796. */
  797. protected function getEvolutionHtml($date, $currentValue, $pastDate, $pastValue)
  798. {
  799. $evolutionPercent = CalculateEvolutionFilter::calculate(
  800. $currentValue, $pastValue, $precision = 1);
  801. // do not display evolution if evolution percent is 0 and current value is 0
  802. if ($evolutionPercent == 0
  803. && $currentValue == 0
  804. ) {
  805. return false;
  806. }
  807. $titleEvolutionPercent = $evolutionPercent;
  808. if ($evolutionPercent < 0) {
  809. $class = "negative-evolution";
  810. $img = "arrow_down.png";
  811. } else if ($evolutionPercent == 0) {
  812. $class = "neutral-evolution";
  813. $img = "stop.png";
  814. } else {
  815. $class = "positive-evolution";
  816. $img = "arrow_up.png";
  817. $titleEvolutionPercent = '+' . $titleEvolutionPercent;
  818. }
  819. $title = Piwik::translate('General_EvolutionSummaryGeneric', array(
  820. Piwik::translate('General_NVisits', $currentValue),
  821. $date,
  822. Piwik::translate('General_NVisits', $pastValue),
  823. $pastDate,
  824. $titleEvolutionPercent
  825. ));
  826. $result = '<span class="metricEvolution" title="' . $title
  827. . '"><img style="padding-right:4px" src="plugins/MultiSites/images/' . $img . '"/><strong';
  828. if (isset($class)) {
  829. $result .= ' class="' . $class . '"';
  830. }
  831. $result .= '>' . $evolutionPercent . '</strong></span>';
  832. return $result;
  833. }
  834. }