PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 4.13.3
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v4.13.3
5.12.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 3 years ago API.php 3 years ago AggregatedMetric.php 5 years ago ArchivedMetric.php 4 years ago Archiver.php 3 years ago Categories.php 5 years ago ComponentFactory.php 5 years ago ComputedMetric.php 4 years ago ConsoleCommand.php 5 years ago Controller.php 3 years ago ControllerAdmin.php 3 years ago Dependency.php 4 years ago LogTablesProvider.php 5 years ago Manager.php 4 years ago Menu.php 5 years ago MetadataLoader.php 4 years ago Metric.php 5 years ago PluginException.php 5 years ago ProcessedMetric.php 5 years ago ReleaseChannels.php 5 years ago Report.php 3 years ago ReportsProvider.php 3 years ago RequestProcessors.php 5 years ago Segment.php 3 years ago SettingsProvider.php 5 years ago Tasks.php 3 years ago ThemeStyles.php 5 years ago ViewDataTable.php 5 years ago Visualization.php 3 years ago WidgetsProvider.php 5 years ago
Controller.php
1085 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\Config\GeneralConfig;
18 use Piwik\Container\StaticContainer;
19 use Piwik\Date;
20 use Piwik\Exception\NoPrivilegesException;
21 use Piwik\Exception\NoWebsiteFoundException;
22 use Piwik\FrontController;
23 use Piwik\Menu\MenuAdmin;
24 use Piwik\Menu\MenuTop;
25 use Piwik\NoAccessException;
26 use Piwik\Notification\Manager as NotificationManager;
27 use Piwik\Period\Month;
28 use Piwik\Period;
29 use Piwik\Period\PeriodValidator;
30 use Piwik\Period\Range;
31 use Piwik\Piwik;
32 use Piwik\Plugins\CoreAdminHome\CustomLogo;
33 use Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph\Evolution;
34 use Piwik\Plugins\LanguagesManager\LanguagesManager;
35 use Piwik\SettingsPiwik;
36 use Piwik\Site;
37 use Piwik\Url;
38 use Piwik\Plugin;
39 use Piwik\View;
40 use Piwik\View\ViewInterface;
41 use Piwik\ViewDataTable\Factory as ViewDataTableFactory;
42
43 /**
44 * Base class of all plugin Controllers.
45 *
46 * Plugins that wish to add display HTML should create a Controller that either
47 * extends from this class or from {@link ControllerAdmin}. Every public method in
48 * the controller will be exposed as a controller method and can be invoked via
49 * an HTTP request.
50 *
51 * Learn more about Piwik's MVC system [here](/guides/mvc-in-piwik).
52 *
53 * ### Examples
54 *
55 * **Defining a controller**
56 *
57 * class Controller extends \Piwik\Plugin\Controller
58 * {
59 * public function index()
60 * {
61 * $view = new View("@MyPlugin/index.twig");
62 * // ... setup view ...
63 * return $view->render();
64 * }
65 * }
66 *
67 * **Linking to a controller action**
68 *
69 * <a href="?module=MyPlugin&action=index&idSite=1&period=day&date=2013-10-10">Link</a>
70 *
71 */
72 abstract class Controller
73 {
74 /**
75 * The plugin name, eg. `'Referrers'`.
76 *
77 * @var string
78 * @api
79 */
80 protected $pluginName;
81
82 /**
83 * The value of the **date** query parameter.
84 *
85 * @var string
86 * @api
87 */
88 protected $strDate;
89
90 /**
91 * The Date object created with ($strDate)[#strDate] or null if the requested date is a range.
92 *
93 * @var Date|null
94 * @api
95 */
96 protected $date;
97
98 /**
99 * The value of the **idSite** query parameter.
100 *
101 * @var int
102 * @api
103 */
104 protected $idSite;
105
106 /**
107 * The Site object created with {@link $idSite}.
108 *
109 * @var Site
110 * @api
111 */
112 protected $site = null;
113
114 /**
115 * The SecurityPolicy object.
116 *
117 * @var \Piwik\View\SecurityPolicy
118 * @api
119 */
120 protected $securityPolicy = null;
121
122 /**
123 * Constructor.
124 *
125 * @api
126 */
127 public function __construct()
128 {
129 $this->init();
130 }
131
132 protected function init()
133 {
134 $aPluginName = explode('\\', get_class($this));
135 $this->pluginName = $aPluginName[2];
136
137 $this->securityPolicy = StaticContainer::get(View\SecurityPolicy::class);
138
139 $date = Common::getRequestVar('date', 'yesterday', 'string');
140 try {
141 $this->idSite = Common::getRequestVar('idSite', false, 'int');
142 $this->site = new Site($this->idSite);
143 $date = $this->getDateParameterInTimezone($date, $this->site->getTimezone());
144 $this->setDate($date);
145 } catch (Exception $e) {
146 // the date looks like YYYY-MM-DD,YYYY-MM-DD or other format
147 $this->date = null;
148 }
149 }
150
151 /**
152 * Helper method that converts `"today"` or `"yesterday"` to the specified timezone.
153 * If the date is absolute, ie. YYYY-MM-DD, it will not be converted to the timezone.
154 *
155 * @param string $date `'today'`, `'yesterday'`, `'YYYY-MM-DD'`
156 * @param string $timezone The timezone to use.
157 * @return Date
158 * @api
159 */
160 protected function getDateParameterInTimezone($date, $timezone)
161 {
162 $timezoneToUse = null;
163 // if the requested date is not YYYY-MM-DD, we need to ensure
164 // it is relative to the website's timezone
165 if (in_array($date, array('today', 'yesterday'))) {
166 // today is at midnight; we really want to get the time now, so that
167 // * if the website is UTC+12 and it is 5PM now in UTC, the calendar will allow to select the UTC "tomorrow"
168 // * if the website is UTC-12 and it is 5AM now in UTC, the calendar will allow to select the UTC "yesterday"
169 if ($date === 'today') {
170 $date = 'now';
171 } elseif ($date === 'yesterday') {
172 $date = 'yesterdaySameTime';
173 }
174 $timezoneToUse = $timezone;
175 }
176 return Date::factory($date, $timezoneToUse);
177 }
178
179 /**
180 * Sets the date to be used by all other methods in the controller.
181 * If the date has to be modified, this method should be called just after
182 * construction.
183 *
184 * @param Date $date The new Date.
185 * @return void
186 * @api
187 */
188 protected function setDate(Date $date)
189 {
190 $this->date = $date;
191 $this->strDate = $date->toString();
192 }
193
194 /**
195 * Returns values that are enabled for the parameter &period=
196 * @return array eg. array('day', 'week', 'month', 'year', 'range')
197 */
198 protected static function getEnabledPeriodsInUI()
199 {
200 $periodValidator = new PeriodValidator();
201 return $periodValidator->getPeriodsAllowedForUI();
202 }
203
204 /**
205 * @return array
206 */
207 private static function getEnabledPeriodsNames()
208 {
209 $availablePeriods = self::getEnabledPeriodsInUI();
210 $periodNames = array(
211 'day' => array(
212 'singular' => Piwik::translate('Intl_PeriodDay'),
213 'plural' => Piwik::translate('Intl_PeriodDays')
214 ),
215 'week' => array(
216 'singular' => Piwik::translate('Intl_PeriodWeek'),
217 'plural' => Piwik::translate('Intl_PeriodWeeks')
218 ),
219 'month' => array(
220 'singular' => Piwik::translate('Intl_PeriodMonth'),
221 'plural' => Piwik::translate('Intl_PeriodMonths')
222 ),
223 'year' => array(
224 'singular' => Piwik::translate('Intl_PeriodYear'),
225 'plural' => Piwik::translate('Intl_PeriodYears')
226 ),
227 // Note: plural is not used for date range
228 'range' => array(
229 'singular' => Piwik::translate('General_DateRangeInPeriodList'),
230 'plural' => Piwik::translate('General_DateRangeInPeriodList')
231 ),
232 );
233
234 $periodNames = array_intersect_key($periodNames, array_fill_keys($availablePeriods, true));
235 return $periodNames;
236 }
237
238 /**
239 * Returns the name of the default method that will be called
240 * when visiting: index.php?module=PluginName without the action parameter.
241 *
242 * @return string
243 * @api
244 */
245 public function getDefaultAction()
246 {
247 return 'index';
248 }
249
250 /**
251 * A helper method that renders a view either to the screen or to a string.
252 *
253 * @param ViewInterface $view The view to render.
254 * @return string|void
255 */
256 protected function renderView(ViewInterface $view)
257 {
258 return $view->render();
259 }
260
261 /**
262 * Assigns the given variables to the template and renders it.
263 *
264 * Example:
265 *
266 * public function myControllerAction () {
267 * return $this->renderTemplate('index', array(
268 * 'answerToLife' => '42'
269 * ));
270 * }
271 *
272 * This will render the 'index.twig' file within the plugin templates folder and assign the view variable
273 * `answerToLife` to `42`.
274 *
275 * @param string $template The name of the template file. If only a name is given it will automatically use
276 * the template within the plugin folder. For instance 'myTemplate' will result in
277 * '@$pluginName/myTemplate.twig'. Alternatively you can include the full path:
278 * '@anyOtherFolder/otherTemplate'. The trailing '.twig' is not needed.
279 * @param array $variables For instance array('myViewVar' => 'myValue'). In template you can use {{ myViewVar }}
280 * @return string
281 * @since 2.5.0
282 * @api
283 */
284 protected function renderTemplate($template, array $variables = [])
285 {
286 return $this->renderTemplateAs($template, $variables);
287 }
288
289 /**
290 * @see {self::renderTemplate()}
291 *
292 * @param $template
293 * @param array $variables
294 * @param string|null $viewType 'basic' or 'admin'. If null, determined based on the controller instance type.
295 * @return string
296 * @throws Exception
297 */
298 protected function renderTemplateAs($template, array $variables = array(), $viewType = null)
299 {
300 if (false === strpos($template, '@') || false === strpos($template, '/')) {
301 $template = '@' . $this->pluginName . '/' . $template;
302 }
303
304 $view = new View($template);
305
306 $this->checkViewType($viewType);
307
308 if (empty($viewType)) {
309 $viewType = $this instanceof ControllerAdmin ? 'admin' : 'basic';
310 }
311
312 // alternatively we could check whether the templates extends either admin.twig or dashboard.twig and based on
313 // that call the correct method. This will be needed once we unify Controller and ControllerAdmin see
314 // https://github.com/piwik/piwik/issues/6151
315 if ($this instanceof ControllerAdmin && $viewType === 'admin') {
316 $this->setBasicVariablesViewAs($view, $viewType);
317 } elseif (empty($this->site) || empty($this->idSite)) {
318 $this->setBasicVariablesViewAs($view, $viewType);
319 } else {
320 $this->setGeneralVariablesViewAs($view, $viewType);
321 }
322
323 foreach ($variables as $key => $value) {
324 $view->$key = $value;
325 }
326
327 if (isset($view->siteName)) {
328 $view->siteNameDecoded = Common::unsanitizeInputValue($view->siteName);
329 }
330
331 return $view->render();
332 }
333
334 /**
335 * Convenience method that creates and renders a ViewDataTable for a API method.
336 *
337 * @param string|\Piwik\Plugin\Report $apiAction The name of the API action (eg, `'getResolution'`) or
338 * an instance of an report.
339 * @param bool $controllerAction The name of the Controller action name that is rendering the report. Defaults
340 * to the `$apiAction`.
341 * @param bool $fetch If `true`, the rendered string is returned, if `false` it is `echo`'d.
342 * @throws \Exception if `$pluginName` is not an existing plugin or if `$apiAction` is not an
343 * existing method of the plugin's API.
344 * @return string|void See `$fetch`.
345 * @api
346 */
347 protected function renderReport($apiAction, $controllerAction = false)
348 {
349 if (empty($controllerAction) && is_string($apiAction)) {
350 $report = ReportsProvider::factory($this->pluginName, $apiAction);
351
352 if (!empty($report)) {
353 $apiAction = $report;
354 }
355 }
356
357 if ($apiAction instanceof Report) {
358 $this->checkSitePermission();
359 $apiAction->checkIsEnabled();
360
361 return $apiAction->render();
362 }
363
364 $pluginName = $this->pluginName;
365
366 /** @var Proxy $apiProxy */
367 $apiProxy = Proxy::getInstance();
368
369 if (!$apiProxy->isExistingApiAction($pluginName, $apiAction)) {
370 throw new \Exception("Invalid action name '$apiAction' for '$pluginName' plugin.");
371 }
372
373 $apiAction = $apiProxy->buildApiActionName($pluginName, $apiAction);
374
375 if ($controllerAction !== false) {
376 $controllerAction = $pluginName . '.' . $controllerAction;
377 }
378
379 $view = ViewDataTableFactory::build(null, $apiAction, $controllerAction);
380 $rendered = $view->render();
381
382 return $rendered;
383 }
384
385 /**
386 * Returns a ViewDataTable object that will render a jqPlot evolution graph
387 * for the last30 days/weeks/etc. of the current period, relative to the current date.
388 *
389 * @param string $currentModuleName The name of the current plugin.
390 * @param string $currentControllerAction The name of the action that renders the desired
391 * report.
392 * @param string $apiMethod The API method that the ViewDataTable will use to get
393 * graph data.
394 * @return ViewDataTable
395 * @api
396 */
397 protected function getLastUnitGraph($currentModuleName, $currentControllerAction, $apiMethod)
398 {
399 $view = ViewDataTableFactory::build(
400 Evolution::ID, $apiMethod, $currentModuleName . '.' . $currentControllerAction, $forceDefault = true);
401 $view->config->show_goals = false;
402 return $view;
403 }
404
405 /**
406 * Same as {@link getLastUnitGraph()}, but will set some properties of the ViewDataTable
407 * object based on the arguments supplied.
408 *
409 * @param string $currentModuleName The name of the current plugin.
410 * @param string $currentControllerAction The name of the action that renders the desired
411 * report.
412 * @param array $columnsToDisplay The value to use for the ViewDataTable's columns_to_display config
413 * property.
414 * @param array $selectableColumns The value to use for the ViewDataTable's selectable_columns config
415 * property.
416 * @param bool|string $reportDocumentation The value to use for the ViewDataTable's documentation config
417 * property.
418 * @param string $apiMethod The API method that the ViewDataTable will use to get graph data.
419 * @return ViewDataTable
420 * @api
421 */
422 protected function getLastUnitGraphAcrossPlugins($currentModuleName, $currentControllerAction, $columnsToDisplay = false,
423 $selectableColumns = array(), $reportDocumentation = false,
424 $apiMethod = 'API.get')
425 {
426 // load translations from meta data
427 $idSite = Common::getRequestVar('idSite');
428 $period = Piwik::getPeriod();
429 $date = Piwik::getDate();
430 $meta = \Piwik\Plugins\API\API::getInstance()->getReportMetadata($idSite, $period, $date);
431
432 $columns = array_merge($columnsToDisplay ? $columnsToDisplay : array(), $selectableColumns);
433 $translations = array_combine($columns, $columns);
434 foreach ($meta as $reportMeta) {
435 if ($reportMeta['action'] === 'get' && !isset($reportMeta['parameters'])) {
436 foreach ($columns as $column) {
437 if (isset($reportMeta['metrics'][$column])) {
438 $translations[$column] = $reportMeta['metrics'][$column];
439 }
440 }
441 }
442 }
443
444 // initialize the graph and load the data
445 $view = $this->getLastUnitGraph($currentModuleName, $currentControllerAction, $apiMethod);
446
447 if ($columnsToDisplay !== false) {
448 $view->config->columns_to_display = $columnsToDisplay;
449 }
450
451 if (property_exists($view->config, 'selectable_columns')) {
452 $view->config->selectable_columns = array_merge($view->config->selectable_columns ? : array(), $selectableColumns);
453 }
454
455 $view->config->translations += $translations;
456
457 if ($reportDocumentation) {
458 $view->config->documentation = $reportDocumentation;
459 }
460
461 return $view;
462 }
463
464 /**
465 * Returns the array of new processed parameters once the parameters are applied.
466 * For example: if you set range=last30 and date=2008-03-10,
467 * the date element of the returned array will be "2008-02-10,2008-03-10"
468 *
469 * Parameters you can set:
470 * - range: last30, previous10, etc.
471 * - date: YYYY-MM-DD, today, yesterday
472 * - period: day, week, month, year
473 *
474 * @param array $paramsToSet array( 'date' => 'last50', 'viewDataTable' =>'sparkline' )
475 * @throws \Piwik\NoAccessException
476 * @return array
477 */
478 protected function getGraphParamsModified($paramsToSet = array())
479 {
480 $period = $paramsToSet['period'] ?? Piwik::getPeriod();
481
482 if ($period === 'range') {
483 return $paramsToSet;
484 }
485
486 $range = isset($paramsToSet['range']) ? $paramsToSet['range'] : 'last30';
487 $endDate = isset($paramsToSet['date']) ? $paramsToSet['date'] : $this->strDate;
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 = Piwik::getDate(GeneralConfig::getConfigValue('default_day'));
634 Period::checkDateFormat($rawDate);
635
636 $periodStr = Piwik::getPeriod(GeneralConfig::getConfigValue('default_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->contactEmail = implode(',', Piwik::getContactEmailAddresses());
720
721 // for BC only. Use contactEmail instead
722 $view->emailSuperUser = implode(',', Piwik::getAllSuperUserAccessEmailAddresses());
723 }
724
725 $capabilities = array();
726 if ($this->idSite && $this->site) {
727 $capabilityProvider = StaticContainer::get(Access\CapabilitiesProvider::class);
728 foreach ($capabilityProvider->getAllCapabilities() as $capability) {
729 if (Piwik::isUserHasCapability($this->idSite, $capability->getId())) {
730 $capabilities[] = $capability->getId();
731 }
732 }
733 }
734
735 $view->userCapabilities = $capabilities;
736
737 $this->addCustomLogoInfo($view);
738
739 $customLogo = new CustomLogo();
740 $view->logoHeader = $customLogo->getHeaderLogoUrl();
741 $view->logoLarge = $customLogo->getLogoUrl();
742 $view->logoSVG = $customLogo->getSVGLogoUrl();
743 $view->hasSVGLogo = $customLogo->hasSVGLogo();
744 $view->contactEmail = implode(',', Piwik::getContactEmailAddresses());
745 $view->themeStyles = ThemeStyles::get();
746
747 $general = PiwikConfig::getInstance()->General;
748 $view->enableFrames = $general['enable_framed_pages']
749 || (isset($general['enable_framed_logins']) && $general['enable_framed_logins']);
750 $embeddedAsIframe = (Common::getRequestVar('module', '', 'string') === 'Widgetize');
751 if (!$view->enableFrames && !$embeddedAsIframe) {
752 $view->setXFrameOptions('sameorigin');
753 }
754
755 $pluginManager = Plugin\Manager::getInstance();
756 $view->relativePluginWebDirs = (object) $pluginManager->getWebRootDirectoriesForCustomPluginDirs();
757 $view->isMultiSitesEnabled = $pluginManager->isPluginActivated('MultiSites');
758 $view->isSingleSite = Access::doAsSuperUser(function() {
759 $allSites = Request::processRequest('SitesManager.getAllSitesId', [], []);
760 return count($allSites) === 1;
761 });
762
763 if (isset($this->site) && is_object($this->site) && $this->site instanceof Site) {
764 $view->siteName = $this->site->getName();
765 }
766
767 self::setHostValidationVariablesView($view);
768 }
769
770 /**
771 * Assigns a set of generally useful variables to a {@link Piwik\View} instance.
772 *
773 * The following variables assigned:
774 *
775 * **isSuperUser** - True if the current user is the Super User, false if otherwise.
776 * **hasSomeAdminAccess** - True if the current user has admin access to at least one site,
777 * false if otherwise.
778 * **isCustomLogo** - The value of the `branding_use_custom_logo` option.
779 * **logoHeader** - The header logo URL to use.
780 * **logoLarge** - The large logo URL to use.
781 * **logoSVG** - The SVG logo URL to use.
782 * **hasSVGLogo** - True if there is a SVG logo, false if otherwise.
783 * **enableFrames** - The value of the `[General] enable_framed_pages` INI config option. If
784 * true, {@link Piwik\View::setXFrameOptions()} is called on the view.
785 *
786 * Also calls {@link setHostValidationVariablesView()}.
787 *
788 * @param View $view
789 * @param string $viewType 'basic' or 'admin'. Used by ControllerAdmin.
790 * @api
791 */
792 protected function setBasicVariablesView($view)
793 {
794 $this->setBasicVariablesViewAs($view);
795 }
796
797 protected function setBasicVariablesViewAs($view, $viewType = null)
798 {
799 $this->checkViewType($viewType); // param is not used here, but the check can be useful for a developer
800
801 $this->setBasicVariablesNoneAdminView($view);
802 }
803
804 protected function addCustomLogoInfo($view)
805 {
806 $customLogo = new CustomLogo();
807 $view->isCustomLogo = $customLogo->isEnabled();
808 $view->customFavicon = $customLogo->getPathUserFavicon();
809 }
810
811 /**
812 * Checks if the current host is valid and sets variables on the given view, including:
813 *
814 * - **isValidHost** - true if host is valid, false if otherwise
815 * - **invalidHostMessage** - message to display if host is invalid (only set if host is invalid)
816 * - **invalidHost** - the invalid hostname (only set if host is invalid)
817 * - **mailLinkStart** - the open tag of a link to email the Super User of this problem (only set
818 * if host is invalid)
819 *
820 * @param View $view
821 * @api
822 */
823 public static function setHostValidationVariablesView($view)
824 {
825 // check if host is valid
826 $view->isValidHost = Url::isValidHost();
827 if (!$view->isValidHost) {
828 // invalid host, so display warning to user
829 $validHosts = Url::getTrustedHostsFromConfig();
830 $validHost = $validHosts[0];
831 $invalidHost = Common::sanitizeInputValue(Url::getHost(false));
832
833 $emailSubject = rawurlencode(Piwik::translate('CoreHome_InjectedHostEmailSubject', $invalidHost));
834 $emailBody = rawurlencode(Piwik::translate('CoreHome_InjectedHostEmailBody'));
835 $superUserEmail = rawurlencode(implode(',', Piwik::getContactEmailAddresses()));
836
837 $mailToUrl = "mailto:$superUserEmail?subject=$emailSubject&body=$emailBody";
838 $mailLinkStart = "<a href=\"$mailToUrl\">";
839
840 $invalidUrl = Url::getCurrentUrlWithoutQueryString($checkIfTrusted = false);
841 $validUrl = Url::getCurrentScheme() . '://' . $validHost
842 . Url::getCurrentScriptName();
843 $invalidUrl = Common::sanitizeInputValue($invalidUrl);
844 $validUrl = Common::sanitizeInputValue($validUrl);
845
846 $changeTrustedHostsUrl = "index.php"
847 . Url::getCurrentQueryStringWithParametersModified(array(
848 'module' => 'CoreAdminHome',
849 'action' => 'generalSettings'
850 ))
851 . "#trustedHostsSection";
852
853 $warningStart = Piwik::translate('CoreHome_InjectedHostWarningIntro', array(
854 '<strong>' . $invalidUrl . '</strong>',
855 '<strong>' . $validUrl . '</strong>'
856 )) . ' <br/>';
857
858 if (Piwik::hasUserSuperUserAccess()) {
859 $view->invalidHostMessage = $warningStart . ' '
860 . Piwik::translate('CoreHome_InjectedHostSuperUserWarning', array(
861 "<a href=\"$changeTrustedHostsUrl\">",
862 $invalidHost,
863 '</a>',
864 "<br/><a href=\"$validUrl\">",
865 Common::sanitizeInputValue($validHost),
866 '</a>'
867 ));
868 } elseif (Piwik::isUserIsAnonymous()) {
869 $view->invalidHostMessage = $warningStart . ' '
870 . Piwik::translate('CoreHome_InjectedHostNonSuperUserWarning', array(
871 "<br/><a href=\"$validUrl\">",
872 '</a>',
873 '<span style="display:none">',
874 '</span>'
875 ));
876 } else {
877 $view->invalidHostMessage = $warningStart . ' '
878 . Piwik::translate('CoreHome_InjectedHostNonSuperUserWarning', array(
879 "<br/><a href=\"$validUrl\">",
880 '</a>',
881 $mailLinkStart,
882 '</a>'
883 ));
884 }
885 $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 /path/to/matomo/config/config.ini.php
886 and add the following lines: <pre>[General]' . "\n" . 'trusted_hosts[] = "' . $invalidHost . '"</pre>After making the change, you will be able to login again.</p>
887 <p>You may also <i>disable this security feature (not recommended)</i>. To do so edit config/config.ini.php and add:
888 <pre>[General]' . "\n" . 'enable_trusted_host_check=0</pre>';
889
890 $view->invalidHost = $invalidHost; // for UserSettings warning
891 $view->invalidHostMailLinkStart = $mailLinkStart;
892 }
893 }
894
895 /**
896 * Sets general period variables on a view, including:
897 *
898 * - **displayUniqueVisitors** - Whether unique visitors should be displayed for the current
899 * period.
900 * - **period** - The value of the **period** query parameter.
901 * - **otherPeriods** - `array('day', 'week', 'month', 'year', 'range')`
902 * - **periodsNames** - List of available periods mapped to their singular and plural translations.
903 *
904 * @param View $view
905 * @throws Exception if the current period is invalid.
906 * @api
907 */
908 public static function setPeriodVariablesView($view)
909 {
910 if (isset($view->period)) {
911 return;
912 }
913
914 $periodValidator = new PeriodValidator();
915
916 $currentPeriod = Piwik::getPeriod(GeneralConfig::getConfigValue('default_period'));
917 $availablePeriods = $periodValidator->getPeriodsAllowedForUI();
918
919 if (! $periodValidator->isPeriodAllowedForUI($currentPeriod)) {
920 throw new Exception("Period must be one of: " . implode(", ", $availablePeriods));
921 }
922
923 $view->displayUniqueVisitors = SettingsPiwik::isUniqueVisitorsEnabled($currentPeriod);
924
925 $found = array_search($currentPeriod, $availablePeriods);
926 unset($availablePeriods[$found]);
927
928 $view->period = $currentPeriod;
929 $view->otherPeriods = $availablePeriods;
930 $view->enabledPeriods = self::getEnabledPeriodsInUI();
931 $view->periodsNames = self::getEnabledPeriodsNames();
932 }
933
934 /**
935 * Helper method used to redirect the current HTTP request to another module/action.
936 *
937 * This function will exit immediately after executing.
938 *
939 * @param string $moduleToRedirect The plugin to redirect to, eg. `"MultiSites"`.
940 * @param string $actionToRedirect Action, eg. `"index"`.
941 * @param int|null $websiteId The new idSite query parameter, eg, `1`.
942 * @param string|null $defaultPeriod The new period query parameter, eg, `'day'`.
943 * @param string|null $defaultDate The new date query parameter, eg, `'today'`.
944 * @param array $parameters Other query parameters to append to the URL.
945 * @api
946 */
947 public function redirectToIndex($moduleToRedirect, $actionToRedirect, $websiteId = null, $defaultPeriod = null,
948 $defaultDate = null, $parameters = array())
949 {
950 try {
951 $this->doRedirectToUrl($moduleToRedirect, $actionToRedirect, $websiteId, $defaultPeriod, $defaultDate, $parameters);
952 } catch (Exception $e) {
953 // no website ID to default to, so could not redirect
954 }
955
956 if (Piwik::hasUserSuperUserAccess()) {
957 $siteTableName = Common::prefixTable('site');
958 $message = "Error: no website was found in this Matomo installation.
959 <br />Check the table '$siteTableName' in your database, it should contain your Matomo websites.";
960
961 $ex = new NoWebsiteFoundException($message);
962 $ex->setIsHtmlMessage();
963
964 throw $ex;
965 }
966
967 if (!Piwik::isUserIsAnonymous()) {
968 $currentLogin = Piwik::getCurrentUserLogin();
969 $emails = rawurlencode(implode(',', Piwik::getContactEmailAddresses()));
970 $errorMessage = sprintf(Piwik::translate('CoreHome_NoPrivilegesAskPiwikAdmin'), $currentLogin, "<br/><a href='mailto:" . $emails . "?subject=Access to Matomo for user $currentLogin'>", "</a>");
971 $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 />";
972
973 $ex = new NoPrivilegesException($errorMessage);
974 $ex->setIsHtmlMessage();
975
976 throw $ex;
977 }
978
979 echo FrontController::getInstance()->dispatch(Piwik::getLoginPluginName(), false);
980 exit;
981 }
982
983
984 /**
985 * Checks that the token_auth in the URL matches the currently logged-in user's token_auth.
986 *
987 * This is a protection against CSRF and should be used in all controller
988 * methods that modify Piwik or any user settings.
989 *
990 * If called from JavaScript by using the `ajaxHelper` you have to call `ajaxHelper.withTokenInUrl();` before
991 * `ajaxHandler.send();` to send the token along with the request.
992 *
993 * **The token_auth should never appear in the browser's address bar.**
994 *
995 * @throws \Piwik\NoAccessException If the token doesn't match.
996 * @api
997 */
998 protected function checkTokenInUrl()
999 {
1000 $tokenRequest = Common::getRequestVar('token_auth', false);
1001 $tokenUser = Piwik::getCurrentUserTokenAuth();
1002
1003 if (empty($tokenRequest) && empty($tokenUser)) {
1004 return; // UI tests
1005 }
1006
1007 if ($tokenRequest !== $tokenUser) {
1008 throw new NoAccessException(Piwik::translate('General_ExceptionSecurityCheckFailed'));
1009 }
1010 }
1011
1012 /**
1013 * Returns a prettified date string for use in period selector widget.
1014 *
1015 * @param Period $period The period to return a pretty string for.
1016 * @return string
1017 * @api
1018 */
1019 public static function getCalendarPrettyDate($period)
1020 {
1021 if ($period instanceof Month) {
1022 // show month name when period is for a month
1023
1024 return $period->getLocalizedLongString();
1025 } else {
1026 return $period->getPrettyString();
1027 }
1028 }
1029
1030 /**
1031 * Returns the pretty date representation
1032 *
1033 * @param $date string
1034 * @param $period string
1035 * @return string Pretty date
1036 */
1037 public static function getPrettyDate($date, $period)
1038 {
1039 return self::getCalendarPrettyDate(Period\Factory::build($period, Date::factory($date)));
1040 }
1041
1042 protected function checkSitePermission()
1043 {
1044 if (!empty($this->idSite)) {
1045 Access::getInstance()->checkUserHasViewAccess($this->idSite);
1046 new Site($this->idSite);
1047 } elseif (empty($this->site) || empty($this->idSite)) {
1048 throw new Exception("The requested website idSite is not found in the request, or is invalid.
1049 Please check that you are logged in Matomo and have permission to access the specified website.");
1050 }
1051 }
1052
1053 /**
1054 * @param $moduleToRedirect
1055 * @param $actionToRedirect
1056 * @param $websiteId
1057 * @param $defaultPeriod
1058 * @param $defaultDate
1059 * @param $parameters
1060 * @throws Exception
1061 */
1062 private function doRedirectToUrl($moduleToRedirect, $actionToRedirect, $websiteId, $defaultPeriod, $defaultDate, $parameters)
1063 {
1064 $menu = new Menu();
1065
1066 $parameters = array_merge(
1067 $menu->urlForDefaultUserParams($websiteId, $defaultPeriod, $defaultDate),
1068 $parameters
1069 );
1070 $queryParams = !empty($parameters) ? '&' . Url::getQueryStringFromParameters($parameters) : '';
1071 $url = "index.php?module=%s&action=%s";
1072 $url = sprintf($url, $moduleToRedirect, $actionToRedirect);
1073 $url = $url . $queryParams;
1074 Url::redirectToUrl($url);
1075 }
1076
1077 private function checkViewType($viewType)
1078 {
1079 if ($viewType === 'admin' && !($this instanceof ControllerAdmin)) {
1080 throw new Exception("'admin' view type is only allowed with ControllerAdmin class.");
1081 }
1082 }
1083 }
1084
1085