File Manager

Current Directory: /home/astoriaah/www/administrator/components/com_sh404sef/helpers
Viewing File: /home/astoriaah/www/administrator/components/com_sh404sef/helpers/analytics.php
<?php /** * sh404SEF - SEO extension for Joomla! * * @author Yannick Gaultier * @copyright (c) Yannick Gaultier - Weeblr llc - 2022 * @package sh404SEF * @license http://www.gnu.org/copyleft/gpl.html GNU/GPL * @version 4.24.1.4308 * @date 2022-05-31 */ use Joomla\CMS\Uri\Uri; use Joomla\CMS\Factory; use Joomla\CMS\Language\Language; use Joomla\CMS\Language\Text; use Joomla\String\StringHelper; // Security check to ensure this file is being included by a parent file. defined('_JEXEC') || die; class Sh404sefHelperAnalytics { // store whether doing a forced update. Reason to store it // is to keep _doCheck() method w/o any parameters // as this would otherwise prevent caching from operating // normally private static $_options = false; /** * Figure out if analytics collection is enabled * */ public static function isEnabled() { $config = Sh404sefFactory::getConfig(); return $config->analyticsEdition != 'none'; } /** * Build the full URL to the front end analytics stats * page, including the secret access key if any is available. * * @return string */ public static function getFrontendStatsUrl() { $accessKey = Sh404sefFactory::getConfig()->analyticsFrontendReportsAccessKey; $statsLink = StringHelper::rtrim(Uri::root(), '/') . '/' . self::getFrontendStatsSlug() . (empty($accessKey) ? '' : '?' . $accessKey); return $statsLink; } /** * Filter and return the frontend analytics URL slug. * Default to "stats". * * @return string */ public static function getFrontendStatsSlug() { /** * Filter the URL slug used to access frontend analytics reports. * * @api * @package sh404SEF\filter\output * @var sh404sef_frontend_analytics_slug * @since 4.20.0 * * @param string $slug * * @return string */ $analyticsSlug = ShlHook::filter( 'sh404sef_frontend_analytics_slug', 'stats' ); return StringHelper::trim($analyticsSlug, '/'); } /** * Decides whether current request should trigger the display of * the analytics reports frontend version. * * @param $uri * @param $jRouter * * @return bool * @throws Exception */ public static function shouldDisplayFrontend(&$uri, $jRouter) { $shouldDisplayFrontend = false; $app = Factory::getApplication(); if ( !$app->isClient('site') || !self::isEnabled() || // @TODO: use wbLib config access empty(Sh404sefFactory::getConfig()->analyticsReportsEnabled) || empty(Sh404sefFactory::getConfig()->analyticsEnableFrontendAccess) ) { return $shouldDisplayFrontend; } $analyticsSlug = self::getFrontendStatsSlug(); if (empty($analyticsSlug)) { return $shouldDisplayFrontend; } // detect stats slug, possibly on different languages $contentLanguages = JLanguageHelper::getLanguages('lang_code'); $possibleSlugs = array(); foreach ($contentLanguages as $langCode => $langDef) { $possibleSlugs[$langDef->sef . '/' . $analyticsSlug] = $langDef; } // add default slug $possibleSlugs[$analyticsSlug] = null; // check current request path against slug(s) $currentSlug = wbLTrim($uri->getPath(), Uri::base(true)); $currentSlug = ltrim($currentSlug, '/'); if (!array_key_exists($currentSlug, $possibleSlugs)) { return $shouldDisplayFrontend; } // set language if found if (!empty($possibleSlugs[$currentSlug])) { $newLanguageDef = $contentLanguages[$possibleSlugs[$currentSlug]->lang_code]; $uri->setVar( 'lang', $newLanguageDef->sef ); $app->input->set('language', $newLanguageDef->lang_code); $app->set('language', $newLanguageDef->lang_code); $language = Factory::getLanguage(); if ($language->getTag() !== $newLanguageDef->lang_code) { $newLanguageObject = Language::getInstance( $newLanguageDef->lang_code ); Factory::$language = $newLanguageObject; $app->loadLanguage($newLanguageObject); } } // authentification: either Joomla ACL based on user or direct access through a secret key passed in // URL: example.com/stats?dfd213245sdf $config = Sh404sefFactory::getConfig(); $keyAccessAllowed = !empty($config->analyticsEnableFrontendAccessWithKey); $keyName = $config->analyticsFrontendReportsAccessKey; if ( $keyAccessAllowed && !empty($keyName) ) { // access per key is allowed, do we have a key? $urlKey = null; if (!empty($keyName)) { $urlKey = $uri->getVar($keyName, null); } // is that key the one we expect? $keyAccessAllowed = !is_null($urlKey); } if (!$keyAccessAllowed) { $user = Factory::getUser(); // authorization if (!$user->authorise('sh404sef.view.analytics', 'com_sh404sef')) { if ($user->guest) { // guests need to login first $return = base64_encode(Uri::getInstance()); $loginUrl = JRoute::_('index.php?option=com_users&view=login&return=' . $return); Factory::getApplication()->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'), 'notice'); Factory::getApplication()->redirect($loginUrl, 403); } throw new \Exception(Text::_('JERROR_ALERTNOAUTHOR'), 403); } } // all clear, display return true; } /** * Check the http status code of a response from analytics servers. * * @param stdClass $response * * @throws Sh404sefExceptionDefault */ public static function verifyAuthResponse($response) { // check if valid response http code $status = $response->code; if ($status != 200) { $decodedResponse = json_decode($response->body); $msg = Text::_('COM_SH404SEF_ERROR_AUTH_ANALYTICS'); $code = 404; if (!empty($decodedResponse->error)) { $msg = $decodedResponse->error->message . ' (' . $decodedResponse->error->code . ')'; $code = (int) $decodedResponse->error->code; } ShlSystem_Log::debug('sh404sef', '%s::%d: %s', __METHOD__, __LINE__, 'Analytics: HTTP error in analytics endpoint comm, response:' . print_r($response, true)); throw new Sh404sefExceptionDefault($msg, $code); } } /** * prepare html filters to allow user to select the way she likes * to view reports * * @param $options * @param $viewList * @param $position * * @return array */ public static function prepareReportsFilters($options, $viewList, $position) { // array to hold various filters $filters = array(); // find if we must display all filters. On dashboard, only a reduced set $allFilters = wbArrayGet($options, 'showFilters', 'yes') == 'yes'; $customSelectSubmit = ' onchange="window.weeblrApp.analytics.update({' . ($allFilters ? '' : 'showFilters:\'no\'') . '});"'; $dateRangeSelectSubmit = ' onchange="window.weeblrApp.analytics.updateDateRange();"'; if ('bottom' == $position) { $select = '<div class="sh404sef-analytics-filters-element">'; $select .= '<label for="viewId">' . Text::_('COM_SH404SEF_ANALYTICS_REPORT') . '</label>'; $select .= Sh404sefHelperHtml::buildSelectList( $viewList, $options['viewId'], 'viewId', $autoSubmit = false, $addSelectAll = false, $selectAllTitle = '', '' ); $select .= '</div>'; $filters[] = $select; // select groupBy (day, week, month) $select = '<div class="sh404sef-analytics-filters-element">'; $select .= '<label for="groupBy">' . Text::_('COM_SH404SEF_ANALYTICS_GROUP_BY') . '</label>'; $select .= Sh404sefHelperAnalytics::buildAnalyticsGroupBySelectList( $options['groupBy'], 'groupBy', $autoSubmit = false, $addSelectAll = false, $selectAllTitle = '', $customSelectSubmit ); $select .= '</div>'; $filters[] = $select; // add a click to update link $filters[] = '<div class="sh404sef-analytics-filters-element sh404sef-analytics-filters-update-button">' . ShlHtmlBs_Helper::button( Text::_('COM_SH404SEF_CHECK_ANALYTICS'), 'primary', '', 'javascript: window.weeblrApp.analytics.update({forced:1' . ($allFilters ? '' : ',showFilters:\'no\'') . '});' ) . '</div>'; } // dashboard only has view selection, no room for anything else // only shows main selection drop downs on analytics view if ('top' == $position) { $select = '<div class="sh404sef-analytics-filters-element">'; $select .= '<label for="dateRange">' . Text::_('COM_SH404SEF_ANALYTICS_DASHBOARD_DATE_RANGE') . '</label>'; $select .= Sh404sefHelperHtml::buildSelectList( array( array('id' => 'last_7_days', 'title' => 'Last 7 days'), array('id' => 'last_30_days', 'title' => 'Last 30 days'), array('id' => 'last_90_days', 'title' => 'Last 90 days'), array('id' => 'this_year', 'title' => 'This year'), array('id' => 'custom', 'title' => 'Custom...'), ), wbArrayGet($options, 'dateRange', 'last_30_days'), 'dateRange', $autoSubmit = true, $addSelectAll = false, $selectAllTitle = '', $dateRangeSelectSubmit ); $select .= '</div>'; $filters[] = $select; $select = '<div class="sh404sef-analytics-filters-element">'; $select .= '<label for="startDate">' . Text::_('COM_SH404SEF_ANALYTICS_START_DATE') . '</label>'; $select .= JHTML::_('calendar', $options['startDate'], 'startDate', 'startDate', '%Y-%m-%d'); $select .= '</div>'; $filters[] = $select; $select = '<div class="sh404sef-analytics-filters-element">'; $select .= '<label for="endDate">' . Text::_('COM_SH404SEF_ANALYTICS_END_DATE') . '</label>'; $select .= JHTML::_('calendar', $options['endDate'], 'endDate', 'endDate', '%Y-%m-%d'); $select .= '</div>'; $filters[] = $select; // add a click to update link $filters[] = self::updateButton($allFilters); } // use layout to render return $filters; } /** * @param $allFilters * * @return string */ public static function updateButton($allFilters) { return '<div class="sh404sef-analytics-filters-element sh404sef-analytics-filters-update-button">' . ShlHtmlBs_Helper::button( Text::_('COM_SH404SEF_CHECK_ANALYTICS'), 'primary', '', 'javascript: window.weeblrApp.analytics.update({forced:1' . ($allFilters ? '' : ',showFilters:\'no\'') . '});' ) . '</div>'; } /** * Get this web site name, w/o scheme, port, etc * */ public static function getWebsiteName() { static $site; // Get the scheme if (!isset($site)) { $uri = JURI::getInstance(JURI::base()); $site = $uri->toString(array('host')); } return $site; } /** * Format an array of date strings for display as abscise * of a graphic * * @param array of object $entries * @param array $options * * @return array */ public static function formatAbciseDates($entries, $options) { // array to receive X labels $formattedDates = array(); // various cases of dimensions requested switch ($options['groupBy']) { case 'ga:year,ga:month,ga:week,ga:day': // date string represents a day. we use : mm/dd foreach ($entries as $entry) { $formattedDates[] = self::_getShortMonthString($entry->dimension['month']) . ' ' . $entry->dimension['day']; } break; // date string represents a week number case 'ga:year,ga:month,ga:week': foreach ($entries as $entry) { $formattedDates[] = self::_getWeekPeriodString($entry->dimension); } break; case 'ga:year,ga:month': // date string is a month number foreach ($entries as $entry) { $formattedDates[] = self::_getShortMonthString($entry->dimension['month']) . ' ' . substr($entry->dimension['year'], 2, 2); } break; } return $formattedDates; } protected static function _getShortMonthString($month) { switch ($month) { case 1: $m = Text::_('JANUARY_SHORT'); break; case 2: $m = Text::_('FEBRUARY_SHORT'); break; case 3: $m = Text::_('MARCH_SHORT'); break; case 4: $m = Text::_('APRIL_SHORT'); break; case 5: $m = Text::_('MAY_SHORT'); break; case 6: $m = Text::_('JUNE_SHORT'); break; case 7: $m = Text::_('JULY_SHORT'); break; case 8: $m = Text::_('AUGUST_SHORT'); break; case 9: $m = Text::_('SEPTEMBER_SHORT'); break; case 10: $m = Text::_('OCTOBER_SHORT'); break; case 11: $m = Text::_('NOVEMBER_SHORT'); break; case 12: $m = Text::_('DECEMBER_SHORT'); break; } return $m; } protected static function _getWeekPeriodString($dimension) { // start jan 1st of that year $date = new DateTime ($dimension['year'] . '-01-01'); // what day of week is that ? $janFirst = $date->format('w'); // if not a Sunday, we have at partial first week of year if ($janFirst != 0) { // jan first is not a Sunday, first add days for this first partial week $days = 6 - $janFirst; // then add days corresponding to the remaining number of weeks if ($dimension['week'] > 1) { $days += 7 * ($dimension['week'] - 1); } } else { // jan first is a sunday, we add 7 x number of weeks to jan first // to find date of LAST day of week #XX $days = 7 * $dimension['week'] - 1; } // add as many days as required to find LAST day of week $date->modify('+' . $days . ' days'); // then format result as needed $string = self::_getShortMonthString($date->format('m')) . ' ' . $date->format('d'); return $string; } /** * Calculates a displayable label for Google raw medium type */ public static function getReferralLabel($rawReferralType) { switch ($rawReferralType) { case '(none)': $label = Text::_('COM_SH404SEF_ANALYTICS_REF_LABEL_DIRECT'); break; case 'organic': $label = Text::_('COM_SH404SEF_ANALYTICS_REF_LABEL_ORGANIC'); break; case 'referral': $label = Text::_('COM_SH404SEF_ANALYTICS_REF_LABEL_REFERRAL'); break; default: $label = $rawReferralType; break; } return $label; } /** * Method to create a select list of possible analytics reports * * @access public * * @param int ID of current item * @param string name of select list * @param boolean if true, changing selected item will submit the form (assume is an "adminForm") * @param boolean , if true, a line 'Select all' is inserted at the start of the list * @param string the "Select all" to be displayed, if $addSelectAll is true * * @return string HTML output */ public static function buildAnalyticsReportSelectList($current, $name, $autoSubmit = false, $addSelectAll = false, $selectAllTitle = '', $customSubmit = '') { // available reports, should not be hardcoded though ! $data = array( array('id' => 'dashboard', 'title' => Text::_('COM_SH404SEF_ANALYTICS_REPORT_DASHBOARD')) , array('id' => 'visits', 'title' => Text::_('COM_SH404SEF_ANALYTICS_REPORT_VISITS')) , array('id' => 'sources', 'title' => Text::_('COM_SH404SEF_ANALYTICS_REPORT_SOURCES')) , array('id' => 'keywords', 'title' => Text::_('COM_SH404SEF_ANALYTICS_REPORT_KEYWORDS')) , array('id' => 'urls', 'title' => Text::_('COM_SH404SEF_ANALYTICS_REPORT_URLS')) , array('id' => 'equipment', 'title' => Text::_('COM_SH404SEF_ANALYTICS_REPORT_VISITORS_EQUIPMENT')) ); // use helper to build html return Sh404sefHelperHtml::buildSelectList($data, $current, $name, $autoSubmit, $addSelectAll, $selectAllTitle, $customSubmit); } /** * Method to create a select list of possible ways to group data on analytics reports * * @access public * * @param int ID of current item * @param string name of select list * @param boolean if true, changing selected item will submit the form (assume is an "adminForm") * @param boolean , if true, a line 'Select all' is inserted at the start of the list * @param string the "Select all" to be displayed, if $addSelectAll is true * * @return string HTML output */ public static function buildAnalyticsGroupBySelectList($current, $name, $autoSubmit = false, $addSelectAll = false, $selectAllTitle = '', $customSubmit = '') { // available reports, should not be hardcoded though ! $data = array( array('id' => 'ga:year,ga:month,ga:week,ga:day', 'title' => Text::_('Day')) , array('id' => 'ga:year,ga:month,ga:week', 'title' => Text::_('Week')) , array('id' => 'ga:year,ga:month', 'title' => Text::_('Month')) ); // use helper to build html return Sh404sefHelperHtml::buildSelectList($data, $current, $name, $autoSubmit, $addSelectAll, $selectAllTitle, $customSubmit); } /** * Encode a time value, in seconds * to predefined numerical codes * using 6 bits (64 values) * * @param float $time * * @return false|float|int */ public static function classifyTime($time) { // default $time = floatval($time); // break it down to classes if ($time < 4.95) { // max value for that case is going to be 49 // convert to 1/10th of second $time = round($time * 10); // ie 1.9 sec becomes 19 } else if ($time < 6.0) { $time = 50; } else if ($time < 7) { $time = 51; } else if ($time < 8) { $time = 52; } else if ($time < 9) { $time = 53; } else if ($time < 10) { $time = 54; } else if ($time < 11) { $time = 55; } else if ($time < 12) { $time = 56; } else if ($time < 13) { $time = 57; } else if ($time < 14) { $time = 58; } else if ($time < 15) { $time = 59; } else if ($time < 20) { $time = 60; } else if ($time < 25) { $time = 61; } else if ($time < 30) { $time = 62; } else { $time = 63; } return $time; } /** * Revert classification of page creation time * to get back approximate time from value range * encoded in analytics data * * @param integer $time code as per classification function * * @return float|int */ public static function declassifyTime($time) { // break it down to classes if ($time < 50) { // actual time in 1/10th of sec // convert back to secondd $time = $time / 10; // ie 1.9 sec becomes 19 } else if ($time == 50) { // $time = 5.5; } else if ($time == 51) { // $time = 6.5; } else if ($time == 52) { // $time = 7.5; } else if ($time == 53) { // $time = 8.5; } else if ($time == 54) { // $time = 9.5; } else if ($time == 55) { // $time = 10.5; } else if ($time == 56) { // $time = 11.5; } else if ($time == 57) { // $time = 12.5; } else if ($time == 58) { // $time = 13.5; } else if ($time == 59) { // $time = 14.5; } else if ($time == 60) { // $time = 17.5; } else if ($time == 61) { // $time = 22.5; } else if ($time == 62) { // $time = 27.5; } else { $time = 40.0; // default value if time exceeds 30 seconds } return $time; } /** * Classify memory consumption into predefined * value ranges, to encode it into only 16 values * * @param float $ram ram used up by page creation * * @return float|int */ public static function classifyMemory($ram) { // default $ram = floatval($ram); // break it down to classes if ($ram < 6) { $ram = 0; } else if ($ram < 8) { $ram = 1; } else if ($ram < 10) { $ram = 2; } else if ($ram < 12) { $ram = 3; } else if ($ram < 14) { $ram = 4; } else if ($ram < 16) { $ram = 5; } else if ($ram < 18) { $ram = 6; } else if ($ram < 20) { $ram = 7; } else if ($ram < 22) { $ram = 8; } else if ($ram < 24) { $ram = 9; } else if ($ram < 28) { $ram = 10; } else if ($ram < 32) { $ram = 11; } else if ($ram < 48) { $ram = 12; } else if ($ram < 64) { $ram = 13; } else if ($ram < 128) { $ram = 14; } else { $ram = 15; } return $ram; } /** * Revert classification of ram consumption * to get back approximate memory from value range * encoded in analytics data * * @param integer $ram code as per classification function * * @return int|mixed */ public static function declassifyMemory($ram) { $ramValues = array(3, 7, 9, 11, 13, 15, 17, 19, 21, 23, 26, 30, 40, 56, 96, 128); if (isset($ramValues[$ram])) { $ram = $ramValues[$ram]; } else { $ram = 0; } return $ram; } }