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