Commit 3136ffa9 authored by Mario Rimann's avatar Mario Rimann Committed by Steffen Ritter
Browse files

[!!!][-FEATURE] Remove page hit statistics

The feature of logging each page request either to a database
table or to an "apache-style"-logfile is rarely used these days
and the functionality can be replaced by the well-known tools
that act on client side (e.g. Google Analytics or Piwik).

Change-Id: Idd69bb485a968ba136b456577a3a5a086dbd3dfa
Resolves: #39347
Releases: 6.0
Reviewed-on: http://review.typo3.org/13229
Reviewed-by: Christian Kuhn
Tested-by: Christian Kuhn
Reviewed-by: Philipp Gampe
Reviewed-by: Steffen Ritter
Tested-by: Steffen Ritter
parent ea9c821a
......@@ -127,6 +127,13 @@ resource field from table sys_template during upgrading, fix the using
TypoScript to refer to resources located in fileadmin or similar directly,
and afterwards to finally delete the resource field in the install tool.
* Removed page hit logging functionality
Writing log entries to some logfile during frontend page hit was removed.
There are tons of solutions on the net and on server basis that can do a
better job than the core implementation ever did. Therefor the complete
code was dropped and all TypoScript config.stat* options are obsolete.
===============================================================================
Changes and Improvements
===============================================================================
......
......@@ -93,8 +93,6 @@ class t3lib_TStemplate {
),
'config.' => array(
'extTarget' => '_top',
'stat' => 1,
'stat_typeNumList' => '0,1',
'uniqueLinkVars' => 1
)
);
......@@ -1489,4 +1487,4 @@ class t3lib_TStemplate {
}
}
}
?>
?>
\ No newline at end of file
......@@ -43,17 +43,9 @@
*/
class tx_cms_layout extends recordList {
// External, static: For page statistics:
// fieldname from sys_stat to select on.
var $stat_select_field = 'page_id';
// eg. "HITS_days:-1"
var $stat_codes = array();
// External, static: Flags of various kinds:
// If TRUE, users/groups are shown in the page info box.
var $pI_showUser = 0;
// If TRUE, hit statistics are shown in the page info box.
var $pI_showStat = 1;
// The number of successive records to edit when showing content elements.
var $nextThree = 3;
// If TRUE, disables the edit-column icon for tt_content elements
......@@ -193,11 +185,8 @@ class tx_cms_layout extends recordList {
if (is_array($row)) {
// Select which fields to show:
$pKey = $GLOBALS['SOBE']->MOD_SETTINGS['function'] == 'tx_cms_webinfo_hits' ? 'hits' : $GLOBALS['SOBE']->MOD_SETTINGS['pages'];
$pKey = $GLOBALS['SOBE']->MOD_SETTINGS['pages'];
switch ($pKey) {
case 'hits':
$this->fieldArray = explode(',', 'title,' . implode(',', $this->stat_codes));
break;
case 1:
$this->cleanTableNames();
$tableNames = $this->allowedTableNames;
......@@ -282,17 +271,6 @@ class tx_cms_layout extends recordList {
array('title' => $GLOBALS['LANG']->sL($GLOBALS['TCA'][$f2]['ctrl']['title'], 1))
);
}
} elseif (substr($field, 0, 5) == 'HITS_') {
$fParts = explode(':', substr($field, 5));
switch ($fParts[0]) {
case 'days':
$timespan = mktime(0, 0, 0) + intval($fParts[1]) * 3600 * 24;
$theData[$field] = ' ' . date('d', $timespan);
break;
default:
$theData[$field] = '';
break;
}
} else {
$theData[$field] = '&nbsp;&nbsp;<strong>' .
$GLOBALS['LANG']->sL($GLOBALS['TCA']['pages']['columns'][$field]['label'], 1) .
......@@ -1105,43 +1083,6 @@ class tx_cms_layout extends recordList {
$c = $this->numberOfRecords($f2, $row['uid']);
$theData[$field] = '&nbsp;&nbsp;' . ($c ? $c : '');
}
} elseif (substr($field, 0, 5) == 'HITS_') {
if (t3lib_extMgm::isLoaded('sys_stat')) {
$fParts = explode(':', substr($field, 5));
switch ($fParts[0]) {
case 'days':
$timespan = mktime(0, 0, 0) + intval($fParts[1]) * 3600 * 24;
// Page hits
$number = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows(
'*',
'sys_stat',
$this->stat_select_field . '=' . intval($row['uid']) .
' AND tstamp >=' . intval($timespan) .
' AND tstamp <' . intval($timespan + 3600 * 24)
);
if ($number) {
// Sessions
$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
'count(*)',
'sys_stat',
$this->stat_select_field . '=' . intval($row['uid']) . '
AND tstamp>=' . intval($timespan) . '
AND tstamp<' . intval($timespan + 3600 * 24) . '
AND surecookie<>\'\'',
'surecookie'
);
$scnumber = $GLOBALS['TYPO3_DB']->sql_num_rows($res);
$number .= '/' . $scnumber;
} else {
$number = '';
}
break;
}
$theData[$field] = '&nbsp;' . $number;
} else {
$theData[$field] = '&nbsp;';
}
} else {
$theData[$field] = '&nbsp;&nbsp;' . htmlspecialchars(t3lib_BEfunc::getProcessedValue('pages', $field, $row[$field]));
}
......@@ -2043,77 +1984,6 @@ class tx_cms_layout extends recordList {
}
}
// Page hits (depends on "sys_stat" extension)
if ($this->pI_showStat && t3lib_extMgm::isLoaded('sys_stat')) {
// Counting total hits:
$count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('*', 'sys_stat', 'page_id=' . intval($rec['uid']));
if ($count) {
// Get min/max
$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('min(tstamp) AS min,max(tstamp) AS max', 'sys_stat', 'page_id=' . intval($rec['uid']));
$rrow2 = $GLOBALS['TYPO3_DB']->sql_fetch_row($res);
$lines[] = '';
$lines[] = array($GLOBALS['LANG']->getLL('pI_hitsPeriod') . ':', t3lib_BEfunc::date($rrow2[0]) . ' - ' .
t3lib_BEfunc::date($rrow2[1]) . ' (' . t3lib_BEfunc::calcAge($rrow2[1] - $rrow2[0], $this->agePrefixes) . ')');
$lines[] = array($GLOBALS['LANG']->getLL('pI_hitsTotal') . ':', $rrow2[0]);
// Last 10 days
$nextMidNight = mktime(0, 0, 0) + 1 * 3600 * 24;
$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('count(*), FLOOR((' . $nextMidNight . '-tstamp)/(24*3600)) AS day', 'sys_stat', 'page_id=' . intval($rec['uid']) . ' AND tstamp>' . ($nextMidNight - 10 * 24 * 3600), 'day');
$days = array();
while ($rrow = $GLOBALS['TYPO3_DB']->sql_fetch_row($res)) {
$days[$rrow[1]] = $rrow[0];
}
$headerH = array();
$contentH = array();
for ($a = 9; $a >= 0; $a--) {
$headerH[] = '
<td class="bgColor5" nowrap="nowrap">&nbsp;' . date('d', $nextMidNight - ($a + 1) * 24 * 3600) . '&nbsp;</td>';
$contentH[] = '
<td align="center">' . ($days[$a] ? intval($days[$a]) : '-') . '</td>';
}
// Compile first hit-table (last 10 days)
$hitTable = '
<table border="0" cellpadding="0" cellspacing="1" class="typo3-page-hits">
<tr>' . implode('', $headerH) . '</tr>
<tr>' . implode('', $contentH) . '</tr>
</table>';
$lines[] = array($GLOBALS['LANG']->getLL('pI_hits10days') . ':', $hitTable, 1);
// Last 24 hours
$nextHour = mktime(date('H'), 0, 0) + 3600;
$hours = 16;
$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('count(*), FLOOR((' . $nextHour . '-tstamp)/3600) AS hours', 'sys_stat', 'page_id=' . intval($rec['uid']) . ' AND tstamp>' . ($nextHour - $hours * 3600), 'hours');
$days = array();
while ($rrow = $GLOBALS['TYPO3_DB']->sql_fetch_row($res)) {
$days[$rrow[1]] = $rrow[0];
}
$headerH = array();
$contentH = array();
for ($a = ($hours - 1); $a >= 0; $a--) {
$headerH[] = '
<td class="bgColor5" nowrap="nowrap">&nbsp;' . intval(date('H', $nextHour - ($a + 1) * 3600)) . '&nbsp;</td>';
$contentH[] = '
<td align="center">' . ($days[$a] ? intval($days[$a]) : '-') . '</td>';
}
// Compile second hit-table (last 24 hours)
$hitTable = '
<table border="0" cellpadding="0" cellspacing="1" class="typo3-page-stat">
<tr>' . implode('', $headerH) . '</tr>
<tr>' . implode('', $contentH) . '</tr>
</table>';
$lines[] = array($GLOBALS['LANG']->getLL('pI_hits24hours') . ':', $hitTable, 1);
}
}
// Finally, wrap the elements in the $lines array in table cells/rows
foreach ($lines as $fV) {
if (is_array($fV)) {
......@@ -2240,4 +2110,4 @@ class tx_cms_layout extends recordList {
return strip_tags($content);
}
}
?>
?>
\ No newline at end of file
......@@ -62,15 +62,6 @@ class tslib_feTest extends tx_phpunit_testcase {
'public function roundTripCryptString($string) {' .
'return parent::roundTripCryptString($string);' .
'}' .
'public function stripIPv4($strIP) {' .
'return parent::stripIPv4($strIP);' .
'}' .
'public function stripIPv6($strIP) {' .
'return parent::stripIPv6($strIP);' .
'}' .
'}'
);
......@@ -222,87 +213,5 @@ class tslib_feTest extends tx_phpunit_testcase {
)
);
}
//////////////////////////////////////
// Tests concerning stat-anonymization
//////////////////////////////////////
/**
* Data provider for stripIPv6Correct
*
* @return array Data sets
*/
public static function stripIPv4DataProviderCorrect() {
return array(
'empty address, prefix-length 24' => array('0.0.0.0', '24', '0.0.0.0'),
'normal address 1, prefix-length 1' => array('1.2.3.4', '1', '0.0.0.0'),
'normal address 2, prefix-length 24' => array('192.168.5.79', '24', '192.168.5.0'),
'normal address 2, prefix-length 30' => array('192.168.5.79', '30', '192.168.5.76'),
// test for no anonymization; full prefix-length
'normal address 2, prefix-length 32' => array('192.168.5.79', '32', '192.168.5.79'),
// test for full anonymization; full prefix-length
'normal address 2, prefix-length 0' => array('192.168.5.79', '0', '0.0.0.0'),
);
}
/**
* @test
* @dataProvider stripIPv4DataProviderCorrect
*/
public function stripIPv4Correct($address, $prefixLength, $anonymized) {
$oldConfig = $this->fixture->config;
$this->fixture->config = array('config' =>
array('stat_IP_anonymize' => '1',
'stat_IP_anonymize_mask_ipv4' => $prefixLength
)
);
$this->assertEquals(
$this->fixture->stripIPv4($address),
$anonymized
);
$this->fixture->config = $oldConfig;
}
/**
* Data provider for stripIPv6Correct
*
* @return array Data sets
*/
public static function stripIPv6DataProviderCorrect() {
return array(
'empty address, prefix-length 96' => array('::', '96', '::'),
'normal address 1, prefix-length 1' => array('1:2:3::4', '1', '::'),
'normal address 2, prefix-length 4' => array('ffff::9876', '4', 'f000::'),
'normal address 2, prefix-length 1' => array('ffff::9876', '1', '8000::'),
'normal address 3, prefix-length 96' => array('abc:def::9876', '96', 'abc:def::'),
'normal address 3, prefix-length 120' => array('abc:def::9876', '120', 'abc:def::9800'),
// test for no anonymization; full prefix-length
'normal address 3, prefix-length 128' => array('abc:def::9876', '128', 'abc:def::9876'),
// test for full anonymization
'normal address 3, prefix-length 0' => array('abc:def::9876', '0', '::'),
);
}
/**
* @test
* @dataProvider stripIPv6DataProviderCorrect
*/
public function stripIPv6Correct($address, $prefixLength, $anonymized) {
$oldConfig = $this->fixture->config;
$this->fixture->config = array('config' =>
array('stat_IP_anonymize' => '1',
'stat_IP_anonymize_mask_ipv6' => $prefixLength
)
);
$this->assertEquals(
$this->fixture->stripIPv6($address),
$anonymized
);
$this->fixture->config = $oldConfig;
}
}
?>
\ No newline at end of file
......@@ -2065,9 +2065,6 @@ class tslib_fe {
$this->config['rootLine'] = $this->tmpl->rootLine;
$this->config['mainScript'] = trim($this->config['config']['mainScript']) ? trim($this->config['config']['mainScript']) : 'index.php';
// Initialize statistics handling: Check filename and permissions
$setStatPageName = $this->statistics_init();
// Class for render Header and Footer parts
$template = '';
if ($this->pSetup['pageHeaderFooterTemplateFile']) {
......@@ -2093,11 +2090,6 @@ class tslib_fe {
// Initialize charset settings etc.
$this->initLLvars();
// We want nice names, so we need to handle the charset
if ($setStatPageName) {
$this->statistics_init_pagename();
}
// No cache
// Set $this->no_cache TRUE if the config.no_cache value is set!
if ($this->config['config']['no_cache']) {
......@@ -2544,7 +2536,6 @@ class tslib_fe {
* Will exit if a location header is sent (for instance if jumpUrl was triggered)
*
* "jumpUrl" is a concept where external links are redirected from the index_ts.php script, which first logs the URL.
* This feature is only interesting if config.sys_stat is used.
*
* @return void
*/
......@@ -3549,119 +3540,15 @@ if (version == "n3") {
- ($GLOBALS['TT']->getMilliseconds($microtime_BE_USER_end) - $GLOBALS['TT']->getMilliseconds($microtime_BE_USER_start));
}
/**
* Initialize file-based statistics handling: Check filename and permissions, and create the logfile if it does not exist yet.
* This function should be called with care because it might overwrite existing settings otherwise.
*
* @return boolean TRUE if statistics are enabled (will require some more processing after charset handling is initialized)
* @access private
*/
protected function statistics_init() {
$setStatPageName = FALSE;
$theLogFile = $this->TYPO3_CONF_VARS['FE']['logfile_dir'].strftime($this->config['config']['stat_apache_logfile']);
// Add PATH_site left to $theLogFile if the path is not absolute yet
if (!t3lib_div::isAbsPath($theLogFile)) {
$theLogFile = PATH_site.$theLogFile;
}
if ($this->config['config']['stat_apache'] && $this->config['config']['stat_apache_logfile'] && !strstr($this->config['config']['stat_apache_logfile'], '/')) {
if (t3lib_div::isAllowedAbsPath($theLogFile)) {
if (!@is_file($theLogFile)) {
// Try to create the logfile
touch($theLogFile);
t3lib_div::fixPermissions($theLogFile);
}
if (@is_file($theLogFile) && @is_writable($theLogFile)) {
$this->config['stat_vars']['logFile'] = $theLogFile;
// Set page name later on
$setStatPageName = TRUE;
} else {
$GLOBALS['TT']->setTSlogMessage('Could not set logfile path. Check filepath and permissions.', 3);
}
}
}
return $setStatPageName;
}
/**
* Set the pagename for the logfile entry
*
* @return void
* @access private
*/
protected function statistics_init_pagename() {
// Make life easier and accept variants for utf-8
if (preg_match('/utf-?8/i', $this->config['config']['stat_apache_niceTitle'])) {
$this->config['config']['stat_apache_niceTitle'] = 'utf-8';
}
if ($this->config['config']['stat_apache_niceTitle'] == 'utf-8') {
$shortTitle = $this->csConvObj->utf8_encode($this->page['title'], $this->renderCharset);
} elseif ($this->config['config']['stat_apache_niceTitle']) {
$shortTitle = $this->csConvObj->specCharsToASCII($this->renderCharset, $this->page['title']);
} else {
$shortTitle = $this->page['title'];
}
$len = t3lib_utility_Math::forceIntegerInRange($this->config['config']['stat_apache_pageLen'], 1, 100, 30);
if ($this->config['config']['stat_apache_niceTitle'] == 'utf-8') {
$shortTitle = rawurlencode($this->csConvObj->substr('utf-8', $shortTitle, 0, $len));
} else {
$shortTitle = substr(preg_replace('/[^.[:alnum:]_-]/', '_', $shortTitle), 0, $len);
}
$pageName = $this->config['config']['stat_apache_pagenames'] ? $this->config['config']['stat_apache_pagenames'] : '[path][title]--[uid].html';
$pageName = str_replace('[title]', $shortTitle, $pageName);
$pageName = str_replace('[uid]', $this->page['uid'], $pageName);
$pageName = str_replace('[alias]', $this->page['alias'], $pageName);
$pageName = str_replace('[type]', $this->type, $pageName);
$pageName = str_replace('[request_uri]', t3lib_div::getIndpEnv('REQUEST_URI'), $pageName);
$temp = $this->config['rootLine'];
// rootLine does not exist if this function is called at early stage (e.g. if DB connection failed)
if ($temp) {
array_pop($temp);
if ($this->config['config']['stat_apache_noRoot']) {
array_shift($temp);
}
$len = t3lib_utility_Math::forceIntegerInRange($this->config['config']['stat_titleLen'], 1, 100, 20);
if ($this->config['config']['stat_apache_niceTitle'] == 'utf-8') {
$path = '';
$c = count($temp);
for ($i=0; $i<$c; $i++) {
if ($temp[$i]['uid']) {
$p = $this->csConvObj->crop('utf-8', $this->csConvObj->utf8_encode($temp[$i]['title'], $this->renderCharset), $len, "\xE2\x80\xA6"); // U+2026; HORIZONTAL ELLIPSIS
$path.= '/' . rawurlencode($p);
}
}
} elseif ($this->config['config']['stat_apache_niceTitle']) {
$path = $this->csConvObj->specCharsToASCII($this->renderCharset, $this->sys_page->getPathFromRootline($temp, $len));
} else {
$path = $this->sys_page->getPathFromRootline($temp, $len);
}
} else {
// If rootLine is missing, we just drop the path...
$path = '';
}
if ($this->config['config']['stat_apache_niceTitle'] == 'utf-8') {
$this->config['stat_vars']['pageName'] = str_replace('[path]', $path.'/', $pageName);
} else {
$this->config['stat_vars']['pageName'] = str_replace('[path]', preg_replace('/[^.[:alnum:]\/_-]/', '_', $path . '/'), $pageName);
}
}
/**
* Get the (partially) anonymized IP address for the log file
* configure: set set config.stat_IP_anonymize=1
* Configure: set set config.stat_IP_anonymize=1
*
* @return string the IP to log
* @return string the IP to log
* @deprecated since 6.0, will be removed with 6.2
*/
public function getLogIPAddress() {
t3lib_div::logDeprecatedFunction();
$result = t3lib_div::getIndpEnv('REMOTE_ADDR');
if ($this->config['config']['stat_IP_anonymize']) {
if (strpos($result, ':')) {
......@@ -3673,70 +3560,15 @@ if (version == "n3") {
return $result;
}
/**
* Strip parts from a IPv6 address
*
* configure: set config.stat_IP_anonymize_mask_ipv6 to a prefix-length (0 to 128)
* defaults to 64 if not set
*
* @param string $strIP Raw IPv6 address
* @return string stripped address
*/
protected function stripIPv6($strIP) {
if (isset($this->config['config']['stat_IP_anonymize_mask_ipv6'])) {
$netPrefix = intval($this->config['config']['stat_IP_anonymize_mask_ipv6']);
} else {
$netPrefix = 64;
}
$bytesIP = t3lib_div::IPv6Hex2Bin($strIP);
$bitsToStrip = (128 - $netPrefix);
for($counter = 15; $counter >= 0; $counter--)
{
$bitsToStripPart = min($bitsToStrip, 8);
// TODO find a nicer solution for bindec and chr/ord below - but it works :-)
$mask = bindec(str_pad('', 8 - $bitsToStripPart, '1') . str_pad('', $bitsToStripPart, '0'));
$bytesIP[$counter] = chr(ord($bytesIP[$counter]) & $mask);
$bitsToStrip -= $bitsToStripPart;
}
$strIP = inet_ntop($bytesIP);
return $strIP;
}
/**
* Strip parts from IPv4 addresses
*
* configure: set config.stat_IP_anonymize_mask_ipv4 to a prefix-length (0 to 32)
* defaults to 24, if not set
*
* @param string $strIP IPv4 address
* @return string stripped IP address
*/
protected function stripIPv4($strIP) {
if (isset($this->config['config']['stat_IP_anonymize_mask_ipv4'])) {
$netPrefix = intval($this->config['config']['stat_IP_anonymize_mask_ipv4']);
} else {
$netPrefix = 24;
}
$bitsToStrip = (32 - $netPrefix);
$ip = ip2long($strIP);
// Shift right
$ip = $ip >> $bitsToStrip;
// Shift left; last bytes will be zero now
$ip = $ip << $bitsToStrip;
$strIP = long2ip($ip);
return $strIP;
}
/**
* Get the (possibly) anonymized host name for the log file
* configure: set config.stat_IP_anonymize=1
* Configure: set config.stat_IP_anonymize=1
*
* @return the host name to log
* @deprecated since 6.0, will be removed with 6.2
*/
public function getLogHostName() {
t3lib_div::logDeprecatedFunction();
if ($this->config['config']['stat_IP_anonymize']) {
// Ignore hostname if IP anonymized
$hostName = '<anonymized>';
......@@ -3748,11 +3580,13 @@ if (version == "n3") {
/**
* Get the (possibly) anonymized username or user id for the log file
* configure: set config.stat_IP_anonymize=1
* Configure: set config.stat_IP_anonymize=1
*
* @return string The user name /uid to log
* @deprecated since 6.0, will be removed with 6.2
*/
public function getLogUserName() {
t3lib_div::logDeprecatedFunction();
$logUser = (isset($this->config['config']['stat_logUser'])) ? $this->config['config']['stat_logUser'] : TRUE;
if ($this->loginUser && $logUser) {
$userName = $this->fe_user->user['username'];
......@@ -3762,113 +3596,6 @@ if (version == "n3") {
return $userName;
}
/**
* Saves hit statistics
*
* @return void
*/
function statistics() {
if (!empty($this->config['config']['stat']) &&
(!strcmp('', $this->config['config']['stat_typeNumList']) || t3lib_div::inList(str_replace(' ', '', $this->config['config']['stat_typeNumList']), $this->type)) &&
(empty($this->config['config']['stat_excludeBEuserHits']) || !$this->beUserLogin) &&
(empty($this->config['config']['stat_excludeIPList']) || !t3lib_div::cmpIP(t3lib_div::getIndpEnv('REMOTE_ADDR'), str_replace(' ', '', $this->config['config']['stat_excludeIPList'])))) {
$GLOBALS['TT']->push('Stat');
if (t3lib_extMgm::isLoaded('sys_stat') && !empty($this->config['config']['stat_mysql'])) {
// Jumpurl:
$sword = t3lib_div::_GP('sword');
if ($sword) {
$jumpurl_msg = 'sword:'.$sword;
} elseif ($this->jumpurl) {
$jumpurl_msg = 'jumpurl:'.$this->jumpurl;
} else {
$jumpurl_msg = '';
}
// Flags: bits: 0 = BE_user, 1=Cached page?
$flags=0;
if ($this->beUserLogin) {$flags|=1;}
if ($this->cacheContentFlag) {$flags|=2;}
// Ref url:
$refUrl = t3lib_div::getIndpEnv('HTTP_REFERER');
$thisUrl = t3lib_div::getIndpEnv('TYPO3_REQUEST_DIR');
if (t3lib_div::isFirstPartOfStr($refUrl