Starter for a re-jig of hit counting (in a per-day table).
authorAndrew Garrett <werdna@users.mediawiki.org>
Wed, 19 Nov 2008 12:01:43 +0000 (12:01 +0000)
committerAndrew Garrett <werdna@users.mediawiki.org>
Wed, 19 Nov 2008 12:01:43 +0000 (12:01 +0000)
Needs configurability, purty graphs, and some cleanup in the back-end.

includes/Article.php
includes/AutoLoader.php
includes/QueryPage.php
includes/Skin.php
includes/SkinTemplate.php
includes/SpecialPage.php
includes/specials/SpecialPageStatistics.php [new file with mode: 0644]
includes/specials/SpecialStatistics.php
languages/messages/MessagesEn.php
maintenance/archives/patch-hit_statistics.sql [new file with mode: 0644]

index f64e9f4..662ab21 100644 (file)
@@ -3125,9 +3125,11 @@ class Article {
                $acchitsTable = $dbw->tableName( 'acchits' );
 
                if( $wgHitcounterUpdateFreq <= 1 ) {
-                       $dbw->query( "UPDATE $pageTable SET page_counter = page_counter + 1 WHERE page_id = $id" );
+                       self::incrementCounterByValue( $id, 1 );
                        return;
                }
+               
+               ## TODO: Will get to making this stuff use the new infrastructure.
 
                # Not important enough to warrant an error page in case of failure
                $oldignore = $dbw->ignoreErrors( true );
@@ -3171,6 +3173,47 @@ class Article {
                }
                $dbw->ignoreErrors( $oldignore );
        }
