PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 4.3.0
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v4.3.0
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / Plugin / Controller.php
matomo / app / core / Plugin Last commit date
Dimension 5 years ago API.php 5 years ago AggregatedMetric.php 5 years ago ArchivedMetric.php 5 years ago Archiver.php 5 years ago Categories.php 5 years ago ComponentFactory.php 5 years ago ComputedMetric.php 5 years ago ConsoleCommand.php 5 years ago Controller.php 5 years ago ControllerAdmin.php 5 years ago Dependency.php 5 years ago LogTablesProvider.php 5 years ago Manager.php 5 years ago Menu.php 5 years ago MetadataLoader.php 5 years ago Metric.php 5 years ago PluginException.php 5 years ago ProcessedMetric.php 5 years ago ReleaseChannels.php 5 years ago Report.php 5 years ago ReportsProvider.php 5 years ago RequestProcessors.php 5 years ago Segment.php 5 years ago SettingsProvider.php 5 years ago Tasks.php 5 years ago ThemeStyles.php 5 years ago ViewDataTable.php 5 years ago Visualization.php 5 years ago WidgetsProvider.php 5 years ago
Controller.php
1081 lines
1 <?php
2 /**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 *
8 */
9 namespace Piwik\Plugin;
10
11 use Exception;
12 use Piwik\Access;
13 use Piwik\API\Proxy;
14 use Piwik\API\Request;
15 use Piwik\Common;
16 use Piwik\Config as PiwikConfig;
17 use Piwik\Container\StaticContainer;
18 use Piwik\Date;
19 use Piwik\Exception\NoPrivilegesException;
20 use Piwik\Exception\NoWebsiteFoundException;
21 use Piwik\FrontController;
22 use Piwik\Menu\MenuAdmin;
23 use Piwik\Menu\MenuTop;
24 use Piwik\NoAccessException;
25 use Piwik\Notification\Manager as NotificationManager;
26 use Piwik\Period\Month;
27 use Piwik\Period;
28 use Piwik\Period\PeriodValidator;
29 use Piwik\Period\Range;
30 use Piwik\Piwik;
31 use Piwik\Plugins\CoreAdminHome\CustomLogo;
32 use Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph\Evolution;
33 use Piwik\Plugins\LanguagesManager\LanguagesManager;
34 use Piwik\SettingsPiwik;
35 use Piwik\Site;
36 use Piwik\Url;
37 use Piwik\Plugin;
38 use Piwik\View;
39 use Piwik\View\ViewInterface;
40 use Piwik\ViewDataTable\Factory as ViewDataTableFactory;
41
42 /**
43 * Base class of all plugin Controllers.
44 *
45 * Plugins that wish to add display HTML should create a Controller that either
46 * extends from this class or from {@link ControllerAdmin}. Every public method in
47 * the controller will be exposed as a controller method and can be invoked via
48 * an HTTP request.
49 *
50 * Learn more about Piwik's MVC system [here](/guides/mvc-in-piwik).
51 *
52 * ### Examples
53 *
54 * **Defining a controller**
55 *
56 * class Controller extends \Piwik\Plugin\Controller
57 * {
58 * public function index()
59 * {
60 * $view = new View("@MyPlugin/index.twig");
61 * // ... setup view ...
62 * return $view->render();
63 * }
64 * }
65 *
66 * **Linking to a controller action**
67 *
68 * <a href="?module=MyPlugin&action=index&idSite=1&period=day&date=2013-10-10">Link</a>
69 *
70 */
71 abstract class Controller
72 {
73 /**
74 * The plugin name, eg. `'Referrers'`.
75 *
76 * @var string
77 * @api
78 */
79 protected $pluginName;
80
81 /**
82 * The value of the **date** query parameter.
83 *
84 * @var string
85 * @api
86 */
87 protected $strDate;
88
89 /**
90 * The Date object created with ($strDate)[#strDate] or null if the requested date is a range.
91 *
92 * @var Date|null
93 * @api
94 */
95 protected $date;
96
97 /**
98 * The value of the **idSite** query parameter.
99 *
100 * @var int
101 * @api
102 */
103 protected $idSite;
104
105 /**
106 * The Site object created with {@link $idSite}.
107 *
108 * @var Site
109 * @api
110 */
111 protected $site = null;
112
113 /**
114 * Constructor.
115 *
116 * @api
117 */
118 public function __construct()
119 {
120 $this->init();
121 }
122
123 protected function init()
124 {
125 $aPluginName = explode('\\', get_class($this));
126 $this->pluginName = $aPluginName[2];
127
128 $date = Common::getRequestVar('date', 'yesterday', 'string');
129 try {
130 $this->idSite = Common::getRequestVar('idSite', false, 'int');
131 $this->site = new Site($this->idSite);
132 $date = $this->getDateParameterInTimezone($date, $this->site->getTimezone());
133 $this->setDate($date);
134 } catch (Exception $e) {
135 // the date looks like YYYY-MM-DD,YYYY-MM-DD or other format
136 $this->date = null;
137 }
138 }
139
140 /**
141 * Helper method that converts `"today"` or `"yesterday"` to the specified timezone.
142 * If the date is absolute, ie. YYYY-MM-DD, it will not be converted to the timezone.
143 *
144 * @param string $date `'today'`, `'yesterday'`, `'YYYY-MM-DD'`
145 * @param string $timezone The timezone to use.
146 * @return Date
147 * @api
148 */
149 protected function getDateParameterInTimezone($date, $timezone)
150 {
151 $timezoneToUse = null;
152 // if the requested date is not YYYY-MM-DD, we need to ensure
153 // it is relative to the website's timezone
154 if (in_array($date, array('today', 'yesterday'))) {
155 // today is at midnight; we really want to get the time now, so that
156 // * if the website is UTC+12 and it is 5PM now in UTC, the calendar will allow to select the UTC "tomorrow"
157 // * if the website is UTC-12 and it is 5AM now in UTC, the calendar will allow to select the UTC "yesterday"
158 if ($date === 'today') {
159 $date = 'now';
160 } elseif ($date === 'yesterday') {
161 $date = 'yesterdaySameTime';
162 }
163 $timezoneToUse = $timezone;
164 }
165 return Date::factory($date, $timezoneToUse);
166 }
167
168 /**
169 * Sets the date to be used by all other methods in the controller.
170 * If the date has to be modified, this method should be called just after
171 * construction.
172 *
173 * @param Date $date The new Date.
174 * @return void
175 * @api
176 */
177 protected function setDate(Date $date)
178 {
179 $this->date = $date;
180 $this->strDate = $date->toString();
181 }
182
183 /**
184 * Returns values that are enabled for the parameter &period=
185 * @return array eg. array('day', 'week', 'month', 'year', 'range')
186 */
187 protected static function getEnabledPeriodsInUI()
188 {
189 $periodValidator = new PeriodValidator();
190 return $periodValidator->getPeriodsAllowedForUI();
191 }
192
193 /**
194 * @return array
195 */
196 private static function getEnabledPeriodsNames()
197 {
198 $availablePeriods = self::getEnabledPeriodsInUI();
199 $periodNames = array(
200 'day' => array(
201 'singular' => Piwik::translate('Intl_PeriodDay'),
202 'plural' => Piwik::translate('Intl_PeriodDays')
203 ),
204 'week' => array(
205 'singular' => Piwik::translate('Intl_PeriodWeek'),
206 'plural' => Piwik::translate('Intl_PeriodWeeks')
207 ),
208 'month' => array(
209 'singular' => Piwik::translate('Intl_PeriodMonth'),
210 'plural' => Piwik::translate('Intl_PeriodMonths')
211 ),
212 'year' => array(
213 'singular' => Piwik::translate('Intl_PeriodYear'),
214 'plural' => Piwik::translate('Intl_PeriodYears')
215 ),
216 // Note: plural is not used for date range
217 'range' => array(
218 'singular' => Piwik::translate('General_DateRangeInPeriodList'),
219 'plural' => Piwik::translate('General_DateRangeInPeriodList')
220 ),
221 );
222
223 $periodNames = array_intersect_key($periodNames, array_fill_keys($availablePeriods, true));
224 return $periodNames;
225 }
226
227 /**
228 * Returns the name of the default method that will be called
229 * when visiting: index.php?module=PluginName without the action parameter.
230 *
231 * @return string
232 * @api
233 */
234 public function getDefaultAction()
235 {
236 return 'index';
237 }
238
239 /**
240 * A helper method that renders a view either to the screen or to a string.
241 *
242 * @param ViewInterface $view The view to render.
243 * @return string|void
244 */
245 protected function renderView(ViewInterface $view)
246 {
247 return $view->render();
248 }
249
250 /**
251 * Assigns the given variables to the template and renders it.
252 *
253 * Example:
254 *
255 * public function myControllerAction () {
256 * return $this->renderTemplate('index', array(
257 * 'answerToLife' => '42'
258 * ));
259 * }
260 *
261 * This will render the 'index.twig' file within the plugin templates folder and assign the view variable
262 * `answerToLife` to `42`.
263 *
264 * @param string $template The name of the template file. If only a name is given it will automatically use
265 * the template within the plugin folder. For instance 'myTemplate' will result in
266 * '@$pluginName/myTemplate.twig'. Alternatively you can include the full path:
267 * '@anyOtherFolder/otherTemplate'. The trailing '.twig' is not needed.
268 * @param array $variables For instance array('myViewVar' => 'myValue'). In template you can use {{ myViewVar }}
269 * @return string
270 * @since 2.5.0
271 * @api
272 */
273 protected function renderTemplate($template, array $variables = [])
274 {
275 return $this->renderTemplateAs($template, $variables);
276 }
277
278 /**
279 * @see {self::renderTemplate()}
280 *
281 * @param $template
282 * @param array $variables
283 * @param string|null $viewType 'basic' or 'admin'. If null, determined based on the controller instance type.
284 * @return string
285 * @throws Exception
286 */
287 protected function renderTemplateAs($template, array $variables = array(), $viewType = null)
288 {
289 if (false === strpos($template, '@') || false === strpos($template, '/')) {
290 $template = '@' . $this->pluginName . '/' . $template;
291 }
292
293 $view = new View($template);
294
295 $this->checkViewType($viewType);
296
297 if (empty($viewType)) {
298 $viewType = $this instanceof ControllerAdmin ? 'admin' : 'basic';
299 }
300
301 // alternatively we could check whether the templates extends either admin.twig or dashboard.twig and based on
302 // that call the correct method. This will be needed once we unify Controller and ControllerAdmin see
303 // https://github.com/piwik/piwik/issues/6151
304 if ($this instanceof ControllerAdmin && $viewType === 'admin') {
305 $this->setBasicVariablesViewAs($view, $viewType);
306 } elseif (empty($this->site) || empty($this->idSite)) {
307 $this->setBasicVariablesViewAs($view, $viewType);
308 } else {
309 $this->setGeneralVariablesViewAs($view, $viewType);
310 }
311
312 foreach ($variables as $key => $value) {
313 $view->$key = $value;
314 }
315
316 if (isset($view->siteName)) {
317 $view->siteNameDecoded = Common::unsanitizeInputValue($view->siteName);
318 }
319
320 return $view->render();
321 }
322
323 /**
324 * Convenience method that creates and renders a ViewDataTable for a API method.
325 *
326 * @param string|\Piwik\Plugin\Report $apiAction The name of the API action (eg, `'getResolution'`) or
327 * an instance of an report.
328 * @param bool $controllerAction The name of the Controller action name that is rendering the report. Defaults
329 * to the `$apiAction`.
330 * @param bool $fetch If `true`, the rendered string is returned, if `false` it is `echo`'d.
331 * @throws \Exception if `$pluginName` is not an existing plugin or if `$apiAction` is not an
332 * existing method of the plugin's API.
333 * @return string|void See `$fetch`.
334 * @api
335 */
336 protected function renderReport($apiAction, $controllerAction = false)
337 {
338 if (empty($controllerAction) && is_string($apiAction)) {
339 $report = ReportsProvider::factory($this->pluginName, $apiAction);
340
341 if (!empty($report)) {
342 $apiAction = $report;
343 }
344 }
345
346 if ($apiAction instanceof Report) {
347 $this->checkSitePermission();
348 $apiAction->checkIsEnabled();
349
350 return $apiAction->render();
351 }
352
353 $pluginName = $this->pluginName;
354
355 /** @var Proxy $apiProxy */
356 $apiProxy = Proxy::getInstance();
357
358 if (!$apiProxy->isExistingApiAction($pluginName, $apiAction)) {
359 throw new \Exception("Invalid action name '$apiAction' for '$pluginName' plugin.");
360 }
361
362 $apiAction = $apiProxy->buildApiActionName($pluginName, $apiAction);
363
364 if ($controllerAction !== false) {
365 $controllerAction = $pluginName . '.' . $controllerAction;
366 }
367
368 $view = ViewDataTableFactory::build(null, $apiAction, $controllerAction);
369 $rendered = $view->render();
370
371 return $rendered;
372 }
373
374 /**
375 * Returns a ViewDataTable object that will render a jqPlot evolution graph
376 * for the last30 days/weeks/etc. of the current period, relative to the current date.
377 *
378 * @param string $currentModuleName The name of the current plugin.
379 * @param string $currentControllerAction The name of the action that renders the desired
380 * report.
381 * @param string $apiMethod The API method that the ViewDataTable will use to get
382 * graph data.
383 * @return ViewDataTable
384 * @api
385 */
386 protected function getLastUnitGraph($currentModuleName, $currentControllerAction, $apiMethod)
387 {
388 $view = ViewDataTableFactory::build(
389 Evolution::ID, $apiMethod, $currentModuleName . '.' . $currentControllerAction, $forceDefault = true);
390 $view->config->show_goals = false;
391 return $view;
392 }
393
394 /**
395 * Same as {@link getLastUnitGraph()}, but will set some properties of the ViewDataTable
396 * object based on the arguments supplied.
397 *
398 * @param string $currentModuleName The name of the current plugin.
399 * @param string $currentControllerAction The name of the action that renders the desired
400 * report.
401 * @param array $columnsToDisplay The value to use for the ViewDataTable's columns_to_display config
402 * property.
403 * @param array $selectableColumns The value to use for the ViewDataTable's selectable_columns config
404 * property.
405 * @param bool|string $reportDocumentation The value to use for the ViewDataTable's documentation config
406 * property.
407 * @param string $apiMethod The API method that the ViewDataTable will use to get graph data.
408 * @return ViewDataTable
409 * @api
410 */
411 protected function getLastUnitGraphAcrossPlugins($currentModuleName, $currentControllerAction, $columnsToDisplay = false,
412 $selectableColumns = array(), $reportDocumentation = false,
413 $apiMethod = 'API.get')
414 {
415 // load translations from meta data
416 $idSite = Common::getRequestVar('idSite');
417 $period = Common::getRequestVar('period');
418 $date = Common::getRequestVar('date');
419 $meta = \Piwik\Plugins\API\API::getInstance()->getReportMetadata($idSite, $period, $date);
420
421 $columns = array_merge($columnsToDisplay ? $columnsToDisplay : array(), $selectableColumns);
422 $translations = array_combine($columns, $columns);
423 foreach ($meta as $reportMeta) {
424 if ($reportMeta['action'] === 'get' && !isset($reportMeta['parameters'])) {
425 foreach ($columns as $column) {
426 if (isset($reportMeta['metrics'][$column])) {
427 $translations[$column] = $reportMeta['metrics'][$column];
428 }
429 }
430 }
431 }
432
433 // initialize the graph and load the data
434 $view = $this->getLastUnitGraph($currentModuleName, $currentControllerAction, $apiMethod);
435
436 if ($columnsToDisplay !== false) {
437 $view->config->columns_to_display = $columnsToDisplay;
438 }
439
440 if (property_exists($view->config, 'selectable_columns')) {
441 $view->config->selectable_columns = array_merge($view->config->selectable_columns ? : array(), $selectableColumns);
442 }
443
444 $view->config->translations += $translations;
445
446 if ($reportDocumentation) {
447 $view->config->documentation = $reportDocumentation;
448 }
449
450 return $view;
451 }
452
453 /**
454 * Returns the array of new processed parameters once the parameters are applied.
455 * For example: if you set range=last30 and date=2008-03-10,
456 * the date element of the returned array will be "2008-02-10,2008-03-10"
457 *
458 * Parameters you can set:
459 * - range: last30, previous10, etc.
460 * - date: YYYY-MM-DD, today, yesterday
461 * - period: day, week, month, year
462 *
463 * @param array $paramsToSet array( 'date' => 'last50', 'viewDataTable' =>'sparkline' )
464 * @throws \Piwik\NoAccessException
465 * @return array
466 */
467 protected function getGraphParamsModified($paramsToSet = array())
468 {
469 if (!isset($paramsToSet['period'])) {
470 $period = Common::getRequestVar('period');
471 } else {
472 $period = $paramsToSet['period'];
473 }
474 if ($period === 'range') {
475 return $paramsToSet;
476 }
477 if (!isset($paramsToSet['range'])) {
478 $range = 'last30';
479 } else {
480 $range = $paramsToSet['range'];
481 }
482
483 if (!isset($paramsToSet['date'])) {
484 $endDate = $this->strDate;
485 } else {
486 $endDate = $paramsToSet['date'];
487 }
488
489 if (is_null($this->site)) {
490 throw new NoAccessException("Website not initialized, check that you are logged in and/or using the correct token_auth.");
491 }
492 $paramDate = Range::getRelativeToEndDate($period, $range, $endDate, $this->site);
493
494 $params = array_merge($paramsToSet, array('date' => $paramDate));
495 return $params;
496 }
497
498 /**
499 * Returns a numeric value from the API.
500 * Works only for API methods that originally returns numeric values (there is no cast here)
501 *
502 * @param string $methodToCall Name of method to call, eg. Referrers.getNumberOfDistinctSearchEngines
503 * @param bool|string $date A custom date to use when getting the value. If false, the 'date' query
504 * parameter is used.
505 *
506 * @return int|float
507 */
508 protected function getNumericValue($methodToCall, $date = false)
509 {
510 $params = $date === false ? array() : array('date' => $date);
511
512 $return = Request::processRequest($methodToCall, $params);
513 $columns = $return->getFirstRow()->getColumns();
514 return reset($columns);
515 }
516
517 /**
518 * Returns a URL to a sparkline image for a report served by the current plugin.
519 *
520 * The result of this URL should be used with the [sparkline()](/api-reference/Piwik/View#twig) twig function.
521 *
522 * The current site ID and period will be used.
523 *
524 * @param string $action Method name of the controller that serves the report.
525 * @param array $customParameters The array of query parameter name/value pairs that
526 * should be set in result URL.
527 * @return string The generated URL.
528 * @api
529 */
530 protected function getUrlSparkline($action, $customParameters = array())
531 {
532 $params = $this->getGraphParamsModified(
533 array('viewDataTable' => 'sparkline',
534 'action' => $action,
535 'module' => $this->pluginName)
536 + $customParameters
537 );
538 // convert array values to comma separated
539 foreach ($params as &$value) {
540 if (is_array($value)) {
541 $value = rawurlencode(implode(',', $value));
542 }
543 }
544 $url = Url::getCurrentQueryStringWithParametersModified($params);
545 return $url;
546 }
547
548 /**
549 * Sets the first date available in the period selector's calendar.
550 *
551 * @param Date $minDate The min date.
552 * @param View $view The view that contains the period selector.
553 * @api
554 */
555 protected function setMinDateView(Date $minDate, $view)
556 {
557 $view->minDateYear = $minDate->toString('Y');
558 $view->minDateMonth = $minDate->toString('m');
559 $view->minDateDay = $minDate->toString('d');
560 }
561
562 /**
563 * Sets the last date available in the period selector's calendar. Usually this is just the "today" date
564 * for a site (which varies based on the timezone of a site).
565 *
566 * @param Date $maxDate The max date.
567 * @param View $view The view that contains the period selector.
568 * @api
569 */
570 protected function setMaxDateView(Date $maxDate, $view)
571 {
572 $view->maxDateYear = $maxDate->toString('Y');
573 $view->maxDateMonth = $maxDate->toString('m');
574 $view->maxDateDay = $maxDate->toString('d');
575 }
576
577 /**
578 * Assigns variables to {@link Piwik\View} instances that display an entire page.
579 *
580 * The following variables assigned:
581 *
582 * **date** - The value of the **date** query parameter.
583 * **idSite** - The value of the **idSite** query parameter.
584 * **rawDate** - The value of the **date** query parameter.
585 * **prettyDate** - A pretty string description of the current period.
586 * **siteName** - The current site's name.
587 * **siteMainUrl** - The URL of the current site.
588 * **startDate** - The start date of the current period. A {@link Piwik\Date} instance.
589 * **endDate** - The end date of the current period. A {@link Piwik\Date} instance.
590 * **language** - The current language's language code.
591 * **config_action_url_category_delimiter** - The value of the `[General] action_url_category_delimiter`
592 * INI config option.
593 * **topMenu** - The result of `MenuTop::getInstance()->getMenu()`.
594 *
595 * As well as the variables set by {@link setPeriodVariablesView()}.
596 *
597 * Will exit on error.
598 *
599 * @param View $view
600 * @param string|null $viewType 'basic' or 'admin'. If null, set based on the type of controller.
601 * @return void
602 * @api
603 */
604 protected function setGeneralVariablesView($view)
605 {
606 $this->setGeneralVariablesViewAs($view, $viewType = null);
607 }
608
609 protected function setGeneralVariablesViewAs($view, $viewType)
610 {
611 $this->checkViewType($viewType);
612
613 if ($viewType === null) {
614 $viewType = $this instanceof ControllerAdmin ? 'admin' : 'basic';
615 }
616
617 $view->idSite = $this->idSite;
618 $this->checkSitePermission();
619 $this->setPeriodVariablesView($view);
620
621 $view->siteName = $this->site->getName();
622 $view->siteMainUrl = $this->site->getMainUrl();
623
624 $siteTimezone = $this->site->getTimezone();
625
626 $datetimeMinDate = $this->site->getCreationDate()->getDatetime();
627 $minDate = Date::factory($datetimeMinDate, $siteTimezone);
628 $this->setMinDateView($minDate, $view);
629
630 $maxDate = Date::factory('now', $siteTimezone);
631 $this->setMaxDateView($maxDate, $view);
632
633 $rawDate = Common::getRequestVar('date');
634 Period::checkDateFormat($rawDate);
635
636 $periodStr = Common::getRequestVar('period');
637
638 if ($periodStr !== 'range') {
639 $date = Date::factory($this->strDate);
640 $validDate = $this->getValidDate($date, $minDate, $maxDate);
641 $period = Period\Factory::build($periodStr, $validDate);
642
643 if ($date->toString() !== $validDate->toString()) {
644 // we to not always change date since it could convert a strDate "today" to "YYYY-MM-DD"
645 // only change $this->strDate if it was not valid before
646 $this->setDate($validDate);
647 }
648 } else {
649 $period = new Range($periodStr, $rawDate, $siteTimezone);
650 }
651
652 // Setting current period start & end dates, for pre-setting the calendar when "Date Range" is selected
653 $dateStart = $period->getDateStart();
654 $dateStart = $this->getValidDate($dateStart, $minDate, $maxDate);
655
656 $dateEnd = $period->getDateEnd();
657 $dateEnd = $this->getValidDate($dateEnd, $minDate, $maxDate);
658
659 if ($periodStr === 'range') {
660 // make sure we actually display the correct calendar pretty date
661 $newRawDate = $dateStart->toString() . ',' . $dateEnd->toString();
662 $period = new Range($periodStr, $newRawDate, $siteTimezone);
663 }
664
665 $view->date = $this->strDate;
666 $view->prettyDate = self::getCalendarPrettyDate($period);
667 // prettyDateLong is not used by core, leaving in case plugins may be using it
668 $view->prettyDateLong = $period->getLocalizedLongString();
669 $view->rawDate = $rawDate;
670 $view->startDate = $dateStart;
671 $view->endDate = $dateEnd;
672
673 $timezoneOffsetInSeconds = Date::getUtcOffset($siteTimezone);
674 $view->timezoneOffset = $timezoneOffsetInSeconds;
675
676 $language = LanguagesManager::getLanguageForSession();
677 $view->language = !empty($language) ? $language : LanguagesManager::getLanguageCodeForCurrentUser();
678
679 $this->setBasicVariablesViewAs($view, $viewType);
680
681 $view->topMenu = MenuTop::getInstance()->getMenu();
682 $view->adminMenu = MenuAdmin::getInstance()->getMenu();
683
684 $notifications = $view->notifications;
685 if (empty($notifications)) {
686 $view->notifications = NotificationManager::getAllNotificationsToDisplay();
687 NotificationManager::cancelAllNonPersistent();
688 }
689 }
690
691 private function getValidDate(Date $date, Date $minDate, Date $maxDate)
692 {
693 if ($date->isEarlier($minDate)) {
694 $date = $minDate;
695 }
696
697 if ($date->isLater($maxDate)) {
698 $date = $maxDate;
699 }
700
701 return $date;
702 }
703
704 /**
705 * Needed when a controller extends ControllerAdmin but you don't want to call the controller admin basic variables
706 * view. Solves a problem when a controller has regular controller and admin controller views.
707 * @param View $view
708 */
709 protected function setBasicVariablesNoneAdminView($view)
710 {
711 $view->clientSideConfig = PiwikConfig::getInstance()->getClientSideOptions();
712 $view->isSuperUser = Access::getInstance()->hasSuperUserAccess();
713 $view->hasSomeAdminAccess = Piwik::isUserHasSomeAdminAccess();
714 $view->hasSomeViewAccess = Piwik::isUserHasSomeViewAccess();
715 $view->isUserIsAnonymous = Piwik::isUserIsAnonymous();
716 $view->hasSuperUserAccess = Piwik::hasUserSuperUserAccess();
717
718 if (!Piwik::isUserIsAnonymous()) {
719 $view->emailSuperUser = implode(',', Piwik::getAllSuperUserAccessEmailAddresses());
720 }
721
722 $capabilities = array();
723 if ($this->idSite && $this->site) {
724 $capabilityProvider = StaticContainer::get(Access\CapabilitiesProvider::class);
725 foreach ($capabilityProvider->getAllCapabilities() as $capability) {
726 if (Piwik::isUserHasCapability($this->idSite, $capability->getId())) {
727 $capabilities[] = $capability->getId();
728 }
729 }
730 }
731
732 $view->userCapabilities = $capabilities;
733
734 $this->addCustomLogoInfo($view);
735
736 $customLogo = new CustomLogo();
737 $view->logoHeader = $customLogo->getHeaderLogoUrl();
738 $view->logoLarge = $customLogo->getLogoUrl();
739 $view->logoSVG = $customLogo->getSVGLogoUrl();
740 $view->hasSVGLogo = $customLogo->hasSVGLogo();
741 $view->superUserEmails = implode(',', Piwik::getAllSuperUserAccessEmailAddresses());
742 $view->themeStyles = ThemeStyles::get();
743
744 $general = PiwikConfig::getInstance()->General;
745 $view->enableFrames = $general['enable_framed_pages']
746 || (isset($general['enable_framed_logins']) && $general['enable_framed_logins']);
747 $embeddedAsIframe = (Common::getRequestVar('module', '', 'string') === 'Widgetize');
748 if (!$view->enableFrames && !$embeddedAsIframe) {
749 $view->setXFrameOptions('sameorigin');
750 }
751
752 $pluginManager = Plugin\Manager::getInstance();
753 $view->relativePluginWebDirs = (object) $pluginManager->getWebRootDirectoriesForCustomPluginDirs();
754 $view->isMultiSitesEnabled = $pluginManager->isPluginActivated('MultiSites');
755 $view->isSingleSite = Access::doAsSuperUser(function() {
756 $allSites = Request::processRequest('SitesManager.getAllSitesId', [], []);
757 return count($allSites) === 1;
758 });
759
760 if (isset($this->site) && is_object($this->site) && $this->site instanceof Site) {
761 $view->siteName = $this->site->getName();
762 }
763
764 self::setHostValidationVariablesView($view);
765 }
766
767 /**
768 * Assigns a set of generally useful variables to a {@link Piwik\View} instance.
769 *
770 * The following variables assigned:
771 *
772 * **isSuperUser** - True if the current user is the Super User, false if otherwise.
773 * **hasSomeAdminAccess** - True if the current user has admin access to at least one site,
774 * false if otherwise.
775 * **isCustomLogo** - The value of the `branding_use_custom_logo` option.
776 * **logoHeader** - The header logo URL to use.
777 * **logoLarge** - The large logo URL to use.
778 * **logoSVG** - The SVG logo URL to use.
779 * **hasSVGLogo** - True if there is a SVG logo, false if otherwise.
780 * **enableFrames** - The value of the `[General] enable_framed_pages` INI config option. If
781 * true, {@link Piwik\View::setXFrameOptions()} is called on the view.
782 *
783 * Also calls {@link setHostValidationVariablesView()}.
784 *
785 * @param View $view
786 * @param string $viewType 'basic' or 'admin'. Used by ControllerAdmin.
787 * @api
788 */
789 protected function setBasicVariablesView($view)
790 {
791 $this->setBasicVariablesViewAs($view);
792 }
793
794 protected function setBasicVariablesViewAs($view, $viewType = null)
795 {
796 $this->checkViewType($viewType); // param is not used here, but the check can be useful for a developer
797
798 $this->setBasicVariablesNoneAdminView($view);
799 }
800
801 protected function addCustomLogoInfo($view)
802 {
803 $customLogo = new CustomLogo();
804 $view->isCustomLogo = $customLogo->isEnabled();
805 $view->customFavicon = $customLogo->getPathUserFavicon();
806 }
807
808 /**
809 * Checks if the current host is valid and sets variables on the given view, including:
810 *
811 * - **isValidHost** - true if host is valid, false if otherwise
812 * - **invalidHostMessage** - message to display if host is invalid (only set if host is invalid)
813 * - **invalidHost** - the invalid hostname (only set if host is invalid)
814 * - **mailLinkStart** - the open tag of a link to email the Super User of this problem (only set
815 * if host is invalid)
816 *
817 * @param View $view
818 * @api
819 */
820 public static function setHostValidationVariablesView($view)
821 {
822 // check if host is valid
823 $view->isValidHost = Url::isValidHost();
824 if (!$view->isValidHost) {
825 // invalid host, so display warning to user
826 $validHosts = Url::getTrustedHostsFromConfig();
827 $validHost = $validHosts[0];
828 $invalidHost = Common::sanitizeInputValue(Url::getHost(false));
829
830 $emailSubject = rawurlencode(Piwik::translate('CoreHome_InjectedHostEmailSubject', $invalidHost));
831 $emailBody = rawurlencode(Piwik::translate('CoreHome_InjectedHostEmailBody'));
832 $superUserEmail = implode(',', Piwik::getAllSuperUserAccessEmailAddresses());
833
834 $mailToUrl = "mailto:$superUserEmail?subject=$emailSubject&body=$emailBody";
835 $mailLinkStart = "<a href=\"$mailToUrl\">";
836
837 $invalidUrl = Url::getCurrentUrlWithoutQueryString($checkIfTrusted = false);
838 $validUrl = Url::getCurrentScheme() . '://' . $validHost
839 . Url::getCurrentScriptName();
840 $invalidUrl = Common::sanitizeInputValue($invalidUrl);
841 $validUrl = Common::sanitizeInputValue($validUrl);
842
843 $changeTrustedHostsUrl = "index.php"
844 . Url::getCurrentQueryStringWithParametersModified(array(
845 'module' => 'CoreAdminHome',
846 'action' => 'generalSettings'
847 ))
848 . "#trustedHostsSection";
849
850 $warningStart = Piwik::translate('CoreHome_InjectedHostWarningIntro', array(
851 '<strong>' . $invalidUrl . '</strong>',
852 '<strong>' . $validUrl . '</strong>'
853 )) . ' <br/>';
854
855 if (Piwik::hasUserSuperUserAccess()) {
856 $view->invalidHostMessage = $warningStart . ' '
857 . Piwik::translate('CoreHome_InjectedHostSuperUserWarning', array(
858 "<a href=\"$changeTrustedHostsUrl\">",
859 $invalidHost,
860 '</a>',
861 "<br/><a href=\"$validUrl\">",
862 $validHost,
863 '</a>'
864 ));
865 } elseif (Piwik::isUserIsAnonymous()) {
866 $view->invalidHostMessage = $warningStart . ' '
867 . Piwik::translate('CoreHome_InjectedHostNonSuperUserWarning', array(
868 "<br/><a href=\"$validUrl\">",
869 '</a>',
870 '<span style="display:none">',
871 '</span>'
872 ));
873 } else {
874 $view->invalidHostMessage = $warningStart . ' '
875 . Piwik::translate('CoreHome_InjectedHostNonSuperUserWarning', array(
876 "<br/><a href=\"$validUrl\">",
877 '</a>',
878 $mailLinkStart,
879 '</a>'
880 ));
881 }
882 $view->invalidHostMessageHowToFix = '<p><b>How do I fix this problem and how do I login again?</b><br/> The Matomo Super User can manually edit the file piwik/config/config.ini.php
883 and add the following lines: <pre>[General]' . "\n" . 'trusted_hosts[] = "' . $invalidHost . '"</pre>After making the change, you will be able to login again.</p>
884 <p>You may also <i>disable this security feature (not recommended)</i>. To do so edit config/config.ini.php and add:
885 <pre>[General]' . "\n" . 'enable_trusted_host_check=0</pre>';
886
887 $view->invalidHost = $invalidHost; // for UserSettings warning
888 $view->invalidHostMailLinkStart = $mailLinkStart;
889 }
890 }
891
892 /**
893 * Sets general period variables on a view, including:
894 *
895 * - **displayUniqueVisitors** - Whether unique visitors should be displayed for the current
896 * period.
897 * - **period** - The value of the **period** query parameter.
898 * - **otherPeriods** - `array('day', 'week', 'month', 'year', 'range')`
899 * - **periodsNames** - List of available periods mapped to their singular and plural translations.
900 *
901 * @param View $view
902 * @throws Exception if the current period is invalid.
903 * @api
904 */
905 public static function setPeriodVariablesView($view)
906 {
907 if (isset($view->period)) {
908 return;
909 }
910
911 $periodValidator = new PeriodValidator();
912
913 $currentPeriod = Common::getRequestVar('period');
914 $view->displayUniqueVisitors = SettingsPiwik::isUniqueVisitorsEnabled($currentPeriod);
915 $availablePeriods = $periodValidator->getPeriodsAllowedForUI();
916
917 if (! $periodValidator->isPeriodAllowedForUI($currentPeriod)) {
918 throw new Exception("Period must be one of: " . implode(", ", $availablePeriods));
919 }
920
921 $found = array_search($currentPeriod, $availablePeriods);
922 unset($availablePeriods[$found]);
923
924 $view->period = $currentPeriod;
925 $view->otherPeriods = $availablePeriods;
926 $view->enabledPeriods = self::getEnabledPeriodsInUI();
927 $view->periodsNames = self::getEnabledPeriodsNames();
928 }
929
930 /**
931 * Helper method used to redirect the current HTTP request to another module/action.
932 *
933 * This function will exit immediately after executing.
934 *
935 * @param string $moduleToRedirect The plugin to redirect to, eg. `"MultiSites"`.
936 * @param string $actionToRedirect Action, eg. `"index"`.
937 * @param int|null $websiteId The new idSite query parameter, eg, `1`.
938 * @param string|null $defaultPeriod The new period query parameter, eg, `'day'`.
939 * @param string|null $defaultDate The new date query parameter, eg, `'today'`.
940 * @param array $parameters Other query parameters to append to the URL.
941 * @api
942 */
943 public function redirectToIndex($moduleToRedirect, $actionToRedirect, $websiteId = null, $defaultPeriod = null,
944 $defaultDate = null, $parameters = array())
945 {
946 try {
947 $this->doRedirectToUrl($moduleToRedirect, $actionToRedirect, $websiteId, $defaultPeriod, $defaultDate, $parameters);
948 } catch (Exception $e) {
949 // no website ID to default to, so could not redirect
950 }
951
952 if (Piwik::hasUserSuperUserAccess()) {
953 $siteTableName = Common::prefixTable('site');
954 $message = "Error: no website was found in this Matomo installation.
955 <br />Check the table '$siteTableName' in your database, it should contain your Matomo websites.";
956
957 $ex = new NoWebsiteFoundException($message);
958 $ex->setIsHtmlMessage();
959
960 throw $ex;
961 }
962
963 if (!Piwik::isUserIsAnonymous()) {
964 $currentLogin = Piwik::getCurrentUserLogin();
965 $emails = implode(',', Piwik::getAllSuperUserAccessEmailAddresses());
966 $errorMessage = sprintf(Piwik::translate('CoreHome_NoPrivilegesAskPiwikAdmin'), $currentLogin, "<br/><a href='mailto:" . $emails . "?subject=Access to Matomo for user $currentLogin'>", "</a>");
967 $errorMessage .= "<br /><br />&nbsp;&nbsp;&nbsp;<b><a href='index.php?module=" . Piwik::getLoginPluginName() . "&amp;action=logout'>&rsaquo; " . Piwik::translate('General_Logout') . "</a></b><br />";
968
969 $ex = new NoPrivilegesException($errorMessage);
970 $ex->setIsHtmlMessage();
971
972 throw $ex;
973 }
974
975 echo FrontController::getInstance()->dispatch(Piwik::getLoginPluginName(), false);
976 exit;
977 }
978
979
980 /**
981 * Checks that the token_auth in the URL matches the currently logged-in user's token_auth.
982 *
983 * This is a protection against CSRF and should be used in all controller
984 * methods that modify Piwik or any user settings.
985 *
986 * If called from JavaScript by using the `ajaxHelper` you have to call `ajaxHelper.withTokenInUrl();` before
987 * `ajaxHandler.send();` to send the token along with the request.
988 *
989 * **The token_auth should never appear in the browser's address bar.**
990 *
991 * @throws \Piwik\NoAccessException If the token doesn't match.
992 * @api
993 */
994 protected function checkTokenInUrl()
995 {
996 $tokenRequest = Common::getRequestVar('token_auth', false);
997 $tokenUser = Piwik::getCurrentUserTokenAuth();
998
999 if (empty($tokenRequest) && empty($tokenUser)) {
1000 return; // UI tests
1001 }
1002
1003 if ($tokenRequest !== $tokenUser) {
1004 throw new NoAccessException(Piwik::translate('General_ExceptionInvalidToken'));
1005 }
1006 }
1007
1008 /**
1009 * Returns a prettified date string for use in period selector widget.
1010 *
1011 * @param Period $period The period to return a pretty string for.
1012 * @return string
1013 * @api
1014 */
1015 public static function getCalendarPrettyDate($period)
1016 {
1017 if ($period instanceof Month) {
1018 // show month name when period is for a month
1019
1020 return $period->getLocalizedLongString();
1021 } else {
1022 return $period->getPrettyString();
1023 }
1024 }
1025
1026 /**
1027 * Returns the pretty date representation
1028 *
1029 * @param $date string
1030 * @param $period string
1031 * @return string Pretty date
1032 */
1033 public static function getPrettyDate($date, $period)
1034 {
1035 return self::getCalendarPrettyDate(Period\Factory::build($period, Date::factory($date)));
1036 }
1037
1038 protected function checkSitePermission()
1039 {
1040 if (!empty($this->idSite)) {
1041 Access::getInstance()->checkUserHasViewAccess($this->idSite);
1042 new Site($this->idSite);
1043 } elseif (empty($this->site) || empty($this->idSite)) {
1044 throw new Exception("The requested website idSite is not found in the request, or is invalid.
1045 Please check that you are logged in Matomo and have permission to access the specified website.");
1046 }
1047 }
1048
1049 /**
1050 * @param $moduleToRedirect
1051 * @param $actionToRedirect
1052 * @param $websiteId
1053 * @param $defaultPeriod
1054 * @param $defaultDate
1055 * @param $parameters
1056 * @throws Exception
1057 */
1058 private function doRedirectToUrl($moduleToRedirect, $actionToRedirect, $websiteId, $defaultPeriod, $defaultDate, $parameters)
1059 {
1060 $menu = new Menu();
1061
1062 $parameters = array_merge(
1063 $menu->urlForDefaultUserParams($websiteId, $defaultPeriod, $defaultDate),
1064 $parameters
1065 );
1066 $queryParams = !empty($parameters) ? '&' . Url::getQueryStringFromParameters($parameters) : '';
1067 $url = "index.php?module=%s&action=%s";
1068 $url = sprintf($url, $moduleToRedirect, $actionToRedirect);
1069 $url = $url . $queryParams;
1070 Url::redirectToUrl($url);
1071 }
1072
1073 private function checkViewType($viewType)
1074 {
1075 if ($viewType === 'admin' && !($this instanceof ControllerAdmin)) {
1076 throw new Exception("'admin' view type is only allowed with ControllerAdmin class.");
1077 }
1078 }
1079 }
1080
1081