PageRenderTime 26ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/extensions/FlaggedRevs/presentation/specialpages/reports/ValidationStatistics_body.php

https://github.com/ChuguluGames/mediawiki-svn
PHP | 307 lines | 265 code | 27 blank | 15 comment | 22 complexity | aa60809a7aff5f3c228012aa7d3a0c43 MD5 | raw file
  1. <?php
  2. class ValidationStatistics extends IncludableSpecialPage {
  3. protected $latestData = null;
  4. public function __construct() {
  5. parent::__construct( 'ValidationStatistics' );
  6. }
  7. public function execute( $par ) {
  8. global $wgUser, $wgOut, $wgLang, $wgContLang, $wgFlaggedRevsStats;
  9. $this->setHeaders();
  10. $this->skin = $wgUser->getSkin();
  11. $this->db = wfGetDB( DB_SLAVE );
  12. $this->maybeUpdate();
  13. $ec = $this->getEditorCount();
  14. $rc = $this->getReviewerCount();
  15. $mt = $this->getMeanReviewWait();
  16. $mdt = $this->getMedianReviewWait();
  17. $pt = $this->getMeanPendingWait();
  18. $pData = $this->getPercentiles();
  19. $timestamp = $this->getLastUpdate();
  20. $wgOut->addWikiMsg( 'validationstatistics-users',
  21. $wgLang->formatnum( $ec ), $wgLang->formatnum( $rc )
  22. );
  23. # Most of the output depends on background queries
  24. if ( !$this->readyForQuery() ) {
  25. return false;
  26. }
  27. # Is there a review time table available?
  28. if ( count( $pData ) ) {
  29. $headerRows = $dataRows = '';
  30. foreach ( $pData as $percentile => $perValue ) {
  31. $headerRows .= "<th>P<sub>" . intval( $percentile ) . "</sub></th>";
  32. $dataRows .= '<td>' .
  33. $wgLang->formatTimePeriod( $perValue, 'avoidminutes' ) . '</td>';
  34. }
  35. $css = 'wikitable flaggedrevs_stats_table';
  36. $reviewChart = "<table class='$css' style='white-space: nowrap;'>\n";
  37. $reviewChart .= "<tr align='center'>$headerRows</tr>\n";
  38. $reviewChart .= "<tr align='center'>$dataRows</tr>\n";
  39. $reviewChart .= "</table>\n";
  40. } else {
  41. $reviewChart = '';
  42. }
  43. if ( $timestamp != '-' ) {
  44. # Show "last updated"...
  45. $wgOut->addWikiMsg( 'validationstatistics-lastupdate',
  46. $wgLang->date( $timestamp, true ),
  47. $wgLang->time( $timestamp, true )
  48. );
  49. }
  50. $wgOut->addHtml( '<hr/>' );
  51. # Show pending time stats...
  52. $wgOut->addWikiMsg( 'validationstatistics-pndtime',
  53. $wgLang->formatTimePeriod( $pt, 'avoidminutes' ) );
  54. # Show review time stats...
  55. if ( !FlaggedRevs::useOnlyIfProtected() ) {
  56. $wgOut->addWikiMsg( 'validationstatistics-revtime',
  57. $wgLang->formatTimePeriod( $mt, 'avoidminutes' ),
  58. $wgLang->formatTimePeriod( $mdt, 'avoidminutes' ),
  59. $reviewChart
  60. );
  61. }
  62. # Show per-namespace stats table...
  63. $wgOut->addWikiMsg( 'validationstatistics-table' );
  64. $wgOut->addHTML(
  65. Xml::openElement( 'table', array( 'class' => 'wikitable flaggedrevs_stats_table' ) )
  66. );
  67. $wgOut->addHTML( "<tr>\n" );
  68. // Headings (for a positive grep result):
  69. // validationstatistics-ns, validationstatistics-total, validationstatistics-stable,
  70. // validationstatistics-latest, validationstatistics-synced, validationstatistics-old
  71. $msgs = array( 'ns', 'total', 'stable', 'latest', 'synced', 'old' ); // our headings
  72. foreach ( $msgs as $msg ) {
  73. $wgOut->addHTML( '<th>' .
  74. wfMsgExt( "validationstatistics-$msg", 'parseinline' ) . '</th>' );
  75. }
  76. $wgOut->addHTML( "</tr>\n" );
  77. $namespaces = FlaggedRevs::getReviewNamespaces();
  78. foreach ( $namespaces as $namespace ) {
  79. $total = $this->getTotalPages( $namespace );
  80. $reviewed = $this->getReviewedPages( $namespace );
  81. $synced = $this->getSyncedPages( $namespace );
  82. if ( $total === '-' || $reviewed === '-' || $synced === '-' ) {
  83. continue; // NS added to config recently?
  84. }
  85. $NsText = $wgContLang->getFormattedNsText( $namespace );
  86. $NsText = $NsText ? $NsText : wfMsgHTML( 'blanknamespace' );
  87. $percRev = intval( $total ) == 0
  88. ? '-' // devision by zero
  89. : wfMsg( 'parentheses',
  90. wfMsgExt( 'percent', array( 'escapenoentities' ),
  91. $wgLang->formatnum( sprintf( '%4.2f',
  92. 100 * intval( $reviewed ) / intval( $total ) ) )
  93. )
  94. );
  95. $percLatest = intval( $total ) == 0
  96. ? '-' // devision by zero
  97. : wfMsg( 'parentheses',
  98. wfMsgExt( 'percent', array( 'escapenoentities' ),
  99. $wgLang->formatnum( sprintf( '%4.2f',
  100. 100 * intval( $synced ) / intval( $total ) ) )
  101. )
  102. );
  103. $percSynced = intval( $reviewed ) == 0
  104. ? '-' // devision by zero
  105. : wfMsgExt( 'percent', array( 'escapenoentities' ),
  106. $wgLang->formatnum( sprintf( '%4.2f',
  107. 100 * intval( $synced ) / intval( $reviewed ) ) )
  108. );
  109. $outdated = intval( $reviewed ) - intval( $synced );
  110. $outdated = $wgLang->formatnum( max( 0, $outdated ) ); // lag between queries
  111. $wgOut->addHTML(
  112. "<tr align='center'>
  113. <td>" .
  114. htmlspecialchars( $NsText ) .
  115. "</td>
  116. <td>" .
  117. htmlspecialchars( $wgLang->formatnum( $total ) ) .
  118. "</td>
  119. <td>" .
  120. htmlspecialchars( $wgLang->formatnum( $reviewed ) .
  121. $wgContLang->getDirMark() ) . " <i>$percRev</i>
  122. </td>
  123. <td>" .
  124. htmlspecialchars( $wgLang->formatnum( $synced ) .
  125. $wgContLang->getDirMark() ) . " <i>$percLatest</i>
  126. </td>
  127. <td>" .
  128. $percSynced .
  129. "</td>
  130. <td>" .
  131. $this->skin->linkKnown( SpecialPage::getTitleFor( 'PendingChanges' ),
  132. htmlspecialchars( $outdated ),
  133. array(),
  134. array( 'namespace' => $namespace )
  135. ) .
  136. "</td>
  137. </tr>"
  138. );
  139. }
  140. $wgOut->addHTML( Xml::closeElement( 'table' ) );
  141. # Is there a top X user list? If so, then show it...
  142. $data = $this->getTopReviewers();
  143. if ( is_array( $data ) && count( $data ) ) {
  144. $wgOut->addWikiMsg( 'validationstatistics-utable',
  145. $wgLang->formatNum( $wgFlaggedRevsStats['topReviewersCount'] ),
  146. $wgLang->formatNum( $wgFlaggedRevsStats['topReviewersHours'] )
  147. );
  148. $css = 'wikitable flaggedrevs_stats_table';
  149. $reviewChart = "<table class='$css' style='white-space: nowrap;'>\n";
  150. $reviewChart .= '<tr><th>' . wfMsgHtml( 'validationstatistics-user' ) .
  151. '</th><th>' . wfMsgHtml( 'validationstatistics-reviews' ) . '</th></tr>';
  152. foreach ( $data as $userId => $reviews ) {
  153. $reviewChart .= '<tr><td>' . htmlspecialchars( User::whois( $userId ) ) .
  154. '</td><td>' . $wgLang->formatNum( $reviews ) . '</td></tr>';
  155. }
  156. $reviewChart .= "</table>\n";
  157. $wgOut->addHTML( $reviewChart );
  158. }
  159. }
  160. protected function maybeUpdate() {
  161. global $wgFlaggedRevsStatsAge;
  162. if ( !$wgFlaggedRevsStatsAge ) {
  163. return false;
  164. }
  165. $dbCache = wfGetCache( CACHE_DB );
  166. $key = wfMemcKey( 'flaggedrevs', 'statsUpdated' );
  167. $keySQL = wfMemcKey( 'flaggedrevs', 'statsUpdating' );
  168. // If a cache update is needed, do so asynchronously.
  169. // Don't trigger query while another is running.
  170. if ( $dbCache->get( $key ) ) {
  171. wfDebugLog( 'ValidationStatistics', __METHOD__ . " skipping, got data" );
  172. } elseif ( $dbCache->get( $keySQL ) ) {
  173. wfDebugLog( 'ValidationStatistics', __METHOD__ . " skipping, in progress" );
  174. } else {
  175. global $wgPhpCli;
  176. $ext = !empty( $wgPhpCli ) ? $wgPhpCli : 'php';
  177. $path = wfEscapeShellArg( dirname( __FILE__ ) . '/../maintenance/updateStats.php' );
  178. $wiki = wfEscapeShellArg( wfWikiId() );
  179. $devNull = wfIsWindows() ? "NUL:" : "/dev/null";
  180. $commandLine = "$ext $path --wiki=$wiki > $devNull &";
  181. wfDebugLog( 'ValidationStatistics', __METHOD__ . " executing: $commandLine" );
  182. wfShellExec( $commandLine );
  183. return true;
  184. }
  185. return false;
  186. }
  187. protected function readyForQuery() {
  188. if ( !$this->db->tableExists( 'flaggedrevs_statistics' ) ) {
  189. return false;
  190. } else {
  191. return ( 0 != $this->db->selectField( 'flaggedrevs_statistics', 'COUNT(*)' ) );
  192. }
  193. }
  194. protected function getEditorCount() {
  195. return $this->db->selectField( 'user_groups', 'COUNT(*)',
  196. array( 'ug_group' => 'editor' ),
  197. __METHOD__ );
  198. }
  199. protected function getReviewerCount() {
  200. return $this->db->selectField( 'user_groups', 'COUNT(*)',
  201. array( 'ug_group' => 'reviewer' ),
  202. __METHOD__ );
  203. }
  204. protected function getStats() {
  205. if ( $this->latestData === null ) {
  206. $this->latestData = FlaggedRevsStats::getStats();
  207. }
  208. return $this->latestData;
  209. }
  210. protected function getMeanReviewWait() {
  211. $stats = $this->getStats();
  212. return $stats['reviewLag-average'];
  213. }
  214. protected function getMedianReviewWait() {
  215. $stats = $this->getStats();
  216. return $stats['reviewLag-median'];
  217. }
  218. protected function getMeanPendingWait() {
  219. $stats = $this->getStats();
  220. return $stats['pendingLag-average'];
  221. }
  222. protected function getTotalPages( $ns ) {
  223. $stats = $this->getStats();
  224. return isset( $stats['totalPages-NS'][$ns] )
  225. ? $stats['totalPages-NS'][$ns]
  226. : '-';
  227. }
  228. protected function getReviewedPages( $ns ) {
  229. $stats = $this->getStats();
  230. return isset( $stats['reviewedPages-NS'][$ns] )
  231. ? $stats['reviewedPages-NS'][$ns]
  232. : '-';
  233. }
  234. protected function getSyncedPages( $ns ) {
  235. $stats = $this->getStats();
  236. return isset( $stats['syncedPages-NS'][$ns] )
  237. ? $stats['syncedPages-NS'][$ns]
  238. : '-';
  239. }
  240. protected function getPercentiles() {
  241. $stats = $this->getStats();
  242. return $stats['reviewLag-percentile'];
  243. }
  244. protected function getLastUpdate() {
  245. $stats = $this->getStats();
  246. return $stats['statTimestamp'];
  247. }
  248. // top X reviewers in the last Y hours
  249. protected function getTopReviewers() {
  250. global $wgFlaggedRevsStats;
  251. $key = wfMemcKey( 'flaggedrevs', 'reviewTopUsers' );
  252. $dbCache = wfGetCache( CACHE_DB );
  253. $data = $dbCache->get( $key );
  254. if ( is_array( $data ) ) {
  255. return $data; // cache hit
  256. }
  257. $limit = (int)$wgFlaggedRevsStats['topReviewersCount'];
  258. $seconds = 3600*$wgFlaggedRevsStats['topReviewersHours'];
  259. $dbr = wfGetDB( DB_SLAVE );
  260. $cutoff = $dbr->timestamp( time() - $seconds );
  261. $res = $dbr->select( 'logging',
  262. array( 'log_user', 'COUNT(*) AS reviews' ),
  263. array(
  264. 'log_type' => 'review', // page reviews
  265. // manual approvals (filter on log_action)
  266. 'log_action' => array( 'approve', 'approve2', 'approve-i', 'approve2-i' ),
  267. 'log_timestamp >= ' . $dbr->addQuotes( $cutoff ) // last hour
  268. ),
  269. __METHOD__,
  270. array( 'GROUP BY' => 'log_user', 'ORDER BY' => 'reviews DESC', 'LIMIT' => $limit )
  271. );
  272. $data = array();
  273. foreach ( $res as $row ) {
  274. $data[$row->log_user] = $row->reviews;
  275. }
  276. // Save/cache users
  277. $dbCache->set( $key, $data, 3600 );
  278. return $data;
  279. }
  280. }