+       
+       static function incrementCounterByValue( $article_id, $number ) {
+               $dbw = wfGetDB( DB_MASTER );
+               $dbr = wfGetDB( DB_SLAVE );
+               $oldIgnore = $dbw->ignoreErrors( true );
+               
+               ## Does a row already exist?
+               ## We really need ON DUPLICATE KEY UPDATE :(
+               $startOfToday = $dbw->timestamp( strtotime( '0:00' ) ); // Hack hack hack
+               $endOfToday = $dbw->timestamp( strtotime( '0:00 tomorrow' ) );
+               $rowExists = $dbr->selectField( 'hit_statistics', '1', array( 'hs_page' => $article_id, "hs_period_start='$startOfToday'", "hs_period_end='$endOfToday'" ), __METHOD__ );
+               
+               if ($rowExists) {
+                       $dbw->update( 'hit_statistics', array( 'hs_count=hs_count+'.intval($number) ), array( 'hs_page' => $article_id, 'hs_period_start' => $startOfToday, 'hs_period_end' => $endOfToday ), __METHOD__ );
+               } else {
+                       $row = array(
+                               'hs_page' => $article_id,
+                               'hs_period_start' => $startOfToday,
+                               'hs_period_end' => $endOfToday,
+                               'hs_period_length' => 86400, // One day.
+                               'hs_count' => $number
+                       );
+                       
+                       $dbw->insert( 'hit_statistics', $row, __METHOD__ );
+                       
+                       ## Update with the previous day's hits...
+                       Article::updatePageRowCounter( $article_id );
+               }
+               
+               $dbw->ignoreErrors( false );
+       }
+       
+       static function updatePageRowCounter( $article_id ) {
+               $dbw = wfGetDB( DB_MASTER );
+               $oi = $dbw->ignoreErrors( true );
+               
+               $total = $dbw->selectField( 'hit_statistics', 'sum(hs_count)', array( 'hs_page' => $article_id ), __METHOD__ );
+               $dbw->update( 'page', array( 'page_counter' => $total ), array( 'page_id' => $article_id ), __METHOD__ );
+               
+               $dbw->ignoreErrors( $oi );
+       }
 
        /**#@+
         * The onArticle*() functions are supposed to be a kind of hooks
index b531599..1e75ca0 100644 (file)
@@ -471,6 +471,7 @@ $wgAutoloadLocalClasses = array(
        'PageArchive' => 'includes/specials/SpecialUndelete.php',
        'PasswordResetForm' => 'includes/specials/SpecialResetpass.php',
        'PopularPagesPage' => 'includes/specials/SpecialPopularpages.php',
+       'SpecialPageStatistics' => 'includes/specials/SpecialPageStatistics.php',
        'PreferencesForm' => 'includes/specials/SpecialPreferences.php',
        'RandomPage' => 'includes/specials/SpecialRandompage.php',
        'RevisionDeleteForm' => 'includes/specials/SpecialRevisiondelete.php',
index 5f1acdb..51226ed 100644 (file)
@@ -49,6 +49,7 @@ $wgQueryPages = array(
 );
 wfRunHooks( 'wgQueryPages', array( &$wgQueryPages ) );
 
+## Maybe this can be re-enabled with the new hit-counting infrastructure??
 global $wgDisableCounters;
 if ( !$wgDisableCounters )
        $wgQueryPages[] = array( 'PopularPagesPage',            'Popularpages'          );
index 1da33ec..64f9a22 100644 (file)
@@ -1269,11 +1269,10 @@ END;
                if ( 0 == $wgArticle->getID() ) { return ''; }
 
                $s = '';
-               if ( !$wgDisableCounters ) {
-                       $count = $wgLang->formatNum( $wgArticle->getCount() );
-                       if ( $count ) {
-                               $s = wfMsgExt( 'viewcount', array( 'parseinline' ), $count );
-                       }
+               $count = $wgArticle->getCount();
+               if ( $count ) {
+                       $count = $wgLang->formatNum( $count );
+                       $s = wfMsgExt( 'viewcount', array( 'parseinline' ), $count );
                }
 
                if( $wgMaxCredits != 0 ){
index 33307e3..4e15515 100644 (file)
@@ -138,7 +138,7 @@ class SkinTemplate extends Skin {
                global $wgScript, $wgStylePath, $wgContLanguageCode;
                global $wgMimeType, $wgJsMimeType, $wgOutputEncoding, $wgRequest;
                global $wgXhtmlDefaultNamespace, $wgXhtmlNamespaces;
-               global $wgDisableCounters, $wgLogo, $action, $wgFeedClasses, $wgHideInterlanguageLinks;
+               global $wgLogo, $action, $wgFeedClasses, $wgHideInterlanguageLinks;
                global $wgMaxCredits, $wgShowCreditsIfMax;
                global $wgPageShowWatchingUsers;
                global $wgUseTrackbacks, $wgUseSiteJs;
@@ -329,8 +329,10 @@ class SkinTemplate extends Skin {
                if ( $out->isArticle() and (!isset( $oldid ) or isset( $diff )) and
                        $wgArticle and 0 != $wgArticle->getID() )
                {
-                       if ( !$wgDisableCounters ) {
-                               $viewcount = $wgLang->formatNum( $wgArticle->getCount() );
+                       $count = $wgArticle->getCount();
+                       
+                       if ( $count ) {
+                               $viewcount = $wgLang->formatNum( $count );
                                if ( $viewcount ) {
                                        $tpl->set('viewcount', wfMsgExt( 'viewcount', array( 'parseinline' ), $viewcount ) );
                                } else {
index a6afba4..9d1d503 100644 (file)
@@ -158,6 +158,7 @@ class SpecialPage
                'Randomredirect'            => 'SpecialRandomredirect',
                'Withoutinterwiki'          => array( 'SpecialPage', 'Withoutinterwiki' ),
                'Filepath'                  => array( 'SpecialPage', 'Filepath' ),
+               'PageStatistics'            => 'SpecialPageStatistics',
 
                'Mypage'                    => array( 'SpecialMypage' ),
                'Mytalk'                    => array( 'SpecialMytalk' ),
diff --git a/includes/specials/SpecialPageStatistics.php b/includes/specials/SpecialPageStatistics.php
new file mode 100644 (file)
index 0000000..3781c73
--- /dev/null
@@ -0,0 +1,125 @@
+<?php
+if (!defined('MEDIAWIKI'))
+       die();
+
+class SpecialPageStatistics extends SpecialPage {
+       function __construct() {
+               parent::__construct( 'PageStatistics' );
+       }
+       
+       function execute( $subpage ) {
+               global $wgOut;
+               
+               $wgOut->setPageTitle( wfMsg( 'pagestatistics' ) );
+               $wgOut->setRobotPolicy( "noindex,nofollow" );
+               $wgOut->setArticleRelated( false );
+               $wgOut->enableClientCache( false );
+               
+               $this->setHeaders();
+               $this->loadParameters( $subpage );
+       
+               if ($this->page) {
+                       $this->showStatistics( );
+               } else {
+                       $this->showMain();
+               }
+               
+               
+       }
+       
+       function loadParameters( $subpage ) {
+               global $wgRequest;
+               
+               $this->page = $subpage;
+               $this->periodStart = $wgRequest->getVal( 'periodstart' );
+               $this->periodEnd = $wgRequest->getVal( 'periodend' );
+               
+               if ($p = $wgRequest->getVal( 'target' ) )
+                       $this->page = $p;
+       }
+       
+       function showSearchBox(  ) {
+               global $wgOut;
+               
+               $fields = array();
+               $fields['pagestatistics-page'] = Xml::input( 'target', 45, $this->page );
+               $fields['pagestatistics-periodstart'] = Xml::input( 'periodstart', 45, $this->periodStart );
+               $fields['pagestatistics-periodend'] = Xml::input( 'periodend', 45, $this->periodEnd );
+               
+               $form = Xml::buildForm( $fields, 'pagestatistics-search' );
+               $form .= Xml::hidden( 'title', $this->getTitle()->getPrefixedText() );
+               $form = Xml::tags( 'form', array( 'method' => 'GET', 'action' => $this->getTitle()->getFullURL() ), $form );
+               $form = Xml::fieldset( wfMsgExt( 'pagestatistics-search-legend', 'parseinline' ), $form );
+               
+               $wgOut->addHTML( $form );
+       }
+       
+       function showMain() {
+               global $wgUser, $wgOut;
+               
+               $sk = $wgUser->getSkin();
+               
+               ## Create initial intro
+               $wgOut->addWikiMsg( 'pagestatistics-intro' );
+               
+               ## Fieldset with search stuff
+               $this->showSearchBox( );
+       }
+       
+       function showStatistics() {
+               global $wgLang, $wgOut;
+               
+               $this->showSearchBox();
+               
+               ## For now, just a data table.
+               $dbr = wfGetDB( DB_SLAVE );
+               
+               $article = new Article( Title::newFromText( $this->page ) );
+               
+               $periodStart = $dbr->addQuotes( $dbr->timestamp( strtotime( $this->periodStart ) ) );
+               $periodEnd = $dbr->addQuotes( $dbr->timestamp( strtotime( $this->periodEnd ) ) );
+               
+               $res = $dbr->select( 'hit_statistics', '*', array( "hs_period_start>=$periodStart", "hs_period_end<=$periodEnd", 'hs_page' => $article->getId() ), __METHOD__ );
+
+               $html = Xml::tags( 'th', null, wfMsgExt( 'pagestatistics-datatable-periodstart', 'parseinline' ) );
+               $html .= Xml::tags( 'th', null, wfMsgExt( 'pagestatistics-datatable-periodend', 'parseinline' ) );
+               $html .= Xml::tags( 'th', null, wfMsgExt( 'pagestatistics-datatable-count', 'parseinline' ) );
+               $html = Xml::tags( 'tr', null, $html );
+               
+               $total = 0;
+               $data = array();
+               while( $row = $dbr->fetchObject( $res ) ) {
+                       $thisData = array(
+                               'count' => $row->hs_count,
+                               'start' => $row->hs_period_start,
+                               'end' => $row->hs_period_end,
+                       );                      
+                       $data[] = $thisData;
+                       
+                       $total += $row->hs_count;
+                       
+                       $thisRow = Xml::tags( 'td', null, $wgLang->timeanddate( $row->hs_period_start ) );
+                       $thisRow .= Xml::tags('td', null, $wgLang->timeanddate( $row->hs_period_end ) );
+                       $thisRow .= Xml::tags('td', null, $row->hs_count );
+                       $thisRow = Xml::tags( 'tr', null, $thisRow );
+                       
+                       $html .= "$thisRow\n";
+               }
+               
+               ## Rollup total row
+               $totalLabel = Xml::tags( 'strong', null, wfMsgExt( 'pagestatistics-datatable-total', 'parseinline' ) );
+               $thisRow = Xml::tags( 'td', null, $totalLabel );
+               $thisRow .= Xml::tags('td', null, $totalLabel );
+               $thisRow .= Xml::tags('td', null, $total );
+               $thisRow = Xml::tags( 'tr', null, $thisRow );
+               
+               $html .= "$thisRow\n";
+               
+               $html = Xml::tags( 'table', null, Xml::tags( 'tbody', null, $html ) );
+               $wgOut->addHTML( $html );
+               
+               $wgOut->addWikitext( 'Purty graph goes here' );
+               
+               ## TODO purty graph :)
+       }
+}
\ No newline at end of file
index cd4a9b2..cc2af07 100644 (file)
@@ -31,6 +31,7 @@ function wfSpecialStatistics( $par = '' ) {
        $numJobs = SiteStats::jobs();
 
        # Staticic - views
+       ## Maybe re-enablable with new hitcounter infrastructure, plus more goodies like newly popular pages.
        $viewsStats = '';
        if( !$wgDisableCounters ) {
                $viewsStats = Xml::tags( 'th', array( 'colspan' => '2' ), wfMsg( 'statistics-header-views' ) ) .
index ca1907b..f2afaaa 100644 (file)
@@ -3727,6 +3727,19 @@ Enter the filename without the "{{ns:image}}:" prefix.',
 'fileduplicatesearch-result-1' => 'The file "$1" has no identical duplication.',
 'fileduplicatesearch-result-n' => 'The file "$1" has {{PLURAL:$2|1 identical duplication|$2 identical duplications}}.',
 
+## Special:PageStatistics
+'pagestatistics' => 'Page statistics',
+'pagestatistics-intro' => 'This page allows you to search page-view statistics for given pages on the wiki.',
+'pagestatistics-search-legend' => 'Search for statistics',
+'pagestatistics-page' => 'Page:',
+'pagestatistics-periodstart' => 'Start of period:',
+'pagestatistics-periodend' => 'End of period:',
+'pagestatistics-search' => 'Search',
+'pagestatistics-datatable-periodstart' => 'Start of period',
+'pagestatistics-datatable-periodend' => 'End of period',
+'pagestatistics-datatable-count' => 'Count',
+'pagestatistics-datatable-total' => 'Total',
+
 # Special:SpecialPages
 'specialpages'                   => 'Special pages',
 'specialpages-summary'           => '', # do not translate or duplicate this message to other languages
diff --git a/maintenance/archives/patch-hit_statistics.sql b/maintenance/archives/patch-hit_statistics.sql
new file mode 100644 (file)
index 0000000..96053c4
--- /dev/null
@@ -0,0 +1,14 @@
+-- Creates the hit_statistics table, for storing, as the name implies, hit statistics.
+
+CREATE TABLE /*$wgDBprefix*/hit_statistics (
+       hs_page bigint(20) NOT NULL, -- Maybe this should be a namespace/title tuple instead.
+       hs_period_start binary(14) NOT NULL,
+       hs_period_end binary(14) NOT NULL,
+       hs_period_length bigint(20) NOT NULL,
+       hs_count bigint(20) NOT NULL,
+
+       PRIMARY KEY  (hs_page,hs_period_start),
+       KEY hs_period_start (hs_period_start),
+       KEY hs_period_length (hs_period_length),
+       KEY hs_count (hs_count)
+) /*$wgDBTableOptions*/;
\ No newline at end of file