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