Teknik is a suite of services with attractive and functional interfaces. https://www.teknik.io/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Piwik.php 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864
  1. <?php
  2. /**
  3. * Piwik - free/libre analytics platform
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. *
  8. */
  9. namespace Piwik;
  10. use Exception;
  11. use Piwik\Common;
  12. use Piwik\Db\Adapter;
  13. use Piwik\Db\Schema;
  14. use Piwik\Db;
  15. use Piwik\Plugin;
  16. use Piwik\Plugins\SitesManager\API as APISitesManager;
  17. use Piwik\Plugins\UsersManager\API as APIUsersManager;
  18. use Piwik\Session;
  19. use Piwik\Tracker;
  20. use Piwik\View;
  21. /**
  22. * @see core/Translate.php
  23. */
  24. require_once PIWIK_INCLUDE_PATH . '/core/Translate.php';
  25. /**
  26. * Main piwik helper class.
  27. *
  28. * Contains helper methods for a variety of common tasks. Plugin developers are
  29. * encouraged to reuse these methods as much as possible.
  30. */
  31. class Piwik
  32. {
  33. /**
  34. * Piwik periods
  35. * @var array
  36. */
  37. public static $idPeriods = array(
  38. 'day' => 1,
  39. 'week' => 2,
  40. 'month' => 3,
  41. 'year' => 4,
  42. 'range' => 5,
  43. );
  44. /**
  45. * The idGoal query parameter value for the special 'abandoned carts' goal.
  46. *
  47. * @api
  48. */
  49. const LABEL_ID_GOAL_IS_ECOMMERCE_CART = 'ecommerceAbandonedCart';
  50. /**
  51. * The idGoal query parameter value for the special 'ecommerce' goal.
  52. *
  53. * @api
  54. */
  55. const LABEL_ID_GOAL_IS_ECOMMERCE_ORDER = 'ecommerceOrder';
  56. /**
  57. * Trigger E_USER_ERROR with optional message
  58. *
  59. * @param string $message
  60. */
  61. public static function error($message = '')
  62. {
  63. trigger_error($message, E_USER_ERROR);
  64. }
  65. /**
  66. * Display the message in a nice red font with a nice icon
  67. * ... and dies
  68. *
  69. * @param string $message
  70. */
  71. public static function exitWithErrorMessage($message)
  72. {
  73. Common::sendHeader('Content-Type: text/html; charset=utf-8');
  74. $output = "<style>a{color:red;}</style>\n" .
  75. "<div style='color:red;font-family:Georgia;font-size:120%'>" .
  76. "<p><img src='plugins/Morpheus/images/error_medium.png' style='vertical-align:middle; float:left;padding:20 20 20 20' />" .
  77. $message .
  78. "</p></div>";
  79. print($output);
  80. exit;
  81. }
  82. /**
  83. * Computes the division of i1 by i2. If either i1 or i2 are not number, or if i2 has a value of zero
  84. * we return 0 to avoid the division by zero.
  85. *
  86. * @param number $i1
  87. * @param number $i2
  88. * @return number The result of the division or zero
  89. */
  90. public static function secureDiv($i1, $i2)
  91. {
  92. if (is_numeric($i1) && is_numeric($i2) && floatval($i2) != 0) {
  93. return $i1 / $i2;
  94. }
  95. return 0;
  96. }
  97. /**
  98. * Safely compute a percentage. Return 0 to avoid division by zero.
  99. *
  100. * @param number $dividend
  101. * @param number $divisor
  102. * @param int $precision
  103. * @return number
  104. */
  105. public static function getPercentageSafe($dividend, $divisor, $precision = 0)
  106. {
  107. if ($divisor == 0) {
  108. return 0;
  109. }
  110. return round(100 * $dividend / $divisor, $precision);
  111. }
  112. /**
  113. * Returns the Javascript code to be inserted on every page to track
  114. *
  115. * @param int $idSite
  116. * @param string $piwikUrl http://path/to/piwik/directory/
  117. * @return string
  118. */
  119. public static function getJavascriptCode($idSite, $piwikUrl, $mergeSubdomains = false, $groupPageTitlesByDomain = false,
  120. $mergeAliasUrls = false, $visitorCustomVariables = false, $pageCustomVariables = false,
  121. $customCampaignNameQueryParam = false, $customCampaignKeywordParam = false,
  122. $doNotTrack = false, $disableCookies = false)
  123. {
  124. // changes made to this code should be mirrored in plugins/CoreAdminHome/javascripts/jsTrackingGenerator.js var generateJsCode
  125. $jsCode = file_get_contents(PIWIK_INCLUDE_PATH . "/plugins/Morpheus/templates/javascriptCode.tpl");
  126. $jsCode = htmlentities($jsCode);
  127. if(substr($piwikUrl, 0, 4) !== 'http') {
  128. $piwikUrl = 'http://' . $piwikUrl;
  129. }
  130. preg_match('~^(http|https)://(.*)$~D', $piwikUrl, $matches);
  131. $piwikUrl = rtrim(@$matches[2], "/");
  132. // Build optional parameters to be added to text
  133. $options = '';
  134. if ($groupPageTitlesByDomain) {
  135. $options .= ' _paq.push(["setDocumentTitle", document.domain + "/" + document.title]);' . PHP_EOL;
  136. }
  137. if ($mergeSubdomains || $mergeAliasUrls) {
  138. $options .= self::getJavascriptTagOptions($idSite, $mergeSubdomains, $mergeAliasUrls);
  139. }
  140. $maxCustomVars = Plugins\CustomVariables\CustomVariables::getMaxCustomVariables();
  141. if ($visitorCustomVariables) {
  142. $options .= ' // you can set up to ' . $maxCustomVars . ' custom variables for each visitor' . PHP_EOL;
  143. $index = 1;
  144. foreach ($visitorCustomVariables as $visitorCustomVariable) {
  145. if (empty($visitorCustomVariable)) {
  146. continue;
  147. }
  148. $options .= ' _paq.push(["setCustomVariable", '.$index++.', "'.$visitorCustomVariable[0].'", "'.$visitorCustomVariable[1].'", "visit"]);' . PHP_EOL;
  149. }
  150. }
  151. if ($pageCustomVariables) {
  152. $options .= ' // you can set up to ' . $maxCustomVars . ' custom variables for each action (page view, download, click, site search)' . PHP_EOL;
  153. $index = 1;
  154. foreach ($pageCustomVariables as $pageCustomVariable) {
  155. if (empty($pageCustomVariable)) {
  156. continue;
  157. }
  158. $options .= ' _paq.push(["setCustomVariable", '.$index++.', "'.$pageCustomVariable[0].'", "'.$pageCustomVariable[1].'", "page"]);' . PHP_EOL;
  159. }
  160. }
  161. if ($customCampaignNameQueryParam) {
  162. $options .= ' _paq.push(["setCampaignNameKey", "'.$customCampaignNameQueryParam.'"]);' . PHP_EOL;
  163. }
  164. if ($customCampaignKeywordParam) {
  165. $options .= ' _paq.push(["setCampaignKeywordKey", "'.$customCampaignKeywordParam.'"]);' . PHP_EOL;
  166. }
  167. if ($doNotTrack) {
  168. $options .= ' _paq.push(["setDoNotTrack", true]);' . PHP_EOL;
  169. }
  170. if ($disableCookies) {
  171. $options .= ' _paq.push(["disableCookies"]);' . PHP_EOL;
  172. }
  173. $codeImpl = array(
  174. 'idSite' => $idSite,
  175. 'piwikUrl' => Common::sanitizeInputValue($piwikUrl),
  176. 'options' => $options
  177. );
  178. $parameters = compact('mergeSubdomains', 'groupPageTitlesByDomain', 'mergeAliasUrls', 'visitorCustomVariables',
  179. 'pageCustomVariables', 'customCampaignNameQueryParam', 'customCampaignKeywordParam',
  180. 'doNotTrack');
  181. /**
  182. * Triggered when generating JavaScript tracking code server side. Plugins can use
  183. * this event to customise the JavaScript tracking code that is displayed to the
  184. * user.
  185. *
  186. * @param array &$codeImpl An array containing snippets of code that the event handler
  187. * can modify. Will contain the following elements:
  188. *
  189. * - **idSite**: The ID of the site being tracked.
  190. * - **piwikUrl**: The tracker URL to use.
  191. * - **options**: A string of JavaScript code that customises
  192. * the JavaScript tracker.
  193. *
  194. * The **httpsPiwikUrl** element can be set if the HTTPS
  195. * domain is different from the normal domain.
  196. * @param array $parameters The parameters supplied to the `Piwik::getJavascriptCode()`.
  197. */
  198. self::postEvent('Piwik.getJavascriptCode', array(&$codeImpl, $parameters));
  199. $setTrackerUrl = 'var u="//{$piwikUrl}/";';
  200. if (!empty($codeImpl['httpsPiwikUrl'])) {
  201. $setTrackerUrl = 'var u=((document.location.protocol === "https:") ? "https://{$httpsPiwikUrl}/" : "http://{$piwikUrl}/");';
  202. $codeImpl['httpsPiwikUrl'] = rtrim($codeImpl['httpsPiwikUrl'], "/");
  203. }
  204. $codeImpl = array('setTrackerUrl' => htmlentities($setTrackerUrl)) + $codeImpl;
  205. foreach ($codeImpl as $keyToReplace => $replaceWith) {
  206. $jsCode = str_replace('{$' . $keyToReplace . '}', $replaceWith, $jsCode);
  207. }
  208. return $jsCode;
  209. }
  210. /**
  211. * Generate a title for image tags
  212. *
  213. * @return string
  214. */
  215. public static function getRandomTitle()
  216. {
  217. static $titles = array(
  218. 'Web analytics',
  219. 'Open analytics platform',
  220. 'Real Time Web Analytics',
  221. 'Analytics',
  222. 'Real Time Analytics',
  223. 'Analytics in Real time',
  224. 'Free/Libre Web Analytics',
  225. 'Free Website Analytics',
  226. 'Free Web Analytics',
  227. 'Analytics Platform',
  228. 'Data Platform',
  229. );
  230. $id = abs(intval(md5(Url::getCurrentHost())));
  231. $title = $titles[$id % count($titles)];
  232. return $title;
  233. }
  234. /*
  235. * Access
  236. */
  237. /**
  238. * Returns the current user's email address.
  239. *
  240. * @return string
  241. * @api
  242. */
  243. public static function getCurrentUserEmail()
  244. {
  245. $user = APIUsersManager::getInstance()->getUser(Piwik::getCurrentUserLogin());
  246. return $user['email'];
  247. }
  248. /**
  249. * Get a list of all email addresses having Super User access.
  250. *
  251. * @return array
  252. */
  253. public static function getAllSuperUserAccessEmailAddresses()
  254. {
  255. $emails = array();
  256. try {
  257. $superUsers = APIUsersManager::getInstance()->getUsersHavingSuperUserAccess();
  258. } catch (\Exception $e) {
  259. return $emails;
  260. }
  261. foreach ($superUsers as $superUser) {
  262. $emails[$superUser['login']] = $superUser['email'];
  263. }
  264. return $emails;
  265. }
  266. /**
  267. * Returns the current user's username.
  268. *
  269. * @return string
  270. * @api
  271. */
  272. public static function getCurrentUserLogin()
  273. {
  274. return Access::getInstance()->getLogin();
  275. }
  276. /**
  277. * Returns the current user's token auth.
  278. *
  279. * @return string
  280. * @api
  281. */
  282. public static function getCurrentUserTokenAuth()
  283. {
  284. return Access::getInstance()->getTokenAuth();
  285. }
  286. /**
  287. * Returns `true` if the current user is either the Super User or the user specified by
  288. * `$theUser`.
  289. *
  290. * @param string $theUser A username.
  291. * @return bool
  292. * @api
  293. */
  294. public static function hasUserSuperUserAccessOrIsTheUser($theUser)
  295. {
  296. try {
  297. self::checkUserHasSuperUserAccessOrIsTheUser($theUser);
  298. return true;
  299. } catch (Exception $e) {
  300. return false;
  301. }
  302. }
  303. /**
  304. * Check that the current user is either the specified user or the superuser.
  305. *
  306. * @param string $theUser A username.
  307. * @throws NoAccessException If the user is neither the Super User nor the user `$theUser`.
  308. * @api
  309. */
  310. public static function checkUserHasSuperUserAccessOrIsTheUser($theUser)
  311. {
  312. try {
  313. if (Piwik::getCurrentUserLogin() !== $theUser) {
  314. // or to the Super User
  315. Piwik::checkUserHasSuperUserAccess();
  316. }
  317. } catch (NoAccessException $e) {
  318. throw new NoAccessException(Piwik::translate('General_ExceptionCheckUserHasSuperUserAccessOrIsTheUser', array($theUser)));
  319. }
  320. }
  321. /**
  322. * Check whether the given user has superuser access.
  323. *
  324. * @param string $theUser A username.
  325. * @return bool
  326. * @api
  327. */
  328. public static function hasTheUserSuperUserAccess($theUser)
  329. {
  330. if (empty($theUser)) {
  331. return false;
  332. }
  333. if (Piwik::getCurrentUserLogin() === $theUser && Piwik::hasUserSuperUserAccess()) {
  334. return true;
  335. }
  336. try {
  337. $superUsers = APIUsersManager::getInstance()->getUsersHavingSuperUserAccess();
  338. } catch (\Exception $e) {
  339. return false;
  340. }
  341. foreach ($superUsers as $superUser) {
  342. if ($theUser === $superUser['login']) {
  343. return true;
  344. }
  345. }
  346. return false;
  347. }
  348. /**
  349. * Returns true if the current user has Super User access.
  350. *
  351. * @return bool
  352. * @api
  353. */
  354. public static function hasUserSuperUserAccess()
  355. {
  356. try {
  357. self::checkUserHasSuperUserAccess();
  358. return true;
  359. } catch (Exception $e) {
  360. return false;
  361. }
  362. }
  363. /**
  364. * Returns true if the current user is the special **anonymous** user or not.
  365. *
  366. * @return bool
  367. * @api
  368. */
  369. public static function isUserIsAnonymous()
  370. {
  371. return Piwik::getCurrentUserLogin() == 'anonymous';
  372. }
  373. /**
  374. * Checks that the user is not the anonymous user.
  375. *
  376. * @throws NoAccessException if the current user is the anonymous user.
  377. * @api
  378. */
  379. public static function checkUserIsNotAnonymous()
  380. {
  381. if (Access::getInstance()->hasSuperUserAccess()) {
  382. return;
  383. }
  384. if (self::isUserIsAnonymous()) {
  385. throw new NoAccessException(Piwik::translate('General_YouMustBeLoggedIn'));
  386. }
  387. }
  388. /**
  389. * Helper method user to set the current as superuser.
  390. * This should be used with great care as this gives the user all permissions.
  391. *
  392. * @param bool $bool true to set current user as Super User
  393. */
  394. public static function setUserHasSuperUserAccess($bool = true)
  395. {
  396. Access::getInstance()->setSuperUserAccess($bool);
  397. }
  398. /**
  399. * Check that the current user has superuser access.
  400. *
  401. * @throws Exception if the current user is not the superuser.
  402. * @api
  403. */
  404. public static function checkUserHasSuperUserAccess()
  405. {
  406. Access::getInstance()->checkUserHasSuperUserAccess();
  407. }
  408. /**
  409. * Returns `true` if the user has admin access to the requested sites, `false` if otherwise.
  410. *
  411. * @param int|array $idSites The list of site IDs to check access for.
  412. * @return bool
  413. * @api
  414. */
  415. public static function isUserHasAdminAccess($idSites)
  416. {
  417. try {
  418. self::checkUserHasAdminAccess($idSites);
  419. return true;
  420. } catch (Exception $e) {
  421. return false;
  422. }
  423. }
  424. /**
  425. * Checks that the current user has admin access to the requested list of sites.
  426. *
  427. * @param int|array $idSites One or more site IDs to check access for.
  428. * @throws Exception If user doesn't have admin access.
  429. * @api
  430. */
  431. public static function checkUserHasAdminAccess($idSites)
  432. {
  433. Access::getInstance()->checkUserHasAdminAccess($idSites);
  434. }
  435. /**
  436. * Returns `true` if the current user has admin access to at least one site.
  437. *
  438. * @return bool
  439. * @api
  440. */
  441. public static function isUserHasSomeAdminAccess()
  442. {
  443. try {
  444. self::checkUserHasSomeAdminAccess();
  445. return true;
  446. } catch (Exception $e) {
  447. return false;
  448. }
  449. }
  450. /**
  451. * Checks that the current user has admin access to at least one site.
  452. *
  453. * @throws Exception if user doesn't have admin access to any site.
  454. * @api
  455. */
  456. public static function checkUserHasSomeAdminAccess()
  457. {
  458. Access::getInstance()->checkUserHasSomeAdminAccess();
  459. }
  460. /**
  461. * Returns `true` if the user has view access to the requested list of sites.
  462. *
  463. * @param int|array $idSites One or more site IDs to check access for.
  464. * @return bool
  465. * @api
  466. */
  467. public static function isUserHasViewAccess($idSites)
  468. {
  469. try {
  470. self::checkUserHasViewAccess($idSites);
  471. return true;
  472. } catch (Exception $e) {
  473. return false;
  474. }
  475. }
  476. /**
  477. * Checks that the current user has view access to the requested list of sites
  478. *
  479. * @param int|array $idSites The list of site IDs to check access for.
  480. * @throws Exception if the current user does not have view access to every site in the list.
  481. * @api
  482. */
  483. public static function checkUserHasViewAccess($idSites)
  484. {
  485. Access::getInstance()->checkUserHasViewAccess($idSites);
  486. }
  487. /**
  488. * Returns `true` if the current user has view access to at least one site.
  489. *
  490. * @return bool
  491. * @api
  492. */
  493. public static function isUserHasSomeViewAccess()
  494. {
  495. try {
  496. self::checkUserHasSomeViewAccess();
  497. return true;
  498. } catch (Exception $e) {
  499. return false;
  500. }
  501. }
  502. /**
  503. * Checks that the current user has view access to at least one site.
  504. *
  505. * @throws Exception if user doesn't have view access to any site.
  506. * @api
  507. */
  508. public static function checkUserHasSomeViewAccess()
  509. {
  510. Access::getInstance()->checkUserHasSomeViewAccess();
  511. }
  512. /*
  513. * Current module, action, plugin
  514. */
  515. /**
  516. * Returns the name of the Login plugin currently being used.
  517. * Must be used since it is not allowed to hardcode 'Login' in URLs
  518. * in case another Login plugin is being used.
  519. *
  520. * @return string
  521. */
  522. public static function getLoginPluginName()
  523. {
  524. return Registry::get('auth')->getName();
  525. }
  526. /**
  527. * Returns the plugin currently being used to display the page
  528. *
  529. * @return Plugin
  530. */
  531. public static function getCurrentPlugin()
  532. {
  533. return \Piwik\Plugin\Manager::getInstance()->getLoadedPlugin(Piwik::getModule());
  534. }
  535. /**
  536. * Returns the current module read from the URL (eg. 'API', 'UserSettings', etc.)
  537. *
  538. * @return string
  539. */
  540. public static function getModule()
  541. {
  542. return Common::getRequestVar('module', '', 'string');
  543. }
  544. /**
  545. * Returns the current action read from the URL
  546. *
  547. * @return string
  548. */
  549. public static function getAction()
  550. {
  551. return Common::getRequestVar('action', '', 'string');
  552. }
  553. /**
  554. * Helper method used in API function to introduce array elements in API parameters.
  555. * Array elements can be passed by comma separated values, or using the notation
  556. * array[]=value1&array[]=value2 in the URL.
  557. * This function will handle both cases and return the array.
  558. *
  559. * @param array|string $columns
  560. * @return array
  561. */
  562. public static function getArrayFromApiParameter($columns)
  563. {
  564. if (empty($columns)) {
  565. return array();
  566. }
  567. if (is_array($columns)) {
  568. return $columns;
  569. }
  570. $array = explode(',', $columns);
  571. $array = array_unique($array);
  572. return $array;
  573. }
  574. /**
  575. * Redirects the current request to a new module and action.
  576. *
  577. * @param string $newModule The target module, eg, `'UserCountry'`.
  578. * @param string $newAction The target controller action, eg, `'index'`.
  579. * @param array $parameters The query parameter values to modify before redirecting.
  580. * @api
  581. */
  582. public static function redirectToModule($newModule, $newAction = '', $parameters = array())
  583. {
  584. $newUrl = 'index.php' . Url::getCurrentQueryStringWithParametersModified(
  585. array('module' => $newModule, 'action' => $newAction)
  586. + $parameters
  587. );
  588. Url::redirectToUrl($newUrl);
  589. }
  590. /*
  591. * User input validation
  592. */
  593. /**
  594. * Returns `true` if supplied the email address is a valid.
  595. *
  596. * @param string $emailAddress
  597. * @return bool
  598. * @api
  599. */
  600. public static function isValidEmailString($emailAddress)
  601. {
  602. return (preg_match('/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9_.-]+\.[a-zA-Z]{2,}$/D', $emailAddress) > 0);
  603. }
  604. /**
  605. * Returns `true` if the login is valid.
  606. *
  607. * _Warning: does not check if the login already exists! You must use UsersManager_API->userExists as well._
  608. *
  609. * @param string $userLogin
  610. * @throws Exception
  611. * @return bool
  612. */
  613. public static function checkValidLoginString($userLogin)
  614. {
  615. if (!SettingsPiwik::isUserCredentialsSanityCheckEnabled()
  616. && !empty($userLogin)
  617. ) {
  618. return;
  619. }
  620. $loginMinimumLength = 3;
  621. $loginMaximumLength = 100;
  622. $l = strlen($userLogin);
  623. if (!($l >= $loginMinimumLength
  624. && $l <= $loginMaximumLength
  625. && (preg_match('/^[A-Za-z0-9_.@+-]*$/D', $userLogin) > 0))
  626. ) {
  627. throw new Exception(Piwik::translate('UsersManager_ExceptionInvalidLoginFormat', array($loginMinimumLength, $loginMaximumLength)));
  628. }
  629. }
  630. /**
  631. * Utility function that checks if an object type is in a set of types.
  632. *
  633. * @param mixed $o
  634. * @param array $types List of class names that $o is expected to be one of.
  635. * @throws Exception if $o is not an instance of the types contained in $types.
  636. */
  637. public static function checkObjectTypeIs($o, $types)
  638. {
  639. foreach ($types as $type) {
  640. if ($o instanceof $type) {
  641. return;
  642. }
  643. }
  644. $oType = is_object($o) ? get_class($o) : gettype($o);
  645. throw new Exception("Invalid variable type '$oType', expected one of following: " . implode(', ', $types));
  646. }
  647. /**
  648. * Returns true if an array is an associative array, false if otherwise.
  649. *
  650. * This method determines if an array is associative by checking that the
  651. * first element's key is 0, and that each successive element's key is
  652. * one greater than the last.
  653. *
  654. * @param array $array
  655. * @return bool
  656. */
  657. public static function isAssociativeArray($array)
  658. {
  659. reset($array);
  660. if (!is_numeric(key($array))
  661. || key($array) != 0
  662. ) // first key must be 0
  663. {
  664. return true;
  665. }
  666. // check that each key is == next key - 1 w/o actually indexing the array
  667. while (true) {
  668. $current = key($array);
  669. next($array);
  670. $next = key($array);
  671. if ($next === null) {
  672. break;
  673. } else if ($current + 1 != $next) {
  674. return true;
  675. }
  676. }
  677. return false;
  678. }
  679. public static function isMultiDimensionalArray($array)
  680. {
  681. $first = reset($array);
  682. foreach ($array as $first) {
  683. if (is_array($first)) {
  684. // Yes, this is a multi dim array
  685. return true;
  686. }
  687. }
  688. return false;
  689. }
  690. /**
  691. * Returns the class name of an object without its namespace.
  692. *
  693. * @param mixed|string $object
  694. * @return string
  695. */
  696. public static function getUnnamespacedClassName($object)
  697. {
  698. $className = is_string($object) ? $object : get_class($object);
  699. $parts = explode('\\', $className);
  700. return end($parts);
  701. }
  702. /**
  703. * Post an event to Piwik's event dispatcher which will execute the event's observers.
  704. *
  705. * @param string $eventName The event name.
  706. * @param array $params The parameter array to forward to observer callbacks.
  707. * @param bool $pending If true, plugins that are loaded after this event is fired will
  708. * have their observers for this event executed.
  709. * @param array|null $plugins The list of plugins to execute observers for. If null, all
  710. * plugin observers will be executed.
  711. * @api
  712. */
  713. public static function postEvent($eventName, $params = array(), $pending = false, $plugins = null)
  714. {
  715. EventDispatcher::getInstance()->postEvent($eventName, $params, $pending, $plugins);
  716. }
  717. /**
  718. * Register an observer to an event.
  719. *
  720. * **_Note: Observers should normally be defined in plugin objects. It is unlikely that you will
  721. * need to use this function._**
  722. *
  723. * @param string $eventName The event name.
  724. * @param callable|array $function The observer.
  725. * @api
  726. */
  727. public static function addAction($eventName, $function)
  728. {
  729. EventDispatcher::getInstance()->addObserver($eventName, $function);
  730. }
  731. /**
  732. * Posts an event if we are currently running tests. Whether we are running tests is
  733. * determined by looking for the PIWIK_TEST_MODE constant.
  734. */
  735. public static function postTestEvent($eventName, $params = array(), $pending = false, $plugins = null)
  736. {
  737. if (defined('PIWIK_TEST_MODE')) {
  738. Piwik::postEvent($eventName, $params, $pending, $plugins);
  739. }
  740. }
  741. /**
  742. * Returns an internationalized string using a translation token. If a translation
  743. * cannot be found for the toke, the token is returned.
  744. *
  745. * @param string $translationId Translation ID, eg, `'General_Date'`.
  746. * @param array|string|int $args `sprintf` arguments to be applied to the internationalized
  747. * string.
  748. * @return string The translated string or `$translationId`.
  749. * @api
  750. */
  751. public static function translate($translationId, $args = array())
  752. {
  753. if (!is_array($args)) {
  754. $args = array($args);
  755. }
  756. if (strpos($translationId, "_") !== false) {
  757. list($plugin, $key) = explode("_", $translationId, 2);
  758. if (isset($GLOBALS['Piwik_translations'][$plugin]) && isset($GLOBALS['Piwik_translations'][$plugin][$key])) {
  759. $translationId = $GLOBALS['Piwik_translations'][$plugin][$key];
  760. }
  761. }
  762. if (count($args) == 0) {
  763. return $translationId;
  764. }
  765. return vsprintf($translationId, $args);
  766. }
  767. protected static function getJavascriptTagOptions($idSite, $mergeSubdomains, $mergeAliasUrls)
  768. {
  769. try {
  770. $websiteUrls = APISitesManager::getInstance()->getSiteUrlsFromId($idSite);
  771. } catch (\Exception $e) {
  772. return '';
  773. }
  774. // We need to parse_url to isolate hosts
  775. $websiteHosts = array();
  776. foreach ($websiteUrls as $site_url) {
  777. $referrerParsed = parse_url($site_url);
  778. $websiteHosts[] = $referrerParsed['host'];
  779. }
  780. $options = '';
  781. if ($mergeSubdomains && !empty($websiteHosts)) {
  782. $options .= ' _paq.push(["setCookieDomain", "*.' . $websiteHosts[0] . '"]);' . PHP_EOL;
  783. }
  784. if ($mergeAliasUrls && !empty($websiteHosts)) {
  785. $urls = '["*.' . implode('","*.', $websiteHosts) . '"]';
  786. $options .= ' _paq.push(["setDomains", ' . $urls . ']);' . PHP_EOL;
  787. }
  788. return $options;
  789. }
  790. }