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 */ 9namespace Piwik\Plugins\SitesManager; 10 11use DateTimeZone; 12use Exception; 13use Piwik\Access; 14use Piwik\CacheId; 15use Piwik\Common; 16use Piwik\Container\StaticContainer; 17use Piwik\Date; 18use Piwik\Intl\Data\Provider\CurrencyDataProvider; 19use Matomo\Network\IPUtils; 20use Piwik\Option; 21use Piwik\Piwik; 22use Piwik\Plugin\SettingsProvider; 23use Piwik\Plugins\CorePluginsAdmin\SettingsMetadata; 24use Piwik\Plugins\WebsiteMeasurable\Settings\Urls; 25use Piwik\Settings\Measurable\MeasurableProperty; 26use Piwik\Settings\Measurable\MeasurableSettings; 27use Piwik\ProxyHttp; 28use Piwik\Scheduler\Scheduler; 29use Piwik\SettingsPiwik; 30use Piwik\SettingsServer; 31use Piwik\Site; 32use Piwik\Tracker; 33use Piwik\Tracker\Cache; 34use Piwik\Tracker\TrackerCodeGenerator; 35use Piwik\Measurable\Type; 36use Piwik\Translation\Translator; 37use Piwik\Url; 38use Piwik\UrlHelper; 39use Piwik\DataAccess\Model as CoreModel; 40 41/** 42 * The SitesManager API gives you full control on Websites in Matomo (create, update and delete), and many methods to retrieve websites based on various attributes. 43 * 44 * This API lets you create websites via "addSite", update existing websites via "updateSite" and delete websites via "deleteSite". 45 * When creating websites, it can be useful to access internal codes used by Matomo for currencies via "getCurrencyList", or timezones via "getTimezonesList". 46 * 47 * There are also many ways to request a list of websites: from the website ID via "getSiteFromId" or the site URL via "getSitesIdFromSiteUrl". 48 * Often, the most useful technique is to list all websites that are known to a current user, based on the token_auth, via 49 * "getSitesWithAdminAccess", "getSitesWithViewAccess" or "getSitesWithAtLeastViewAccess" (which returns both). 50 * 51 * Some methods will affect all websites globally: "setGlobalExcludedIps" will set the list of IPs to be excluded on all websites, 52 * "setGlobalExcludedQueryParameters" will set the list of URL parameters to remove from URLs for all websites. 53 * The existing values can be fetched via "getExcludedIpsGlobal" and "getExcludedQueryParametersGlobal". 54 * See also the documentation about <a href='http://matomo.org/docs/manage-websites/' rel='noreferrer' target='_blank'>Managing Websites</a> in Matomo. 55 * @method static \Piwik\Plugins\SitesManager\API getInstance() 56 */ 57class API extends \Piwik\Plugin\API 58{ 59 const DEFAULT_SEARCH_KEYWORD_PARAMETERS = 'q,query,s,search,searchword,k,keyword'; 60 const OPTION_EXCLUDED_IPS_GLOBAL = 'SitesManager_ExcludedIpsGlobal'; 61 const OPTION_DEFAULT_TIMEZONE = 'SitesManager_DefaultTimezone'; 62 const OPTION_DEFAULT_CURRENCY = 'SitesManager_DefaultCurrency'; 63 const OPTION_EXCLUDED_QUERY_PARAMETERS_GLOBAL = 'SitesManager_ExcludedQueryParameters'; 64 const OPTION_SEARCH_KEYWORD_QUERY_PARAMETERS_GLOBAL = 'SitesManager_SearchKeywordParameters'; 65 const OPTION_SEARCH_CATEGORY_QUERY_PARAMETERS_GLOBAL = 'SitesManager_SearchCategoryParameters'; 66 const OPTION_EXCLUDED_USER_AGENTS_GLOBAL = 'SitesManager_ExcludedUserAgentsGlobal'; 67 const OPTION_KEEP_URL_FRAGMENTS_GLOBAL = 'SitesManager_KeepURLFragmentsGlobal'; 68 69 /** 70 * @var SettingsProvider 71 */ 72 private $settingsProvider; 73 74 /** 75 * @var SettingsMetadata 76 */ 77 private $settingsMetadata; 78 79 /** 80 * @var Translator 81 */ 82 private $translator; 83 84 private $timezoneNameCache = []; 85 86 public function __construct(SettingsProvider $provider, SettingsMetadata $settingsMetadata, Translator $translator) 87 { 88 $this->settingsProvider = $provider; 89 $this->settingsMetadata = $settingsMetadata; 90 $this->translator = $translator; 91 } 92 93 /** 94 * Returns the javascript tag for the given idSite. 95 * This tag must be included on every page to be tracked by Matomo 96 * 97 * @param int $idSite 98 * @param string $piwikUrl 99 * @param bool $mergeSubdomains 100 * @param bool $groupPageTitlesByDomain 101 * @param bool $mergeAliasUrls 102 * @param bool $visitorCustomVariables 103 * @param bool $pageCustomVariables 104 * @param bool $customCampaignNameQueryParam 105 * @param bool $customCampaignKeywordParam 106 * @param bool $doNotTrack 107 * @param bool $disableCookies 108 * @param bool $trackNoScript 109 * @param bool $crossDomain 110 * @param bool $forceMatomoEndpoint Whether the Matomo endpoint should be forced if Matomo was installed prior 3.7.0. 111 * @param bool $excludedQueryParams 112 * 113 * @return string The Javascript tag ready to be included on the HTML pages 114 * @throws Exception 115 */ 116 public function getJavascriptTag($idSite, $piwikUrl = '', $mergeSubdomains = false, $groupPageTitlesByDomain = false, 117 $mergeAliasUrls = false, $visitorCustomVariables = false, $pageCustomVariables = false, 118 $customCampaignNameQueryParam = false, $customCampaignKeywordParam = false, 119 $doNotTrack = false, $disableCookies = false, $trackNoScript = false, 120 $crossDomain = false, $forceMatomoEndpoint = false, $excludedQueryParams = false) 121 { 122 Piwik::checkUserHasViewAccess($idSite); 123 124 if (empty($piwikUrl)) { 125 $piwikUrl = SettingsPiwik::getPiwikUrl(); 126 } 127 128 // Revert the automatic encoding 129 // TODO remove that when https://github.com/piwik/piwik/issues/4231 is fixed 130 $piwikUrl = Common::unsanitizeInputValue($piwikUrl); 131 $visitorCustomVariables = Common::unsanitizeInputValues($visitorCustomVariables); 132 $pageCustomVariables = Common::unsanitizeInputValues($pageCustomVariables); 133 $customCampaignNameQueryParam = Common::unsanitizeInputValue($customCampaignNameQueryParam); 134 $customCampaignKeywordParam = Common::unsanitizeInputValue($customCampaignKeywordParam); 135 136 if (is_array($excludedQueryParams)) { 137 $excludedQueryParams = implode(',', $excludedQueryParams); 138 } 139 $excludedQueryParams = Common::unsanitizeInputValue($excludedQueryParams); 140 141 $generator = new TrackerCodeGenerator(); 142 if ($forceMatomoEndpoint) { 143 $generator->forceMatomoEndpoint(); 144 } 145 146 $code = $generator->generate($idSite, $piwikUrl, $mergeSubdomains, $groupPageTitlesByDomain, 147 $mergeAliasUrls, $visitorCustomVariables, $pageCustomVariables, 148 $customCampaignNameQueryParam, $customCampaignKeywordParam, 149 $doNotTrack, $disableCookies, $trackNoScript, $crossDomain, 150 $excludedQueryParams); 151 $code = str_replace(array('<br>', '<br />', '<br/>'), '', $code); 152 return $code; 153 } 154 155 /** 156 * Returns image link tracking code for a given site with specified options. 157 * 158 * @param int $idSite The ID to generate tracking code for. 159 * @param string $piwikUrl The domain and URL path to the Matomo installation. 160 * @param int $idGoal An ID for a goal to trigger a conversion for. 161 * @param int $revenue The revenue of the goal conversion. Only used if $idGoal is supplied. 162 * @param bool $forceMatomoEndpoint Whether the Matomo endpoint should be forced if Matomo was installed prior 3.7.0. 163 * @return string The HTML tracking code. 164 */ 165 public function getImageTrackingCode($idSite, $piwikUrl = '', $actionName = false, $idGoal = false, $revenue = false, $forceMatomoEndpoint = false) 166 { 167 $urlParams = array('idsite' => $idSite, 'rec' => 1); 168 169 if ($actionName !== false) { 170 $urlParams['action_name'] = urlencode(Common::unsanitizeInputValue($actionName)); 171 } 172 173 if ($idGoal !== false) { 174 $urlParams['idgoal'] = $idGoal; 175 if ($revenue !== false) { 176 $urlParams['revenue'] = $revenue; 177 } 178 } 179 180 /** 181 * Triggered when generating image link tracking code server side. Plugins can use 182 * this event to customise the image tracking code that is displayed to the 183 * user. 184 * 185 * @param string &$piwikHost The domain and URL path to the Matomo installation, eg, 186 * `'examplepiwik.com/path/to/piwik'`. 187 * @param array &$urlParams The query parameters used in the <img> element's src 188 * URL. See Matomo's image tracking docs for more info. 189 */ 190 Piwik::postEvent('SitesManager.getImageTrackingCode', array(&$piwikUrl, &$urlParams)); 191 192 $trackerCodeGenerator = new TrackerCodeGenerator(); 193 if ($forceMatomoEndpoint) { 194 $trackerCodeGenerator->forceMatomoEndpoint(); 195 } 196 $matomoPhp = $trackerCodeGenerator->getPhpTrackerEndpoint(); 197 198 $url = (ProxyHttp::isHttps() ? "https://" : "http://") . rtrim($piwikUrl, '/') . '/'.$matomoPhp.'?' . Url::getQueryStringFromParameters($urlParams); 199 $html = "<!-- Matomo Image Tracker--> 200<img referrerpolicy=\"no-referrer-when-downgrade\" src=\"" . htmlspecialchars($url, ENT_COMPAT, 'UTF-8') . "\" style=\"border:0\" alt=\"\" /> 201<!-- End Matomo -->"; 202 return htmlspecialchars($html, ENT_COMPAT, 'UTF-8'); 203 } 204 205 /** 206 * Returns all websites belonging to the specified group 207 * @param string $group Group name 208 * @return array of sites 209 */ 210 public function getSitesFromGroup($group = '') 211 { 212 Piwik::checkUserHasSuperUserAccess(); 213 214 $group = trim($group); 215 $sites = $this->getModel()->getSitesFromGroup($group); 216 217 foreach ($sites as &$site) { 218 $this->enrichSite($site); 219 } 220 221 $sites = Site::setSitesFromArray($sites); 222 return $sites; 223 } 224 225 /** 226 * Returns the list of website groups, including the empty group 227 * if no group were specified for some websites 228 * 229 * @return array of group names strings 230 */ 231 public function getSitesGroups() 232 { 233 Piwik::checkUserHasSuperUserAccess(); 234 235 $groups = $this->getModel()->getSitesGroups(); 236 $cleanedGroups = array_map('trim', $groups); 237 238 return $cleanedGroups; 239 } 240 241 /** 242 * Returns the website information : name, main_url 243 * 244 * @throws Exception if the site ID doesn't exist or the user doesn't have access to it 245 * @param int $idSite 246 * @return array 247 */ 248 public function getSiteFromId($idSite) 249 { 250 Piwik::checkUserHasViewAccess($idSite); 251 252 $site = $this->getModel()->getSiteFromId($idSite); 253 254 if ($site) { 255 $this->enrichSite($site); 256 } 257 258 Site::setSiteFromArray($idSite, $site); 259 260 return $site; 261 } 262 263 private function getModel() 264 { 265 return new Model(); 266 } 267 268 /** 269 * Returns the list of all URLs registered for the given idSite (main_url + alias URLs). 270 * 271 * @throws Exception if the website ID doesn't exist or the user doesn't have access to it 272 * @param int $idSite 273 * @return array list of URLs 274 */ 275 public function getSiteUrlsFromId($idSite) 276 { 277 Piwik::checkUserHasViewAccess($idSite); 278 return $this->getModel()->getSiteUrlsFromId($idSite); 279 } 280 281 private function getSitesId() 282 { 283 return $this->getModel()->getSitesId(); 284 } 285 286 /** 287 * Returns all websites, requires Super User access 288 * 289 * @return array The list of websites, indexed by idsite 290 */ 291 public function getAllSites() 292 { 293 Piwik::checkUserHasSuperUserAccess(); 294 295 $sites = $this->getModel()->getAllSites(); 296 $return = array(); 297 foreach ($sites as $site) { 298 $this->enrichSite($site); 299 $return[$site['idsite']] = $site; 300 } 301 302 $return = Site::setSitesFromArray($return); 303 304 return $return; 305 } 306 307 /** 308 * Returns the list of all the website IDs registered. 309 * Requires Super User access. 310 * 311 * @return array The list of website IDs 312 */ 313 public function getAllSitesId() 314 { 315 Piwik::checkUserHasSuperUserAccess(); 316 try { 317 return $this->getSitesId(); 318 } catch (Exception $e) { 319 // can be called before Matomo tables are created so return empty 320 return array(); 321 } 322 } 323 324 /** 325 * Returns the list of websites with the 'admin' access for the current user. 326 * For the superUser it returns all the websites in the database. 327 * 328 * @param bool $fetchAliasUrls 329 * @param false|string $pattern 330 * @param false|int $limit 331 * @return array for each site, an array of information (idsite, name, main_url, etc.) 332 */ 333 public function getSitesWithAdminAccess($fetchAliasUrls = false, $pattern = false, $limit = false) 334 { 335 $sitesId = $this->getSitesIdWithAdminAccess(); 336 337 if ($pattern === false) { 338 $sites = $this->getSitesFromIds($sitesId, $limit); 339 } else { 340 $sites = $this->getModel()->getPatternMatchSites($sitesId, $pattern, $limit); 341 342 foreach ($sites as &$site) { 343 $this->enrichSite($site); 344 } 345 346 $sites = Site::setSitesFromArray($sites); 347 } 348 349 if ($fetchAliasUrls) { 350 foreach ($sites as &$site) { 351 $site['alias_urls'] = $this->getSiteUrlsFromId($site['idsite']); 352 } 353 } 354 355 return $sites; 356 } 357 358 /** 359 * Returns the list of websites with the 'view' access for the current user. 360 * For the superUser it doesn't return any result because the superUser has admin access on all the websites (use getSitesWithAtLeastViewAccess() instead). 361 * 362 * @return array for each site, an array of information (idsite, name, main_url, etc.) 363 */ 364 public function getSitesWithViewAccess() 365 { 366 $sitesId = $this->getSitesIdWithViewAccess(); 367 return $this->getSitesFromIds($sitesId); 368 } 369 370 /** 371 * Returns the list of websites with the 'view' or 'admin' access for the current user. 372 * For the superUser it returns all the websites in the database. 373 * 374 * @param bool|int $limit Specify max number of sites to return 375 * @param bool $_restrictSitesToLogin Hack necessary when running scheduled tasks, where "Super User" is forced, but sometimes not desired, see #3017 376 * @return array array for each site, an array of information (idsite, name, main_url, etc.) 377 */ 378 public function getSitesWithAtLeastViewAccess($limit = false, $_restrictSitesToLogin = false) 379 { 380 $sitesId = $this->getSitesIdWithAtLeastViewAccess($_restrictSitesToLogin); 381 return $this->getSitesFromIds($sitesId, $limit); 382 } 383 384 /** 385 * Returns the list of websites ID with the 'admin' access for the current user. 386 * For the superUser it returns all the websites in the database. 387 * 388 * @return array list of websites ID 389 */ 390 public function getSitesIdWithAdminAccess() 391 { 392 $sitesId = Access::getInstance()->getSitesIdWithAdminAccess(); 393 return $sitesId; 394 } 395 396 /** 397 * Returns the list of websites ID with the 'view' access for the current user. 398 * For the superUser it doesn't return any result because the superUser has admin access on all the websites (use getSitesIdWithAtLeastViewAccess() instead). 399 * 400 * @return array list of websites ID 401 */ 402 public function getSitesIdWithViewAccess() 403 { 404 return Access::getInstance()->getSitesIdWithViewAccess(); 405 } 406 407 /** 408 * Returns the list of websites ID with the 'write' access for the current user. 409 * For the superUser it doesn't return any result because the superUser has write access on all the websites (use getSitesIdWithAtLeastWriteAccess() instead). 410 * 411 * @return array list of websites ID 412 */ 413 public function getSitesIdWithWriteAccess() 414 { 415 return Access::getInstance()->getSitesIdWithWriteAccess(); 416 } 417 418 /** 419 * Returns the list of websites ID with the 'view' or 'admin' access for the current user. 420 * For the superUser it returns all the websites in the database. 421 * 422 * @param bool $_restrictSitesToLogin 423 * @return array list of websites ID 424 */ 425 public function getSitesIdWithAtLeastViewAccess($_restrictSitesToLogin = false) 426 { 427 /** @var Scheduler $scheduler */ 428 $scheduler = StaticContainer::getContainer()->get('Piwik\Scheduler\Scheduler'); 429 430 if (Piwik::hasUserSuperUserAccess() && !$scheduler->isRunningTask()) { 431 return Access::getInstance()->getSitesIdWithAtLeastViewAccess(); 432 } 433 434 if (!empty($_restrictSitesToLogin) 435 // Only Super User or logged in user can see viewable sites for a specific login, 436 // but during scheduled task execution, we sometimes want to restrict sites to 437 // a different login than the superuser. 438 && (Piwik::hasUserSuperUserAccessOrIsTheUser($_restrictSitesToLogin) 439 || $scheduler->isRunningTask()) 440 ) { 441 442 if (Piwik::hasTheUserSuperUserAccess($_restrictSitesToLogin)) { 443 return Access::getInstance()->getSitesIdWithAtLeastViewAccess(); 444 } 445 446 $accessRaw = Access::getInstance()->getRawSitesWithSomeViewAccess($_restrictSitesToLogin); 447 $sitesId = array(); 448 449 foreach ($accessRaw as $access) { 450 $sitesId[] = $access['idsite']; 451 } 452 453 return $sitesId; 454 } else { 455 return Access::getInstance()->getSitesIdWithAtLeastViewAccess(); 456 } 457 } 458 459 /** 460 * Returns the list of websites from the ID array in parameters. 461 * The user access is not checked in this method so the ID have to be accessible by the user! 462 * 463 * @param array $idSites list of website ID 464 * @param bool $limit 465 * @return array 466 */ 467 private function getSitesFromIds($idSites, $limit = false) 468 { 469 $sites = $this->getModel()->getSitesFromIds($idSites, $limit); 470 471 foreach ($sites as &$site) { 472 $this->enrichSite($site); 473 } 474 475 $sites = Site::setSitesFromArray($sites); 476 477 return $sites; 478 } 479 480 protected function getNormalizedUrls($url) 481 { 482 // if found, remove scheme and www. from URL 483 $hostname = str_replace('www.', '', $url); 484 $hostname = str_replace('http://', '', $hostname); 485 $hostname = str_replace('https://', '', $hostname); 486 487 // return all variations of the URL 488 return array( 489 $url, 490 "http://" . $hostname, 491 "http://www." . $hostname, 492 "https://" . $hostname, 493 "https://www." . $hostname 494 ); 495 } 496 497 /** 498 * Returns the list of websites ID associated with a URL. 499 * 500 * @param string $url 501 * @return array list of websites ID 502 */ 503 public function getSitesIdFromSiteUrl($url) 504 { 505 $url = $this->removeTrailingSlash($url); 506 $normalisedUrls = $this->getNormalizedUrls($url); 507 508 if (Piwik::hasUserSuperUserAccess()) { 509 $ids = $this->getModel()->getAllSitesIdFromSiteUrl($normalisedUrls); 510 } else { 511 $login = Piwik::getCurrentUserLogin(); 512 $ids = $this->getModel()->getSitesIdFromSiteUrlHavingAccess($login, $normalisedUrls); 513 } 514 515 return $ids; 516 } 517 518 /** 519 * Returns all websites with a timezone matching one the specified timezones 520 * 521 * @param array $timezones 522 * @return array 523 * @ignore 524 */ 525 public function getSitesIdFromTimezones($timezones) 526 { 527 Piwik::checkUserHasSuperUserAccess(); 528 529 $timezones = Piwik::getArrayFromApiParameter($timezones); 530 $timezones = array_unique($timezones); 531 532 $ids = $this->getModel()->getSitesFromTimezones($timezones); 533 534 $return = array(); 535 foreach ($ids as $id) { 536 $return[] = $id['idsite']; 537 } 538 539 return $return; 540 } 541 542 private function enrichSite(&$site) 543 { 544 $cacheKey = $site['timezone'] . $this->translator->getCurrentLanguage(); 545 if (!isset($this->timezoneNameCache[$cacheKey])) { 546 //cached as this can be called VERY often and getTimezoneName is quite slow 547 $this->timezoneNameCache[$cacheKey] = $this->getTimezoneName($site['timezone']); 548 } 549 $site['timezone_name'] = $this->timezoneNameCache[$cacheKey]; 550 551 $key = 'Intl_Currency_' . $site['currency']; 552 $name = $this->translator->translate($key); 553 554 $site['currency_name'] = ($key === $name) ? $site['currency'] : $name; 555 556 // don't want to expose other user logins here 557 if (!Piwik::hasUserSuperUserAccess()) { 558 unset($site['creator_login']); 559 } 560 } 561 562 /** 563 * Add a website. 564 * Requires Super User access. 565 * 566 * The website is defined by a name and an array of URLs. 567 * @param string $siteName Site name 568 * @param array|string $urls The URLs array must contain at least one URL called the 'main_url' ; 569 * if several URLs are provided in the array, they will be recorded 570 * as Alias URLs for this website. 571 * When calling API via HTTP specify multiple URLs via `&urls[]=http...&urls[]=http...`. 572 * @param int $ecommerce Is Ecommerce Reporting enabled for this website? 573 * @param null $siteSearch 574 * @param string $searchKeywordParameters Comma separated list of search keyword parameter names 575 * @param string $searchCategoryParameters Comma separated list of search category parameter names 576 * @param string $excludedIps Comma separated list of IPs to exclude from the reports (allows wildcards) 577 * @param null $excludedQueryParameters 578 * @param string $timezone Timezone string, eg. 'Europe/London' 579 * @param string $currency Currency, eg. 'EUR' 580 * @param string $group Website group identifier 581 * @param string $startDate Date at which the statistics for this website will start. Defaults to today's date in YYYY-MM-DD format 582 * @param null|string $excludedUserAgents 583 * @param int $keepURLFragments If 1, URL fragments will be kept when tracking. If 2, they 584 * will be removed. If 0, the default global behavior will be used. 585 * @param array|null $settingValues JSON serialized settings eg {settingName: settingValue, ...} 586 * @see getKeepURLFragmentsGlobal. 587 * @param string $type The website type, defaults to "website" if not set. 588 * @param bool|null $excludeUnknownUrls Track only URL matching one of website URLs 589 * 590 * @return int the website ID created 591 */ 592 public function addSite($siteName, 593 $urls = null, 594 $ecommerce = null, 595 $siteSearch = null, 596 $searchKeywordParameters = null, 597 $searchCategoryParameters = null, 598 $excludedIps = null, 599 $excludedQueryParameters = null, 600 $timezone = null, 601 $currency = null, 602 $group = null, 603 $startDate = null, 604 $excludedUserAgents = null, 605 $keepURLFragments = null, 606 $type = null, 607 $settingValues = null, 608 $excludeUnknownUrls = null) 609 { 610 Piwik::checkUserHasSuperUserAccess(); 611 SitesManager::dieIfSitesAdminIsDisabled(); 612 613 $this->checkName($siteName); 614 615 if (!isset($settingValues)) { 616 $settingValues = array(); 617 } 618 619 $coreProperties = array(); 620 $coreProperties = $this->setSettingValue('urls', $urls, $coreProperties, $settingValues); 621 $coreProperties = $this->setSettingValue('ecommerce', $ecommerce, $coreProperties, $settingValues); 622 $coreProperties = $this->setSettingValue('group', $group, $coreProperties, $settingValues); 623 $coreProperties = $this->setSettingValue('sitesearch', $siteSearch, $coreProperties, $settingValues); 624 $coreProperties = $this->setSettingValue('sitesearch_keyword_parameters', explode(',', $searchKeywordParameters ?? ''), $coreProperties, $settingValues); 625 $coreProperties = $this->setSettingValue('sitesearch_category_parameters', explode(',', $searchCategoryParameters ?? ''), $coreProperties, $settingValues); 626 $coreProperties = $this->setSettingValue('keep_url_fragment', $keepURLFragments, $coreProperties, $settingValues); 627 $coreProperties = $this->setSettingValue('exclude_unknown_urls', $excludeUnknownUrls, $coreProperties, $settingValues); 628 $coreProperties = $this->setSettingValue('excluded_ips', explode(',', $excludedIps ?? ''), $coreProperties, $settingValues); 629 $coreProperties = $this->setSettingValue('excluded_parameters', explode(',', $excludedQueryParameters ?? ''), $coreProperties, $settingValues); 630 $coreProperties = $this->setSettingValue('excluded_user_agents', explode(',', $excludedUserAgents ?? ''), $coreProperties, $settingValues); 631 632 $timezone = trim($timezone ?? ''); 633 if (empty($timezone)) { 634 $timezone = $this->getDefaultTimezone(); 635 } 636 $this->checkValidTimezone($timezone); 637 638 if (empty($currency)) { 639 $currency = $this->getDefaultCurrency(); 640 } 641 $this->checkValidCurrency($currency); 642 643 $bind = array('name' => $siteName); 644 $bind['timezone'] = $timezone; 645 $bind['currency'] = $currency; 646 $bind['main_url'] = ''; 647 648 if (is_null($startDate)) { 649 $bind['ts_created'] = Date::now()->getDatetime(); 650 } else { 651 $bind['ts_created'] = Date::factory($startDate)->getDatetime(); 652 } 653 654 $bind['type'] = $this->checkAndReturnType($type); 655 656 if (!empty($group) && Piwik::hasUserSuperUserAccess()) { 657 $bind['group'] = trim($group); 658 } else { 659 $bind['group'] = ""; 660 } 661 662 $bind['creator_login'] = Piwik::getCurrentUserLogin(); 663 664 $allSettings = $this->setAndValidateMeasurableSettings(0, 'website', $coreProperties); 665 666 // any setting specified in setting values will overwrite other setting 667 if (!empty($settingValues)) { 668 $this->setAndValidateMeasurableSettings(0, $bind['type'], $settingValues); 669 } 670 671 foreach ($allSettings as $settings) { 672 foreach ($settings->getSettingsWritableByCurrentUser() as $setting) { 673 $name = $setting->getName(); 674 if ($setting instanceof MeasurableProperty && $name !== 'urls') { 675 $default = $setting->getDefaultValue(); 676 if (is_bool($default)) { 677 $default = (int) $default; 678 } elseif (is_array($default)) { 679 $default = implode(',', $default); 680 } 681 682 $bind[$name] = $default; 683 } 684 } 685 } 686 687 $idSite = $this->getModel()->createSite($bind); 688 689 if (!empty($coreProperties)) { 690 $this->saveMeasurableSettings($idSite, 'website', $coreProperties); 691 } 692 if (!empty($settingValues)) { 693 $this->saveMeasurableSettings($idSite, $bind['type'], $settingValues); 694 } 695 696 // we reload the access list which doesn't yet take in consideration this new website 697 Access::getInstance()->reloadAccess(); 698 699 $this->postUpdateWebsite($idSite); 700 701 /** 702 * Triggered after a site has been added. 703 * 704 * @param int $idSite The ID of the site that was added. 705 */ 706 Piwik::postEvent('SitesManager.addSite.end', array($idSite)); 707 708 return (int) $idSite; 709 } 710 711 private function setSettingValue($fieldName, $value, $coreProperties, $settingValues) 712 { 713 $pluginName = 'WebsiteMeasurable'; 714 715 if (isset($value)) { 716 717 if (empty($coreProperties[$pluginName])) { 718 $coreProperties[$pluginName] = array(); 719 } 720 721 $coreProperties[$pluginName][] = array('name' => $fieldName, 'value' => $value); 722 723 } elseif (!empty($settingValues[$pluginName])) { 724 // we check if the value is defined in the setting values instead 725 foreach ($settingValues[$pluginName] as $key => $setting) { 726 if ($setting['name'] === $fieldName) { 727 728 if (empty($coreProperties[$pluginName])) { 729 $coreProperties[$pluginName] = array(); 730 } 731 732 $coreProperties[$pluginName][] = array('name' => $fieldName, 'value' => $setting['value']); 733 return $coreProperties; 734 } 735 } 736 } 737 738 return $coreProperties; 739 } 740 741 public function getSiteSettings($idSite) 742 { 743 Piwik::checkUserHasAdminAccess($idSite); 744 745 $measurableSettings = $this->settingsProvider->getAllMeasurableSettings($idSite, $idMeasurableType = false); 746 747 return $this->settingsMetadata->formatSettings($measurableSettings); 748 } 749 750 private function setAndValidateMeasurableSettings($idSite, $idType, $settingValues) 751 { 752 $measurableSettings = $this->settingsProvider->getAllMeasurableSettings($idSite, $idType); 753 754 $this->settingsMetadata->setPluginSettings($measurableSettings, $settingValues); 755 756 return $measurableSettings; 757 } 758 759 /** 760 * @param MeasurableSettings[] $measurableSettings 761 */ 762 private function saveMeasurableSettings($idSite, $idType, $settingValues) 763 { 764 $measurableSettings = $this->setAndValidateMeasurableSettings($idSite, $idType, $settingValues); 765 766 foreach ($measurableSettings as $measurableSetting) { 767 $measurableSetting->save(); 768 } 769 } 770 771 private function postUpdateWebsite($idSite) 772 { 773 Site::clearCache(); 774 Cache::regenerateCacheWebsiteAttributes($idSite); 775 Cache::clearCacheGeneral(); 776 SiteUrls::clearSitesCache(); 777 } 778 779 /** 780 * Delete a website from the database, given its Id. The method deletes the actual site as well as some associated 781 * data. However, it does not delete any logs or archives that belong to this website. You can delete logs and 782 * archives for a site manually as described in this FAQ: http://matomo.org/faq/how-to/faq_73/ . 783 * 784 * Requires Super User access. 785 * 786 * @param int $idSite 787 * @throws Exception 788 */ 789 public function deleteSite($idSite) 790 { 791 Piwik::checkUserHasSuperUserAccess(); 792 SitesManager::dieIfSitesAdminIsDisabled(); 793 794 $idSites = $this->getSitesId(); 795 if (!in_array($idSite, $idSites)) { 796 throw new Exception("website id = $idSite not found"); 797 } 798 $nbSites = count($idSites); 799 if ($nbSites == 1) { 800 throw new Exception($this->translator->translate("SitesManager_ExceptionDeleteSite")); 801 } 802 803 $this->getModel()->deleteSite($idSite); 804 805 $coreModel = new CoreModel(); 806 $coreModel->deleteInvalidationsForSites([$idSite]); 807 808 /** 809 * Triggered after a site has been deleted. 810 * 811 * Plugins can use this event to remove site specific values or settings, such as removing all 812 * goals that belong to a specific website. If you store any data related to a website you 813 * should clean up that information here. 814 * 815 * @param int $idSite The ID of the site being deleted. 816 */ 817 Piwik::postEvent('SitesManager.deleteSite.end', array($idSite)); 818 } 819 820 private function checkValidTimezone($timezone) 821 { 822 $timezones = $this->getTimezonesList(); 823 foreach (array_values($timezones) as $cities) { 824 foreach ($cities as $timezoneId => $city) { 825 if ($timezoneId == $timezone) { 826 return true; 827 } 828 } 829 } 830 throw new Exception($this->translator->translate('SitesManager_ExceptionInvalidTimezone', array($timezone))); 831 } 832 833 private function checkValidCurrency($currency) 834 { 835 if (!in_array($currency, array_keys($this->getCurrencyList()))) { 836 throw new Exception($this->translator->translate('SitesManager_ExceptionInvalidCurrency', array($currency, "USD, EUR, etc."))); 837 } 838 } 839 840 private function checkAndReturnType($type) 841 { 842 if (empty($type)) { 843 $type = Site::DEFAULT_SITE_TYPE; 844 } 845 846 if (!is_string($type)) { 847 throw new Exception("Invalid website type $type"); 848 } 849 850 return $type; 851 } 852 853 /** 854 * Checks that the submitted IPs (comma separated list) are valid 855 * Returns the cleaned up IPs 856 * 857 * @param string $excludedIps Comma separated list of IP addresses 858 * @throws Exception 859 * @return array of IPs 860 */ 861 private function checkAndReturnExcludedIps($excludedIps) 862 { 863 if (empty($excludedIps)) { 864 return ''; 865 } 866 867 $ips = explode(',', $excludedIps); 868 $ips = array_map('trim', $ips); 869 $ips = array_filter($ips, 'strlen'); 870 871 foreach ($ips as $ip) { 872 if (!$this->isValidIp($ip)) { 873 throw new Exception($this->translator->translate('SitesManager_ExceptionInvalidIPFormat', array($ip, "1.2.3.4, 1.2.3.*, or 1.2.3.4/5"))); 874 } 875 } 876 877 $ips = implode(',', $ips); 878 return $ips; 879 } 880 881 /** 882 * Add a list of alias Urls to the given idSite 883 * 884 * If some URLs given in parameter are already recorded as alias URLs for this website, 885 * they won't be duplicated. The 'main_url' of the website won't be affected by this method. 886 * 887 * @param int $idSite 888 * @param array|string $urls When calling API via HTTP specify multiple URLs via `&urls[]=http...&urls[]=http...`. 889 * @return int the number of inserted URLs 890 */ 891 public function addSiteAliasUrls($idSite, $urls) 892 { 893 Piwik::checkUserHasAdminAccess($idSite); 894 895 if (empty($urls)) { 896 return 0; 897 } 898 899 if (!is_array($urls)) { 900 $urls = array($urls); 901 } 902 903 $urlsInit = $this->getSiteUrlsFromId($idSite); 904 $toInsert = array_merge($urlsInit, $urls); 905 906 $urlsProperty = new Urls($idSite); 907 $urlsProperty->setValue($toInsert); 908 $urlsProperty->save(); 909 910 $inserted = array_diff($urlsProperty->getValue(), $urlsInit); 911 912 $this->postUpdateWebsite($idSite); 913 914 return count($inserted); 915 } 916 917 /** 918 * Set the list of alias Urls for the given idSite 919 * 920 * Completely overwrites the current list of URLs with the provided list. 921 * The 'main_url' of the website won't be affected by this method. 922 * 923 * @return int the number of inserted URLs 924 */ 925 public function setSiteAliasUrls($idSite, $urls = array()) 926 { 927 Piwik::checkUserHasAdminAccess($idSite); 928 929 $mainUrl = Site::getMainUrlFor($idSite); 930 array_unshift($urls, $mainUrl); 931 932 $urlsProperty = new Urls($idSite); 933 $urlsProperty->setValue($urls); 934 $urlsProperty->save(); 935 936 $inserted = array_diff($urlsProperty->getValue(), $urls); 937 938 $this->postUpdateWebsite($idSite); 939 940 return count($inserted); 941 } 942 943 /** 944 * Get the start and end IP addresses for an IP address range 945 * 946 * @param string $ipRange IP address range in presentation format 947 * @return array|false Array( low, high ) IP addresses in presentation format; or false if error 948 */ 949 public function getIpsForRange($ipRange) 950 { 951 $range = IPUtils::getIPRangeBounds($ipRange); 952 if ($range === null) { 953 return false; 954 } 955 956 return array(IPUtils::binaryToStringIP($range[0]), IPUtils::binaryToStringIP($range[1])); 957 } 958 959 /** 960 * Sets IPs to be excluded from all websites. IPs can contain wildcards. 961 * Will also apply to websites created in the future. 962 * 963 * @param string $excludedIps Comma separated list of IPs to exclude from being tracked (allows wildcards) 964 * @return bool 965 */ 966 public function setGlobalExcludedIps($excludedIps) 967 { 968 Piwik::checkUserHasSuperUserAccess(); 969 $excludedIps = $this->checkAndReturnExcludedIps($excludedIps); 970 Option::set(self::OPTION_EXCLUDED_IPS_GLOBAL, $excludedIps); 971 Cache::deleteTrackerCache(); 972 return true; 973 } 974 975 /** 976 * Sets Site Search keyword/category parameter names, to be used on websites which have not specified these values 977 * Expects Comma separated list of query params names 978 * 979 * @param string 980 * @param string 981 * @return bool 982 */ 983 public function setGlobalSearchParameters($searchKeywordParameters, $searchCategoryParameters) 984 { 985 Piwik::checkUserHasSuperUserAccess(); 986 Option::set(self::OPTION_SEARCH_KEYWORD_QUERY_PARAMETERS_GLOBAL, $searchKeywordParameters); 987 Option::set(self::OPTION_SEARCH_CATEGORY_QUERY_PARAMETERS_GLOBAL, $searchCategoryParameters); 988 Cache::deleteTrackerCache(); 989 return true; 990 } 991 992 /** 993 * @return string Comma separated list of URL parameters 994 */ 995 public function getSearchKeywordParametersGlobal() 996 { 997 Piwik::checkUserHasSomeAdminAccess(); 998 $names = Option::get(self::OPTION_SEARCH_KEYWORD_QUERY_PARAMETERS_GLOBAL); 999 if ($names === false) { 1000 $names = self::DEFAULT_SEARCH_KEYWORD_PARAMETERS; 1001 } 1002 if (empty($names)) { 1003 $names = ''; 1004 } 1005 return $names; 1006 } 1007 1008 /** 1009 * @return string Comma separated list of URL parameters 1010 */ 1011 public function getSearchCategoryParametersGlobal() 1012 { 1013 Piwik::checkUserHasSomeAdminAccess(); 1014 return Option::get(self::OPTION_SEARCH_CATEGORY_QUERY_PARAMETERS_GLOBAL); 1015 } 1016 1017 /** 1018 * Returns the list of URL query parameters that are excluded from all websites 1019 * 1020 * @return string Comma separated list of URL parameters 1021 */ 1022 public function getExcludedQueryParametersGlobal() 1023 { 1024 Piwik::checkUserHasSomeViewAccess(); 1025 return Option::get(self::OPTION_EXCLUDED_QUERY_PARAMETERS_GLOBAL); 1026 } 1027 1028 /** 1029 * Returns the list of user agent substrings to look for when excluding visits for 1030 * all websites. If a visitor's user agent string contains one of these substrings, 1031 * their visits will not be included. 1032 * 1033 * @return string Comma separated list of strings. 1034 */ 1035 public function getExcludedUserAgentsGlobal() 1036 { 1037 Piwik::checkUserHasSomeAdminAccess(); 1038 return Option::get(self::OPTION_EXCLUDED_USER_AGENTS_GLOBAL); 1039 } 1040 1041 /** 1042 * Sets list of user agent substrings to look for when excluding visits. For more info, 1043 * @see getExcludedUserAgentsGlobal. 1044 * 1045 * @param string $excludedUserAgents Comma separated list of strings. Each element is trimmed, 1046 * and empty strings are removed. 1047 */ 1048 public function setGlobalExcludedUserAgents($excludedUserAgents) 1049 { 1050 Piwik::checkUserHasSuperUserAccess(); 1051 1052 // update option 1053 $excludedUserAgents = $this->checkAndReturnCommaSeparatedStringList($excludedUserAgents); 1054 Option::set(self::OPTION_EXCLUDED_USER_AGENTS_GLOBAL, $excludedUserAgents); 1055 1056 // make sure tracker cache will reflect change 1057 Cache::deleteTrackerCache(); 1058 } 1059 1060 /** 1061 * Returns true if the default behavior is to keep URL fragments when tracking, 1062 * false if otherwise. 1063 * 1064 * @return bool 1065 */ 1066 public function getKeepURLFragmentsGlobal() 1067 { 1068 Piwik::checkUserHasSomeViewAccess(); 1069 return (bool)Option::get(self::OPTION_KEEP_URL_FRAGMENTS_GLOBAL); 1070 } 1071 1072 /** 1073 * Sets whether the default behavior should be to keep URL fragments when 1074 * tracking or not. 1075 * 1076 * @param $enabled bool If true, the default behavior will be to keep URL 1077 * fragments when tracking. If false, the default 1078 * behavior will be to remove them. 1079 */ 1080 public function setKeepURLFragmentsGlobal($enabled) 1081 { 1082 Piwik::checkUserHasSuperUserAccess(); 1083 1084 // update option 1085 Option::set(self::OPTION_KEEP_URL_FRAGMENTS_GLOBAL, $enabled); 1086 1087 // make sure tracker cache will reflect change 1088 Cache::deleteTrackerCache(); 1089 } 1090 1091 /** 1092 * Sets list of URL query parameters to be excluded on all websites. 1093 * Will also apply to websites created in the future. 1094 * 1095 * @param string $excludedQueryParameters Comma separated list of URL query parameters to exclude from URLs 1096 * @return bool 1097 */ 1098 public function setGlobalExcludedQueryParameters($excludedQueryParameters) 1099 { 1100 Piwik::checkUserHasSuperUserAccess(); 1101 $excludedQueryParameters = $this->checkAndReturnCommaSeparatedStringList($excludedQueryParameters); 1102 Option::set(self::OPTION_EXCLUDED_QUERY_PARAMETERS_GLOBAL, $excludedQueryParameters); 1103 Cache::deleteTrackerCache(); 1104 return true; 1105 } 1106 1107 /** 1108 * Returns the list of IPs that are excluded from all websites 1109 * 1110 * @return string Comma separated list of IPs 1111 */ 1112 public function getExcludedIpsGlobal() 1113 { 1114 Piwik::checkUserHasSomeAdminAccess(); 1115 return Option::get(self::OPTION_EXCLUDED_IPS_GLOBAL); 1116 } 1117 1118 /** 1119 * Returns the default currency that will be set when creating a website through the API. 1120 * 1121 * @return string Currency ID eg. 'USD' 1122 */ 1123 public function getDefaultCurrency() 1124 { 1125 Piwik::checkUserHasSomeAdminAccess(); 1126 $defaultCurrency = Option::get(self::OPTION_DEFAULT_CURRENCY); 1127 if ($defaultCurrency) { 1128 return $defaultCurrency; 1129 } 1130 return 'USD'; 1131 } 1132 1133 /** 1134 * Sets the default currency that will be used when creating websites 1135 * 1136 * @param string $defaultCurrency Currency code, eg. 'USD' 1137 * @return bool 1138 */ 1139 public function setDefaultCurrency($defaultCurrency) 1140 { 1141 Piwik::checkUserHasSuperUserAccess(); 1142 $this->checkValidCurrency($defaultCurrency); 1143 Option::set(self::OPTION_DEFAULT_CURRENCY, $defaultCurrency); 1144 return true; 1145 } 1146 1147 /** 1148 * Returns the default timezone that will be set when creating a website through the API. 1149 * Via the UI, if the default timezone is not UTC, it will be pre-selected in the drop down 1150 * 1151 * @return string Timezone eg. UTC+7 or Europe/Paris 1152 */ 1153 public function getDefaultTimezone() 1154 { 1155 $defaultTimezone = Option::get(self::OPTION_DEFAULT_TIMEZONE); 1156 if ($defaultTimezone) { 1157 return $defaultTimezone; 1158 } 1159 return 'UTC'; 1160 } 1161 1162 /** 1163 * Sets the default timezone that will be used when creating websites 1164 * 1165 * @param string $defaultTimezone Timezone string eg. Europe/Paris or UTC+8 1166 * @return bool 1167 */ 1168 public function setDefaultTimezone($defaultTimezone) 1169 { 1170 Piwik::checkUserHasSuperUserAccess(); 1171 $this->checkValidTimezone($defaultTimezone); 1172 Option::set(self::OPTION_DEFAULT_TIMEZONE, $defaultTimezone); 1173 return true; 1174 } 1175 1176 /** 1177 * Update an existing website. 1178 * If only one URL is specified then only the main url will be updated. 1179 * If several URLs are specified, both the main URL and the alias URLs will be updated. 1180 * 1181 * @param int $idSite website ID defining the website to edit 1182 * @param string $siteName website name 1183 * @param string|array $urls the website URLs 1184 * When calling API via HTTP specify multiple URLs via `&urls[]=http...&urls[]=http...`. 1185 * @param int $ecommerce Whether Ecommerce is enabled, 0 or 1 1186 * @param null|int $siteSearch Whether site search is enabled, 0 or 1 1187 * @param string $searchKeywordParameters Comma separated list of search keyword parameter names 1188 * @param string $searchCategoryParameters Comma separated list of search category parameter names 1189 * @param string $excludedIps Comma separated list of IPs to exclude from being tracked (allows wildcards) 1190 * @param null|string $excludedQueryParameters 1191 * @param string $timezone Timezone 1192 * @param string $currency Currency code 1193 * @param string $group Group name where this website belongs 1194 * @param string $startDate Date at which the statistics for this website will start. Defaults to today's date in YYYY-MM-DD format 1195 * @param null|string $excludedUserAgents 1196 * @param int|null $keepURLFragments If 1, URL fragments will be kept when tracking. If 2, they 1197 * will be removed. If 0, the default global behavior will be used. 1198 * @param string $type The Website type, default value is "website" 1199 * @param array|null $settingValues JSON serialized settings eg {settingName: settingValue, ...} 1200 * @param bool|null $excludeUnknownUrls Track only URL matching one of website URLs 1201 * @throws Exception 1202 * @see getKeepURLFragmentsGlobal. If null, the existing value will 1203 * not be modified. 1204 * 1205 * @return bool true on success 1206 */ 1207 public function updateSite($idSite, 1208 $siteName = null, 1209 $urls = null, 1210 $ecommerce = null, 1211 $siteSearch = null, 1212 $searchKeywordParameters = null, 1213 $searchCategoryParameters = null, 1214 $excludedIps = null, 1215 $excludedQueryParameters = null, 1216 $timezone = null, 1217 $currency = null, 1218 $group = null, 1219 $startDate = null, 1220 $excludedUserAgents = null, 1221 $keepURLFragments = null, 1222 $type = null, 1223 $settingValues = null, 1224 $excludeUnknownUrls = null) 1225 { 1226 Piwik::checkUserHasAdminAccess($idSite); 1227 SitesManager::dieIfSitesAdminIsDisabled(); 1228 1229 $idSites = $this->getSitesId(); 1230 1231 if (!in_array($idSite, $idSites)) { 1232 throw new Exception("website id = $idSite not found"); 1233 } 1234 1235 // Build the SQL UPDATE based on specified updates to perform 1236 $bind = array(); 1237 1238 if (!is_null($siteName)) { 1239 $this->checkName($siteName); 1240 $bind['name'] = $siteName; 1241 } 1242 1243 if (!isset($settingValues)) { 1244 $settingValues = array(); 1245 } 1246 1247 if (empty($coreProperties)) { 1248 $coreProperties = array(); 1249 } 1250 1251 $coreProperties = $this->setSettingValue('urls', $urls, $coreProperties, $settingValues); 1252 $coreProperties = $this->setSettingValue('group', $group, $coreProperties, $settingValues); 1253 $coreProperties = $this->setSettingValue('ecommerce', $ecommerce, $coreProperties, $settingValues); 1254 $coreProperties = $this->setSettingValue('sitesearch', $siteSearch, $coreProperties, $settingValues); 1255 $coreProperties = $this->setSettingValue('sitesearch_keyword_parameters', explode(',', $searchKeywordParameters ?? ''), $coreProperties, $settingValues); 1256 $coreProperties = $this->setSettingValue('sitesearch_category_parameters', explode(',', $searchCategoryParameters ?? ''), $coreProperties, $settingValues); 1257 $coreProperties = $this->setSettingValue('keep_url_fragment', $keepURLFragments, $coreProperties, $settingValues); 1258 $coreProperties = $this->setSettingValue('exclude_unknown_urls', $excludeUnknownUrls, $coreProperties, $settingValues); 1259 $coreProperties = $this->setSettingValue('excluded_ips', explode(',', $excludedIps ?? ''), $coreProperties, $settingValues); 1260 $coreProperties = $this->setSettingValue('excluded_parameters', explode(',', $excludedQueryParameters ?? ''), $coreProperties, $settingValues); 1261 $coreProperties = $this->setSettingValue('excluded_user_agents', explode(',', $excludedUserAgents ?? ''), $coreProperties, $settingValues); 1262 1263 if (isset($currency)) { 1264 $currency = trim($currency); 1265 $this->checkValidCurrency($currency); 1266 $bind['currency'] = $currency; 1267 } 1268 if (isset($timezone)) { 1269 $timezone = trim($timezone); 1270 $this->checkValidTimezone($timezone); 1271 $bind['timezone'] = $timezone; 1272 } 1273 if (isset($group) 1274 && Piwik::hasUserSuperUserAccess() 1275 ) { 1276 $bind['group'] = trim($group); 1277 } 1278 if (isset($startDate)) { 1279 $bind['ts_created'] = Date::factory($startDate)->getDatetime(); 1280 } 1281 1282 if (isset($type)) { 1283 $bind['type'] = $this->checkAndReturnType($type); 1284 } 1285 1286 if (!empty($coreProperties)) { 1287 $this->setAndValidateMeasurableSettings($idSite, $idType = 'website', $coreProperties); 1288 } 1289 1290 if (!empty($settingValues)) { 1291 $this->setAndValidateMeasurableSettings($idSite, $idType = null, $settingValues); 1292 } 1293 1294 if (!empty($bind)) { 1295 $this->getModel()->updateSite($bind, $idSite); 1296 } 1297 1298 if (!empty($coreProperties)) { 1299 $this->saveMeasurableSettings($idSite, $idType = 'website', $coreProperties); 1300 } 1301 1302 if (!empty($settingValues)) { 1303 $this->saveMeasurableSettings($idSite, $idType = null, $settingValues); 1304 } 1305 1306 $this->postUpdateWebsite($idSite); 1307 } 1308 1309 /** 1310 * Updates the field ts_created for the specified websites. 1311 * 1312 * @param $idSites int Id Site to update ts_created 1313 * @param $minDate Date to set as creation date. To play it safe it will subtract one more day. 1314 * 1315 * @ignore 1316 */ 1317 public function updateSiteCreatedTime($idSites, Date $minDate) 1318 { 1319 $idSites = Site::getIdSitesFromIdSitesString($idSites); 1320 Piwik::checkUserHasAdminAccess($idSites); 1321 1322 $minDateSql = $minDate->subDay(1)->getDatetime(); 1323 1324 $this->getModel()->updateSiteCreatedTime($idSites, $minDateSql); 1325 } 1326 1327 private function checkAndReturnCommaSeparatedStringList($parameters) 1328 { 1329 $parameters = trim($parameters); 1330 if (empty($parameters)) { 1331 return ''; 1332 } 1333 1334 $parameters = explode(',', $parameters); 1335 $parameters = array_map('trim', $parameters); 1336 $parameters = array_filter($parameters, 'strlen'); 1337 $parameters = array_unique($parameters); 1338 return implode(',', $parameters); 1339 } 1340 1341 /** 1342 * Returns the list of supported currencies 1343 * @see getCurrencySymbols() 1344 * @return array ( currencyId => currencyName) 1345 */ 1346 public function getCurrencyList() 1347 { 1348 /** @var CurrencyDataProvider $dataProvider */ 1349 $dataProvider = StaticContainer::get('Piwik\Intl\Data\Provider\CurrencyDataProvider'); 1350 $currency = $dataProvider->getCurrencyList(); 1351 1352 $return = array(); 1353 foreach (array_keys($currency) as $currencyCode) { 1354 $return[$currencyCode] = $this->translator->translate('Intl_Currency_' . $currencyCode) . 1355 ' (' . $this->translator->translate('Intl_CurrencySymbol_' . $currencyCode) . ')'; 1356 } 1357 1358 asort($return); 1359 1360 return $return; 1361 } 1362 1363 /** 1364 * Returns the list of currency symbols 1365 * @see getCurrencyList() 1366 * @return array( currencyId => currencySymbol ) 1367 */ 1368 public function getCurrencySymbols() 1369 { 1370 /** @var CurrencyDataProvider $dataProvider */ 1371 $dataProvider = StaticContainer::get('Piwik\Intl\Data\Provider\CurrencyDataProvider'); 1372 $currencies = $dataProvider->getCurrencyList(); 1373 1374 return array_map(function ($a) { 1375 return $a[0]; 1376 }, $currencies); 1377 } 1378 1379 /** 1380 * Return true if Timezone support is enabled on server 1381 * 1382 * @return bool 1383 */ 1384 public function isTimezoneSupportEnabled() 1385 { 1386 Piwik::checkUserHasSomeViewAccess(); 1387 return SettingsServer::isTimezoneSupportEnabled(); 1388 } 1389 1390 /** 1391 * Returns the list of timezones supported. 1392 * Used for addSite and updateSite 1393 * 1394 * @return array of timezone strings 1395 */ 1396 public function getTimezonesList() 1397 { 1398 if (!SettingsServer::isTimezoneSupportEnabled()) { 1399 return array('UTC' => $this->getTimezonesListUTCOffsets()); 1400 } 1401 1402 $countries = StaticContainer::get('Piwik\Intl\Data\Provider\RegionDataProvider')->getCountryList(); 1403 1404 $return = array(); 1405 $continents = array(); 1406 foreach ($countries as $countryCode => $continentCode) { 1407 $countryCode = strtoupper($countryCode); 1408 $timezones = DateTimeZone::listIdentifiers(DateTimeZone::PER_COUNTRY, $countryCode); 1409 foreach ($timezones as $timezone) { 1410 if (!isset($continents[$continentCode])) { 1411 $continents[$continentCode] = $this->translator->translate('Intl_Continent_' . $continentCode); 1412 } 1413 $continent = $continents[$continentCode]; 1414 1415 $return[$continent][$timezone] = $this->getTimezoneName($timezone, $countryCode, count($timezones) > 1); 1416 } 1417 } 1418 1419 // Sort by continent name and then by country name. 1420 ksort($return); 1421 foreach ($return as $continent => $countries) { 1422 asort($return[$continent]); 1423 } 1424 1425 $return['UTC'] = $this->getTimezonesListUTCOffsets(); 1426 return $return; 1427 } 1428 1429 /** 1430 * Returns a user-friendly label for a timezone. 1431 * This is usually the country name of the timezone. For countries spanning multiple timezones, 1432 * a city/location name is added to avoid ambiguity. 1433 * 1434 * @param string $timezone a timezone, e.g. "Asia/Tokyo" or "America/Los_Angeles" 1435 * @param string $countryCode an upper-case country code (if not supplied, it will be looked up) 1436 * @param bool $multipleTimezonesInCountry whether there are multiple timezones in the country (if not supplied, it will be looked up) 1437 * @return string a timezone label, e.g. "Japan" or "United States - Los Angeles" 1438 */ 1439 public function getTimezoneName($timezone, $countryCode = null, $multipleTimezonesInCountry = null) 1440 { 1441 if (substr($timezone, 0, 3) === 'UTC') { 1442 return $this->translator->translate('SitesManager_Format_Utc', str_replace(array('.25', '.5', '.75'), array(':15', ':30', ':45'), substr($timezone, 3))); 1443 } 1444 1445 if (!isset($countryCode)) { 1446 try { 1447 $zone = new DateTimeZone($timezone); 1448 $location = $zone->getLocation(); 1449 if (isset($location['country_code']) && $location['country_code'] !== '??') { 1450 $countryCode = $location['country_code']; 1451 } 1452 } catch (Exception $e) { 1453 } 1454 } 1455 1456 if (!$countryCode) { 1457 $timezoneExploded = explode('/', $timezone); 1458 return str_replace('_', ' ', end($timezoneExploded)); 1459 } 1460 1461 if (!isset($multipleTimezonesInCountry)) { 1462 $timezonesInCountry = DateTimeZone::listIdentifiers(DateTimeZone::PER_COUNTRY, $countryCode); 1463 $multipleTimezonesInCountry = (count($timezonesInCountry) > 1); 1464 } 1465 1466 $return = $this->translator->translate('Intl_Country_' . $countryCode); 1467 1468 if ($multipleTimezonesInCountry) { 1469 $translationId = 'Intl_Timezone_' . str_replace(array('_', '/'), array('', '_'), $timezone); 1470 $city = $this->translator->translate($translationId); 1471 1472 // Fall back to English identifier, if translation is missing due to differences in tzdata in different PHP versions. 1473 if ($city === $translationId) { 1474 $timezoneExploded = explode('/', $timezone); 1475 $city = str_replace('_', ' ', end($timezoneExploded)); 1476 } 1477 1478 $return .= ' - ' . $city; 1479 } 1480 1481 return $return; 1482 } 1483 1484 private function getTimezonesListUTCOffsets() 1485 { 1486 // manually add the UTC offsets 1487 $GmtOffsets = array(-12, -11.5, -11, -10.5, -10, -9.5, -9, -8.5, -8, -7.5, -7, -6.5, -6, -5.5, -5, -4.5, -4, -3.5, -3, -2.5, -2, -1.5, -1, -0.5, 1488 0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5, 5.75, 6, 6.5, 7, 7.5, 8, 8.5, 8.75, 9, 9.5, 10, 10.5, 11, 11.5, 12, 12.75, 13, 13.75, 14); 1489 1490 $return = array(); 1491 foreach ($GmtOffsets as $offset) { 1492 $offset = Common::forceDotAsSeparatorForDecimalPoint($offset); 1493 1494 if ($offset > 0) { 1495 $offset = '+' . $offset; 1496 } elseif ($offset == 0) { 1497 $offset = ''; 1498 } 1499 $timezone = 'UTC' . $offset; 1500 $return[$timezone] = $this->getTimezoneName($timezone); 1501 } 1502 return $return; 1503 } 1504 1505 /** 1506 * Returns the list of unique timezones from all configured sites. 1507 * 1508 * @return array ( string ) 1509 */ 1510 public function getUniqueSiteTimezones() 1511 { 1512 Piwik::checkUserHasSuperUserAccess(); 1513 1514 return $this->getModel()->getUniqueSiteTimezones(); 1515 } 1516 1517 /** 1518 * Remove the final slash in the URLs if found 1519 * 1520 * @param string $url 1521 * @return string the URL without the trailing slash 1522 */ 1523 private function removeTrailingSlash($url) 1524 { 1525 // if there is a final slash, we take the URL without this slash (expected URL format) 1526 if (strlen($url) > 5 1527 && $url[strlen($url) - 1] == '/' 1528 ) { 1529 $url = substr($url, 0, strlen($url) - 1); 1530 } 1531 1532 return $url; 1533 } 1534 1535 /** 1536 * Tests if the URL is a valid URL 1537 * 1538 * @param string $url 1539 * @return bool 1540 */ 1541 private function isValidUrl($url) 1542 { 1543 return UrlHelper::isLookLikeUrl($url); 1544 } 1545 1546 /** 1547 * Tests if the IP is a valid IP, allowing wildcards, except in the first octet. 1548 * Wildcards can only be used from right to left, ie. 1.1.*.* is allowed, but 1.1.*.1 is not. 1549 * 1550 * @param string $ip IP address 1551 * @return bool 1552 */ 1553 private function isValidIp($ip) 1554 { 1555 return IPUtils::getIPRangeBounds($ip) !== null; 1556 } 1557 1558 /** 1559 * Check that the website name has a correct format. 1560 * 1561 * @param $siteName 1562 * @throws Exception 1563 */ 1564 private function checkName($siteName) 1565 { 1566 if (empty($siteName)) { 1567 throw new Exception($this->translator->translate("SitesManager_ExceptionEmptyName")); 1568 } 1569 } 1570 1571 public function renameGroup($oldGroupName, $newGroupName) 1572 { 1573 Piwik::checkUserHasSuperUserAccess(); 1574 1575 if ($oldGroupName == $newGroupName) { 1576 return true; 1577 } 1578 1579 $sitesHavingOldGroup = $this->getSitesFromGroup($oldGroupName); 1580 1581 foreach ($sitesHavingOldGroup as $site) { 1582 $this->updateSite($site['idsite'], 1583 $siteName = null, 1584 $urls = null, 1585 $ecommerce = null, 1586 $siteSearch = null, 1587 $searchKeywordParameters = null, 1588 $searchCategoryParameters = null, 1589 $excludedIps = null, 1590 $excludedQueryParameters = null, 1591 $timezone = null, 1592 $currency = null, 1593 $newGroupName); 1594 } 1595 1596 return true; 1597 } 1598 1599 /** 1600 * Find websites matching the given pattern. 1601 * 1602 * Any website will be returned that matches the pattern in the name, URL or group. 1603 * To limit the number of returned sites you can either specify `filter_limit` as usual or `limit` which is 1604 * faster. 1605 * 1606 * @param string $pattern 1607 * @param int|false $limit 1608 * @return array 1609 */ 1610 public function getPatternMatchSites($pattern, $limit = false) 1611 { 1612 $ids = $this->getSitesIdWithAtLeastViewAccess(); 1613 if (empty($ids)) { 1614 return array(); 1615 } 1616 1617 $sites = $this->getModel()->getPatternMatchSites($ids, $pattern, $limit); 1618 1619 foreach ($sites as &$site) { 1620 $this->enrichSite($site); 1621 } 1622 1623 $sites = Site::setSitesFromArray($sites); 1624 1625 return $sites; 1626 } 1627 1628 /** 1629 * Returns the number of websites to display per page. 1630 * 1631 * For example this is used in the All Websites Dashboard, in the Website Selector etc. If multiple websites are 1632 * shown somewhere, one should request this method to detect how many websites should be shown per page when 1633 * using paging. To use paging is always recommended since some installations have thousands of websites. 1634 * 1635 * @return int 1636 */ 1637 public function getNumWebsitesToDisplayPerPage() 1638 { 1639 Piwik::checkUserHasSomeViewAccess(); 1640 1641 return SettingsPiwik::getWebsitesCountToDisplay(); 1642 } 1643 1644} 1645